Skip to content
This repository has been archived by the owner on Feb 27, 2024. It is now read-only.

Commit

Permalink
Make ProtoFile a container type (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
shaldengeki committed Mar 11, 2023
1 parent f2049fd commit 8aa68dc
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 43 deletions.
111 changes: 74 additions & 37 deletions src/proto_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from src.proto_extend import ProtoExtend
from src.proto_import import ProtoImport
from src.proto_message import ProtoMessage
from src.proto_node import ParsedProtoNode, ProtoNode, ProtoNodeDiff
from src.proto_node import ParsedProtoNode, ProtoContainerNode, ProtoNode, ProtoNodeDiff
from src.proto_option import ProtoOption
from src.proto_package import ProtoPackage
from src.proto_service import ProtoService
Expand All @@ -21,13 +21,40 @@ class ParsedProtoFileNode(ParsedProtoNode):
remaining_source: str


class ProtoFile(ProtoNode):
def __init__(self, syntax: ProtoSyntax, nodes: list[ProtoNode], *args, **kwargs):
class ProtoFileHeaderNode(ProtoNode):
def __init__(
self, header_nodes: list[ProtoNode], syntax: ProtoSyntax, *args, **kwargs
):
super().__init__(*args, **kwargs)
self.header_nodes = header_nodes
self.syntax = syntax
self.nodes = nodes

if len([node for node in nodes if isinstance(node, ProtoPackage)]) > 1:
@classmethod
def match(
cls,
proto_source: str,
parent: Optional["ProtoNode"] = None,
) -> Optional["ParsedProtoNode"]:
pass

def serialize(self) -> str:
raise NotImplementedError

def normalize(self) -> Optional["ProtoNode"]:
raise NotImplementedError


class ParsedProtoFileHeaderNode(ParsedProtoNode):
node: "ProtoFileHeaderNode"
remaining_source: str


class ProtoFile(ProtoContainerNode):
def __init__(self, syntax: ProtoSyntax, *args, **kwargs):
super().__init__(*args, **kwargs)
self.syntax = syntax

if len([node for node in self.nodes if isinstance(node, ProtoPackage)]) > 1:
raise ValueError(f"Proto can't have more than one package statement")

@property
Expand All @@ -53,9 +80,34 @@ def enums(self) -> list[ProtoEnum]:
def messages(self) -> list[ProtoMessage]:
return [node for node in self.nodes if isinstance(node, ProtoMessage)]

@staticmethod
def parse_partial_content(partial_proto_content: str) -> ParsedProtoNode:
node_types: list[type[ProtoNode]] = [
@classmethod
def match_header(
cls,
proto_source: str,
parent: Optional["ProtoNode"] = None,
) -> Optional["ParsedProtoNode"]:
syntax, parsed_tree, remaining_source = cls.parse_syntax_and_preceding_comments(
proto_source.strip()
)
return ParsedProtoFileHeaderNode(
ProtoFileHeaderNode(list(parsed_tree), syntax, parent=parent),
remaining_source.strip(),
)

@classmethod
def match_footer(
cls,
proto_source: str,
parent: Optional[ProtoNode] = None,
) -> Optional[str]:
trimmed_source = proto_source.strip()
if trimmed_source == "":
return ""
return None

@classmethod
def container_types(cls) -> list[type[ProtoNode]]:
return [
ProtoImport,
ProtoMessage,
ProtoPackage,
Expand All @@ -66,16 +118,20 @@ def parse_partial_content(partial_proto_content: str) -> ParsedProtoNode:
ProtoSingleLineComment,
ProtoMultiLineComment,
]
for node_type in node_types:
try:
match_result = node_type.match(partial_proto_content)
except (ValueError, IndexError, TypeError):
raise ValueError(
f"Could not parse proto content:\n{partial_proto_content}"
)
if match_result is not None:
return match_result
raise ValueError(f"Could not parse proto content:\n{partial_proto_content}")

@classmethod
def construct(
cls,
header_match: "ParsedProtoNode",
contained_nodes: list[ProtoNode],
footer_match: str,
parent: Optional[ProtoNode] = None,
) -> ProtoNode:
assert isinstance(header_match, ParsedProtoFileHeaderNode)
return cls(
header_match.node.syntax,
header_match.node.header_nodes + contained_nodes,
)

@staticmethod
def parse_syntax_and_preceding_comments(
Expand Down Expand Up @@ -108,25 +164,6 @@ def parse_syntax_and_preceding_comments(

return syntax, parsed_tree, proto_content

@classmethod
def match(
cls, proto_content: str, parent: Optional["ProtoNode"] = None
) -> Optional[ParsedProtoFileNode]:
syntax, parsed_tree, proto_content = cls.parse_syntax_and_preceding_comments(
proto_content
)
new_tree: list[ProtoNode] = list(parsed_tree)
while proto_content:
# Remove empty statements.
if proto_content.startswith(";"):
proto_content = proto_content[1:].strip()
continue
match_result = cls.parse_partial_content(proto_content)
new_tree.append(match_result.node)
proto_content = match_result.remaining_source.strip()

return ParsedProtoFileNode(cls(syntax, new_tree), proto_content)

def normalize(self) -> Optional["ProtoNode"]:
normalized_nodes = [n.normalize() for n in self.nodes]
return ProtoFile(
Expand Down
9 changes: 6 additions & 3 deletions src/proto_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def match(

proto_source = header_match.remaining_source.strip()
nodes = []
footer_match: Optional[str] = None
while proto_source:
# Remove empty statements.
if proto_source.startswith(";"):
Expand All @@ -114,9 +115,11 @@ def match(
proto_source = match_result.remaining_source.strip()

if footer_match is None:
raise ValueError(
f"Footer was not found when matching container node {cls} for remaining proto source {proto_source}"
)
footer_match = cls.match_footer(proto_source, parent)
if footer_match is None:
raise ValueError(
f"Footer was not found when matching container node {cls} for remaining proto source {proto_source}"
)

return ParsedProtoNode(
cls.construct(header_match, nodes, footer_match, parent=parent),
Expand Down
5 changes: 2 additions & 3 deletions src/util/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@ def loads(proto_content: str) -> ProtoFile:
try:
parsed_file = ProtoFile.match(proto_content, None)
except ValueError as e:
raise ParseError(
f"Proto doesn't have parseable syntax:\n{proto_content}\n{e}"
)
raise ParseError(f"Proto doesn't have parseable syntax:\n{e}")
if parsed_file is None:
raise ParseError(f"Proto doesn't have parseable syntax:\n{proto_content}")

assert isinstance(parsed_file.node, ProtoFile)
return parsed_file.node


Expand Down

0 comments on commit 8aa68dc

Please sign in to comment.