from typing import Any, Dict, Union
from line_iterator import LineIterator
from runtime_collections import add_to_statistics, add_to_custom_yamls
from parse_namespace import parse_namespace
from parse_enum import parse_enum_class
from parse_struct import parse_struct
from parse_using import parse_using
from parse_define import parse_define_macros, is_known_macros
from parse_class import parse_class, parse_friend_class, parse_template_prefix
from parse_method import parse_method_or_constructor
from parse_arguments import parse_argument
from log_tools import debug_log
def deep_copy(data: Any) -> Any:
if isinstance(data, (dict, list)):
return data.copy()
return data
class CppParser:
def __init__(self, data: str, namespace: str = "", parent_class_name: str = ""):
self.it = LineIterator(data)
self.parsed: Any = None
self.res: Dict[str, Any] = {}
self.template: Union[str, None] = None
self.parent_class_name = parent_class_name
self.namespace = namespace
self.current_modifier = ""
def parse(self) -> Dict[str, Any]:
while self.it.next_line():
if self.it.is_access_modifier():
self.update_access_modifier()
elif self.it.is_skip_line() or self.current_modifier in ["private", "protected"]:
add_to_statistics("skip", self.it.current_line)
elif self.it.is_template():
self.it.end, self.template = parse_template_prefix(self.it.data, self.it.start)
elif self.it.is_using():
self.it.end, self.parsed = parse_using(self.it.data, self.it.start)
self.res_append_in_modifier("usings")
elif self.it.is_namespace():
self.it.end, self.parsed = parse_namespace(self.it.data, self.it.start)
self.res_update()
elif self.it.is_enum():
self.it.end, self.parsed = parse_enum_class(self.it.data, self.it.start)
self.res_append_namespace()
elif self.it.is_struct():
self.it.end, self.parsed = parse_struct(self.it.data, self.it.start)
self.res_append("structs")
elif self.it.is_define_macro():
self.it.end, self.parsed = parse_define_macros(self.it.data, self.it.start)
self.res_append("macros")
elif is_known_macros(self.it.current_line):
self.parsed = self.it.current_line
self.res_append("known_macroses")
elif self.it.is_firend_class():
self.it.end, self.parsed = parse_friend_class(self.it.data, self.it.start)
self.res_append("friends")
elif self.it.is_class_forward_decl():
self.parsed = self.it.current_line.replace("class", "").strip(" ;")
self.res_append("class_forward_declaration")
elif self.it.is_class_definition():
self.it.end, self.parsed = parse_class(
self.it.data, self.it.start, self.namespace, self.parent_class_name
)
self.res_append_class_definition()
elif self.it.is_method_or_constructor():
self.it.end, self.parsed = parse_method_or_constructor(self.it.data, self.it.start)
self.res_append_method_or_constructor()
elif self.it.is_field():
self.parsed = parse_argument(self.it.data[self.it.start : self.it.next_semicolon])
self.it.end = self.it.next_semicolon
self.res_append_field()
else:
add_to_statistics("unreachable", self.it.current_line)
return self.res
def res_append(self, key: str) -> None:
if not self.parsed:
return
self.parsed_update_template()
if key not in self.res:
self.res[key] = []
self.res[key].append(deep_copy(self.parsed))
def res_append_in_modifier(self, key: str) -> None:
if not self.parsed:
return
self.parsed_update_template()
if self.current_modifier == "":
if key == "usings":
self.res_append("usings")
return
raise RuntimeError("Unreachable")
if key not in self.res[self.current_modifier]:
self.res[self.current_modifier][key] = []
self.res[self.current_modifier][key].append(deep_copy(self.parsed))
def res_update(self) -> None:
if self.parsed:
self.parsed_update_template()
self.res.update(self.parsed)
def res_append_namespace(self) -> None:
if not self.parsed:
return
self.parsed["namespace"] = self.namespace
if self.parent_class_name != "":
self.parsed["parent_class_name"] = self.parent_class_name
if "flags" in self.parsed or "flag_unions" in self.parsed:
self.res_append("enums")
add_to_custom_yamls("allEnums", "enums", self.parsed)
def update_access_modifier(self) -> None:
if self.parent_class_name == "":
raise RuntimeError("Found modifier not in class")
self.current_modifier = self.it.current_line.strip(" :")
if self.current_modifier not in self.res:
self.res[self.current_modifier] = {}
def res_append_method_or_constructor(self) -> None:
if not self.parsed:
return
if self.parsed["name"] == self.parent_class_name:
self.res_append_in_modifier("constructors")
elif self.parsed["name"] == "~" + self.parent_class_name:
self.res_append_in_modifier("destructors")
elif self.current_modifier != "":
self.res_append_in_modifier("methods")
else:
self.res_append("functions")
def res_append_field(self) -> None:
if self.current_modifier != "":
self.res_append_in_modifier("fields")
else:
self.res_append("vars")
def res_append_class_definition(self) -> None:
self.res_append("class_definitions")
def parsed_update_template(self) -> None:
if self.template and self.parsed:
if isinstance(self.parsed, dict):
self.parsed["template"] = self.template
self.template = None
else:
debug_log(f"Skipping template for '{self.parsed}'")