Allow parser visitors to leverage the parser's state instead of creating their own stacks

This commit is contained in:
Dustin Spicuzza 2023-09-03 19:44:07 -04:00
parent 15ec31b84f
commit 3bae95f2a7
5 changed files with 125 additions and 79 deletions

View File

@ -106,6 +106,8 @@ class CxxParser:
else: else:
self.debug_print = lambda fmt, *args: None self.debug_print = lambda fmt, *args: None
self.visitor.on_parse_start(self.state)
# #
# State management # State management
# #

View File

@ -28,26 +28,41 @@ class ParsedTypeModifiers(typing.NamedTuple):
raise CxxParseError(f"{msg}: unexpected '{tok.value}'") raise CxxParseError(f"{msg}: unexpected '{tok.value}'")
class State: #: custom user data for this state type
T = typing.TypeVar("T")
#: type of custom user data for a parent state
PT = typing.TypeVar("PT")
class State(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 state
parent: typing.Optional["State"] parent: typing.Optional["State[PT, typing.Any]"]
#: Approximate location that the parsed element was found at #: Approximate location that the parsed element was found at
location: Location location: Location
def __init__(self, parent: typing.Optional["State"]) -> None: def __init__(self, parent: typing.Optional["State[PT, typing.Any]"]) -> None:
self.parent = parent self.parent = parent
def _finish(self, visitor: "CxxVisitor") -> None: def _finish(self, visitor: "CxxVisitor") -> None:
pass pass
class EmptyBlockState(State): class EmptyBlockState(State[T, PT]):
parent: State[PT, typing.Any]
def _finish(self, visitor: "CxxVisitor") -> None: def _finish(self, visitor: "CxxVisitor") -> None:
visitor.on_empty_block_end(self) visitor.on_empty_block_end(self)
class ExternBlockState(State): class ExternBlockState(State[T, PT]):
parent: State[PT, typing.Any]
#: The linkage for this extern block #: The linkage for this extern block
linkage: str linkage: str
@ -59,7 +74,9 @@ class ExternBlockState(State):
visitor.on_extern_block_end(self) visitor.on_extern_block_end(self)
class NamespaceBlockState(State): class NamespaceBlockState(State[T, PT]):
parent: State[PT, typing.Any]
#: The incremental namespace for this block #: The incremental namespace for this block
namespace: NamespaceDecl namespace: NamespaceDecl
@ -73,7 +90,9 @@ class NamespaceBlockState(State):
visitor.on_namespace_end(self) visitor.on_namespace_end(self)
class ClassBlockState(State): class ClassBlockState(State[T, PT]):
parent: State[PT, typing.Any]
#: class decl block being processed #: class decl block being processed
class_decl: ClassDecl class_decl: ClassDecl

View File

@ -179,6 +179,13 @@ class ParsedData:
# Visitor implementation # Visitor implementation
# #
# define what user data we store in each state type
SState = State[Block, Block]
SEmptyBlockState = EmptyBlockState[Block, Block]
SExternBlockState = ExternBlockState[Block, Block]
SNamespaceBlockState = NamespaceBlockState[NamespaceScope, NamespaceScope]
SClassBlockState = ClassBlockState[ClassScope, Block]
class SimpleCxxVisitor: class SimpleCxxVisitor:
""" """
@ -190,43 +197,35 @@ class SimpleCxxVisitor:
""" """
data: ParsedData data: ParsedData
namespace: NamespaceScope
block: Block
def __init__(self) -> None: def on_parse_start(self, state: SNamespaceBlockState) -> None:
self.namespace = NamespaceScope("") ns = NamespaceScope("")
self.block = self.namespace self.data = ParsedData(ns)
state.user_data = ns
self.ns_stack = typing.Deque[NamespaceScope]() def on_pragma(self, state: SState, content: Value) -> None:
self.block_stack = typing.Deque[Block]()
self.data = ParsedData(self.namespace)
def on_pragma(self, state: State, content: Value) -> None:
self.data.pragmas.append(Pragma(content)) self.data.pragmas.append(Pragma(content))
def on_include(self, state: State, filename: str) -> None: def on_include(self, state: SState, filename: str) -> None:
self.data.includes.append(Include(filename)) self.data.includes.append(Include(filename))
def on_empty_block_start(self, state: EmptyBlockState) -> None: def on_empty_block_start(self, state: SEmptyBlockState) -> None:
# this matters for some scope/resolving purposes, but you're # this matters for some scope/resolving purposes, but you're
# probably going to want to use clang if you care about that # probably going to want to use clang if you care about that
# level of detail # level of detail
state.user_data = state.parent.user_data
def on_empty_block_end(self, state: SEmptyBlockState) -> None:
pass pass
def on_empty_block_end(self, state: EmptyBlockState) -> None: def on_extern_block_start(self, state: SExternBlockState) -> None:
state.user_data = state.parent.user_data
def on_extern_block_end(self, state: SExternBlockState) -> None:
pass pass
def on_extern_block_start(self, state: ExternBlockState) -> None: def on_namespace_start(self, state: SNamespaceBlockState) -> None:
pass # TODO parent_ns = state.parent.user_data
def on_extern_block_end(self, state: ExternBlockState) -> None:
pass
def on_namespace_start(self, state: NamespaceBlockState) -> None:
parent_ns = self.namespace
self.block_stack.append(parent_ns)
self.ns_stack.append(parent_ns)
ns = None ns = None
names = state.namespace.names names = state.namespace.names
@ -247,81 +246,76 @@ class SimpleCxxVisitor:
ns.inline = state.namespace.inline ns.inline = state.namespace.inline
ns.doxygen = state.namespace.doxygen ns.doxygen = state.namespace.doxygen
self.block = ns state.user_data = ns
self.namespace = ns
def on_namespace_end(self, state: NamespaceBlockState) -> None: def on_namespace_end(self, state: SNamespaceBlockState) -> None:
self.block = self.block_stack.pop() pass
self.namespace = self.ns_stack.pop()
def on_namespace_alias(self, state: State, alias: NamespaceAlias) -> None: def on_namespace_alias(self, state: SState, alias: NamespaceAlias) -> None:
assert isinstance(self.block, NamespaceScope) assert isinstance(state.user_data, NamespaceScope)
self.block.ns_alias.append(alias) state.user_data.ns_alias.append(alias)
def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None: def on_forward_decl(self, state: SState, fdecl: ForwardDecl) -> None:
self.block.forward_decls.append(fdecl) state.user_data.forward_decls.append(fdecl)
def on_template_inst(self, state: State, inst: TemplateInst) -> None: def on_template_inst(self, state: SState, inst: TemplateInst) -> None:
assert isinstance(self.block, NamespaceScope) assert isinstance(state.user_data, NamespaceScope)
self.block.template_insts.append(inst) state.user_data.template_insts.append(inst)
def on_variable(self, state: State, v: Variable) -> None: def on_variable(self, state: SState, v: Variable) -> None:
assert isinstance(self.block, NamespaceScope) assert isinstance(state.user_data, NamespaceScope)
self.block.variables.append(v) state.user_data.variables.append(v)
def on_function(self, state: State, fn: Function) -> None: def on_function(self, state: SState, fn: Function) -> None:
assert isinstance(self.block, NamespaceScope) assert isinstance(state.user_data, NamespaceScope)
self.block.functions.append(fn) state.user_data.functions.append(fn)
def on_method_impl(self, state: State, method: Method) -> None: def on_method_impl(self, state: SState, method: Method) -> None:
assert isinstance(self.block, NamespaceScope) assert isinstance(state.user_data, NamespaceScope)
self.block.method_impls.append(method) state.user_data.method_impls.append(method)
def on_typedef(self, state: State, typedef: Typedef) -> None: def on_typedef(self, state: SState, typedef: Typedef) -> None:
self.block.typedefs.append(typedef) state.user_data.typedefs.append(typedef)
def on_using_namespace(self, state: State, namespace: typing.List[str]) -> None: def on_using_namespace(self, state: SState, namespace: typing.List[str]) -> None:
assert isinstance(self.block, NamespaceScope) assert isinstance(state.user_data, NamespaceScope)
ns = UsingNamespace("::".join(namespace)) ns = UsingNamespace("::".join(namespace))
self.block.using_ns.append(ns) state.user_data.using_ns.append(ns)
def on_using_alias(self, state: State, using: UsingAlias) -> None: def on_using_alias(self, state: SState, using: UsingAlias) -> None:
self.block.using_alias.append(using) state.user_data.using_alias.append(using)
def on_using_declaration(self, state: State, using: UsingDecl) -> None: def on_using_declaration(self, state: SState, using: UsingDecl) -> None:
self.block.using.append(using) state.user_data.using.append(using)
# #
# Enums # Enums
# #
def on_enum(self, state: State, enum: EnumDecl) -> None: def on_enum(self, state: SState, enum: EnumDecl) -> None:
self.block.enums.append(enum) state.user_data.enums.append(enum)
# #
# Class/union/struct # Class/union/struct
# #
def on_class_start(self, state: ClassBlockState) -> None: def on_class_start(self, state: SClassBlockState) -> None:
parent = state.parent.user_data
block = ClassScope(state.class_decl) block = ClassScope(state.class_decl)
self.block.classes.append(block) parent.classes.append(block)
self.block_stack.append(self.block) state.user_data = block
self.block = block
def on_class_field(self, state: State, f: Field) -> None: def on_class_field(self, state: SClassBlockState, f: Field) -> None:
assert isinstance(self.block, ClassScope) state.user_data.fields.append(f)
self.block.fields.append(f)
def on_class_method(self, state: ClassBlockState, method: Method) -> None: def on_class_method(self, state: SClassBlockState, method: Method) -> None:
assert isinstance(self.block, ClassScope) state.user_data.methods.append(method)
self.block.methods.append(method)
def on_class_friend(self, state: ClassBlockState, friend: FriendDecl) -> None: def on_class_friend(self, state: SClassBlockState, friend: FriendDecl) -> None:
assert isinstance(self.block, ClassScope) state.user_data.friends.append(friend)
self.block.friends.append(friend)
def on_class_end(self, state: ClassBlockState) -> None: def on_class_end(self, state: SClassBlockState) -> None:
self.block = self.block_stack.pop() pass
def parse_string( def parse_string(

View File

@ -37,6 +37,11 @@ class CxxVisitor(Protocol):
Defines the interface used by the parser to emit events Defines the interface used by the parser to emit events
""" """
def on_parse_start(self, state: NamespaceBlockState) -> None:
"""
Called when parsing begins
"""
def on_pragma(self, state: State, content: Value) -> None: def on_pragma(self, state: State, content: Value) -> None:
""" """
Called once for each ``#pragma`` directive encountered Called once for each ``#pragma`` directive encountered

View File

@ -351,3 +351,29 @@ def test_warning_directive() -> None:
data = parse_string(content, cleandoc=True) data = parse_string(content, cleandoc=True)
assert data == ParsedData() assert data == ParsedData()
def test_empty_block() -> None:
"""
Ensure the simple visitor doesn't break with an empty block
"""
content = """
{
class X {};
}
"""
data = parse_string(content, cleandoc=True)
assert data == ParsedData(
namespace=NamespaceScope(
classes=[
ClassScope(
class_decl=ClassDecl(
typename=PQName(
segments=[NameSpecifier(name="X")], classkey="class"
)
)
)
]
)
)