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:
self.debug_print = lambda fmt, *args: None
self.visitor.on_parse_start(self.state)
#
# State management
#

View File

@ -28,26 +28,41 @@ class ParsedTypeModifiers(typing.NamedTuple):
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: typing.Optional["State"]
parent: typing.Optional["State[PT, typing.Any]"]
#: Approximate location that the parsed element was found at
location: Location
def __init__(self, parent: typing.Optional["State"]) -> None:
def __init__(self, parent: typing.Optional["State[PT, typing.Any]"]) -> None:
self.parent = parent
def _finish(self, visitor: "CxxVisitor") -> None:
pass
class EmptyBlockState(State):
class EmptyBlockState(State[T, PT]):
parent: State[PT, typing.Any]
def _finish(self, visitor: "CxxVisitor") -> None:
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
linkage: str
@ -59,7 +74,9 @@ class ExternBlockState(State):
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
namespace: NamespaceDecl
@ -73,7 +90,9 @@ class NamespaceBlockState(State):
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: ClassDecl

View File

@ -179,6 +179,13 @@ class ParsedData:
# 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:
"""
@ -190,43 +197,35 @@ class SimpleCxxVisitor:
"""
data: ParsedData
namespace: NamespaceScope
block: Block
def __init__(self) -> None:
self.namespace = NamespaceScope("")
self.block = self.namespace
def on_parse_start(self, state: SNamespaceBlockState) -> None:
ns = NamespaceScope("")
self.data = ParsedData(ns)
state.user_data = ns
self.ns_stack = typing.Deque[NamespaceScope]()
self.block_stack = typing.Deque[Block]()
self.data = ParsedData(self.namespace)
def on_pragma(self, state: State, content: Value) -> None:
def on_pragma(self, state: SState, content: Value) -> None:
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))
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
# probably going to want to use clang if you care about that
# level of detail
state.user_data = state.parent.user_data
def on_empty_block_end(self, state: SEmptyBlockState) -> None:
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
def on_extern_block_start(self, state: ExternBlockState) -> None:
pass # TODO
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)
def on_namespace_start(self, state: SNamespaceBlockState) -> None:
parent_ns = state.parent.user_data
ns = None
names = state.namespace.names
@ -247,81 +246,76 @@ class SimpleCxxVisitor:
ns.inline = state.namespace.inline
ns.doxygen = state.namespace.doxygen
self.block = ns
self.namespace = ns
state.user_data = ns
def on_namespace_end(self, state: NamespaceBlockState) -> None:
self.block = self.block_stack.pop()
self.namespace = self.ns_stack.pop()
def on_namespace_end(self, state: SNamespaceBlockState) -> None:
pass
def on_namespace_alias(self, state: State, alias: NamespaceAlias) -> None:
assert isinstance(self.block, NamespaceScope)
self.block.ns_alias.append(alias)
def on_namespace_alias(self, state: SState, alias: NamespaceAlias) -> None:
assert isinstance(state.user_data, NamespaceScope)
state.user_data.ns_alias.append(alias)
def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None:
self.block.forward_decls.append(fdecl)
def on_forward_decl(self, state: SState, fdecl: ForwardDecl) -> None:
state.user_data.forward_decls.append(fdecl)
def on_template_inst(self, state: State, inst: TemplateInst) -> None:
assert isinstance(self.block, NamespaceScope)
self.block.template_insts.append(inst)
def on_template_inst(self, state: SState, inst: TemplateInst) -> None:
assert isinstance(state.user_data, NamespaceScope)
state.user_data.template_insts.append(inst)
def on_variable(self, state: State, v: Variable) -> None:
assert isinstance(self.block, NamespaceScope)
self.block.variables.append(v)
def on_variable(self, state: SState, v: Variable) -> None:
assert isinstance(state.user_data, NamespaceScope)
state.user_data.variables.append(v)
def on_function(self, state: State, fn: Function) -> None:
assert isinstance(self.block, NamespaceScope)
self.block.functions.append(fn)
def on_function(self, state: SState, fn: Function) -> None:
assert isinstance(state.user_data, NamespaceScope)
state.user_data.functions.append(fn)
def on_method_impl(self, state: State, method: Method) -> None:
assert isinstance(self.block, NamespaceScope)
self.block.method_impls.append(method)
def on_method_impl(self, state: SState, method: Method) -> None:
assert isinstance(state.user_data, NamespaceScope)
state.user_data.method_impls.append(method)
def on_typedef(self, state: State, typedef: Typedef) -> None:
self.block.typedefs.append(typedef)
def on_typedef(self, state: SState, typedef: Typedef) -> None:
state.user_data.typedefs.append(typedef)
def on_using_namespace(self, state: State, namespace: typing.List[str]) -> None:
assert isinstance(self.block, NamespaceScope)
def on_using_namespace(self, state: SState, namespace: typing.List[str]) -> None:
assert isinstance(state.user_data, NamespaceScope)
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:
self.block.using_alias.append(using)
def on_using_alias(self, state: SState, using: UsingAlias) -> None:
state.user_data.using_alias.append(using)
def on_using_declaration(self, state: State, using: UsingDecl) -> None:
self.block.using.append(using)
def on_using_declaration(self, state: SState, using: UsingDecl) -> None:
state.user_data.using.append(using)
#
# Enums
#
def on_enum(self, state: State, enum: EnumDecl) -> None:
self.block.enums.append(enum)
def on_enum(self, state: SState, enum: EnumDecl) -> None:
state.user_data.enums.append(enum)
#
# 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)
self.block.classes.append(block)
self.block_stack.append(self.block)
self.block = block
parent.classes.append(block)
state.user_data = block
def on_class_field(self, state: State, f: Field) -> None:
assert isinstance(self.block, ClassScope)
self.block.fields.append(f)
def on_class_field(self, state: SClassBlockState, f: Field) -> None:
state.user_data.fields.append(f)
def on_class_method(self, state: ClassBlockState, method: Method) -> None:
assert isinstance(self.block, ClassScope)
self.block.methods.append(method)
def on_class_method(self, state: SClassBlockState, method: Method) -> None:
state.user_data.methods.append(method)
def on_class_friend(self, state: ClassBlockState, friend: FriendDecl) -> None:
assert isinstance(self.block, ClassScope)
self.block.friends.append(friend)
def on_class_friend(self, state: SClassBlockState, friend: FriendDecl) -> None:
state.user_data.friends.append(friend)
def on_class_end(self, state: ClassBlockState) -> None:
self.block = self.block_stack.pop()
def on_class_end(self, state: SClassBlockState) -> None:
pass
def parse_string(

View File

@ -37,6 +37,11 @@ class CxxVisitor(Protocol):
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:
"""
Called once for each ``#pragma`` directive encountered

View File

@ -351,3 +351,29 @@ def test_warning_directive() -> None:
data = parse_string(content, cleandoc=True)
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"
)
)
)
]
)
)