From db9ad5133921bcd9c31e686f3488d30dbcab27b5 Mon Sep 17 00:00:00 2001 From: Theodoros Theodoridis Date: Fri, 8 Sep 2023 12:36:11 +0200 Subject: [PATCH] [compiler] adds json serialization to more types --- diopter/compiler.py | 234 ++++++++++++++++++++++++++- tests/compiler_serialization_test.py | 81 ++++++++++ tests/compiler_test.py | 17 -- 3 files changed, 311 insertions(+), 21 deletions(-) create mode 100644 tests/compiler_serialization_test.py diff --git a/diopter/compiler.py b/diopter/compiler.py index d09917e..56cc279 100644 --- a/diopter/compiler.py +++ b/diopter/compiler.py @@ -115,6 +115,29 @@ def to_suffix(self) -> str: case Language.CPP: return ".cpp" + def to_json_dict(self) -> dict[str, Any]: + """Returns a dictionary that can be serialized to json. + + Returns: + dict[str, Any]: + the dictionary + """ + return {"name": self.name} + + @staticmethod + def from_json_dict(d: dict[str, Any]) -> Language: + """Returns a language parsed from a json dictionary. + + Args: + d (dict[str, Any]): + the dictionary + + Returns: + Language: + the language + """ + return Language[d["name"]] + @dataclass(frozen=True) class SourcePath: @@ -131,12 +154,10 @@ def __del__(self) -> None: @dataclass(frozen=True, kw_only=True) class Source(ABC): - """A C or C++ base class for source programs together + """A base class for C or C++source programs together with flags, includes and macro definitions. Attributes: - code (str): - the source code language (Language): the program's language defined_macros (tuple[str,...]): @@ -198,7 +219,122 @@ def get_file_suffix(self) -> str: @abstractmethod def get_filename(self) -> SourcePath: - pass + raise NotImplementedError + + def to_json_dict(self) -> dict[str, Any]: + """Returns a dictionary that can be serialized to json. + + Can be re-parses with Source.from_json_dict. + + Returns: + dict[str, Any]: + the dictionary + """ + j = { + "language": self.language.to_json_dict(), + "defined_macros": self.defined_macros, + "include_paths": self.include_paths, + "system_include_paths": self.system_include_paths, + "flags": self.flags, + } + + j |= self.to_json_dict_impl() + + assert set(j.keys()) == set(field.name for field in fields(self)) | set( + ("kind",) + ) + + return j + + @abstractmethod + def to_json_dict_impl(self) -> dict[str, Any]: + """Returns a dictionary that can be serialized to json. + + Subclasses should implement this method and serialize + their specific attributes. + + + Returns: + dict[str, Any]: + the dictionary + """ + raise NotImplementedError + + @classmethod + def from_json_dict(cls, d: dict[str, Any]) -> Source: + """Returns a source parsed from a json dictionary. + + Args: + d (dict[str, Any]): + the dictionary + + Returns: + Source: + the source + """ + if cls is Source: + match d["kind"]: + case "SourceFile": + return SourceFile.from_json_dict_impl( + d, + language=Language.from_json_dict(d["language"]), + defined_macros=tuple(d["defined_macros"]), + include_paths=tuple(d["include_paths"]), + system_include_paths=tuple(d["system_include_paths"]), + flags=tuple(d["flags"]), + ) + case "SourceProgram": + return SourceProgram.from_json_dict_impl( + d, + language=Language.from_json_dict(d["language"]), + defined_macros=tuple(d["defined_macros"]), + include_paths=tuple(d["include_paths"]), + system_include_paths=tuple(d["system_include_paths"]), + flags=tuple(d["flags"]), + ) + + case _: + raise ValueError(f"Unknown kind: {d['kind']}") + else: + return cls.from_json_dict_impl( + d, + language=Language.from_json_dict(d["language"]), + defined_macros=tuple(d["defined_macros"]), + include_paths=tuple(d["include_paths"]), + system_include_paths=tuple(d["system_include_paths"]), + flags=tuple(d["flags"]), + ) + + @staticmethod + @abstractmethod + def from_json_dict_impl( + d: dict[str, Any], + language: Language, + defined_macros: tuple[str, ...], + include_paths: tuple[str, ...], + system_include_paths: tuple[str, ...], + flags: tuple[str, ...], + ) -> Source: + """Returns a source parsed from a json dictionary. + + Subclasses should implement this and parse their + specific attributes from the dictionary. + + Args: + d (dict[str, Any]): + the dictionary + language (Language): + the program's language + defined_macros (tuple[str,...]): + macros that will be defined when compiling this program + include_paths (tuple[str,...]): + include paths which will be passed to the compiler (with -I) + system_include_paths (tuple[str,...]): + system include paths which will be passed to the compiler (with -isystem) + flags (tuple[str,...]): + flags, prefixed with a dash ("-") that will be passed to the compiler + """ + raise NotImplementedError ProgramType = TypeVar("ProgramType", bound="SourceProgram") @@ -274,6 +410,51 @@ def with_code(self: ProgramType, new_code: str) -> ProgramType: """ return replace(self, code=new_code) + @staticmethod + def from_json_dict_impl( + d: dict[str, Any], + language: Language, + defined_macros: tuple[str, ...], + include_paths: tuple[str, ...], + system_include_paths: tuple[str, ...], + flags: tuple[str, ...], + ) -> SourceProgram: + """Returns a source program parsed from a json dictionary. + + Args: + d (dict[str, Any]): + the dictionary + language (Language): + the program's language + defined_macros (tuple[str,...]): + macros that will be defined when compiling this program + include_paths (tuple[str,...]): + include paths which will be passed to the compiler (with -I) + system_include_paths (tuple[str,...]): + system include paths which will be passed to the compiler (with -isystem) + flags (tuple[str,...]): + flags, prefixed with a dash ("-") that will be passed to the compiler + """ + assert d["kind"] == "SourceProgram" + return SourceProgram( + code=d["code"], + language=language, + defined_macros=defined_macros, + include_paths=include_paths, + system_include_paths=system_include_paths, + flags=flags, + ) + + def to_json_dict_impl(self) -> dict[str, Any]: + """Returns a dictionary representation of this source program, + only including the SourceProgram specific attributes. + + Returns: + dict[str, Any]: + the dictionary + """ + return {"kind": "SourceProgram", "code": self.code} + @dataclass(frozen=True, kw_only=True) class SourceFile(Source): @@ -304,6 +485,51 @@ def __post_init__(self) -> None: def get_filename(self) -> SourcePath: return SourcePath(self.filename, None) + @staticmethod + def from_json_dict_impl( + d: dict[str, Any], + language: Language, + defined_macros: tuple[str, ...], + include_paths: tuple[str, ...], + system_include_paths: tuple[str, ...], + flags: tuple[str, ...], + ) -> SourceFile: + """Returns a source file parsed from a json dictionary. + + Args: + d (dict[str, Any]): + the dictionary + language (Language): + the program's language + defined_macros (tuple[str,...]): + macros that will be defined when compiling this program + include_paths (tuple[str,...]): + include paths which will be passed to the compiler (with -I) + system_include_paths (tuple[str,...]): + system include paths which will be passed to the compiler (with -isystem) + flags (tuple[str,...]): + flags, prefixed with a dash ("-") that will be passed to the compiler + """ + assert d["kind"] == "SourceFile" + return SourceFile( + filename=Path(d["filename"]), + language=language, + defined_macros=defined_macros, + include_paths=include_paths, + system_include_paths=system_include_paths, + flags=flags, + ) + + def to_json_dict_impl(self) -> dict[str, Any]: + """Returns a dictionary representation of this source program, + only including the SourceFile specific attributes. + + Returns: + dict[str, Any]: + the dictionary + """ + return {"kind": "SourceFile", "filename": self.filename} + Revision = str diff --git a/tests/compiler_serialization_test.py b/tests/compiler_serialization_test.py new file mode 100644 index 0000000..f540d9f --- /dev/null +++ b/tests/compiler_serialization_test.py @@ -0,0 +1,81 @@ +from pathlib import Path + +import pytest + +from diopter.compiler import ( + CompilationSetting, + CompilerExe, + CompilerProject, + Language, + OptLevel, + Source, + SourceFile, + SourceProgram, +) + + +def test_setting_serialization() -> None: + setting0 = CompilationSetting( + compiler=CompilerExe(CompilerProject.GCC, Path("gcc"), "test"), + opt_level=OptLevel.O2, + include_paths=("a", "b"), + system_include_paths=("sa", "sb"), + macro_definitions=("M1",), + ) + setting1 = CompilationSetting( + compiler=CompilerExe(CompilerProject.LLVM, Path("llvm"), "test"), + opt_level=OptLevel.O0, + system_include_paths=("sb",), + ) + assert setting0 == CompilationSetting.from_json_dict(setting0.to_json_dict()) + assert setting1 == CompilationSetting.from_json_dict(setting1.to_json_dict()) + + +def test_sourceprogram_serialization() -> None: + p0 = SourceProgram( + language=Language.C, + defined_macros=("M1", "M2"), + include_paths=("a", "b"), + system_include_paths=("sa", "sb"), + flags=("-fPIC", "-fno-omit-frame-pointer"), + code="bla bla", + ) + + assert p0 == SourceProgram.from_json_dict(p0.to_json_dict()) + assert p0 == Source.from_json_dict(p0.to_json_dict()) + + p1 = SourceProgram( + language=Language.CPP, + defined_macros=("M1",), + system_include_paths=("sa",), + code="bla bla blac", + ) + + assert p1 == SourceProgram.from_json_dict(p1.to_json_dict()) + assert p1 == Source.from_json_dict(p1.to_json_dict()) + + with pytest.raises(AssertionError): + SourceFile.from_json_dict(p1.to_json_dict()) + + +def test_sourcefile_serialization() -> None: + p0 = SourceFile( + language=Language.C, + filename=Path("/bla/bla"), + ) + + assert p0 == SourceFile.from_json_dict(p0.to_json_dict()) + assert p0 == Source.from_json_dict(p0.to_json_dict()) + + p1 = SourceFile( + language=Language.CPP, + defined_macros=("M1",), + system_include_paths=("sa",), + filename=Path("bla/bla/blac"), + ) + + assert p1 == SourceFile.from_json_dict(p1.to_json_dict()) + assert p1 == Source.from_json_dict(p1.to_json_dict()) + + with pytest.raises(AssertionError): + SourceProgram.from_json_dict(p1.to_json_dict()) diff --git a/tests/compiler_test.py b/tests/compiler_test.py index c868106..4114e5b 100644 --- a/tests/compiler_test.py +++ b/tests/compiler_test.py @@ -287,20 +287,3 @@ def test_opt() -> None: opt = get_opt() opt_res = opt.run_on_input(res.output.filename, ("-S",)) assert llvm_ir.strip() != opt_res.stdout.strip() - - -def test_serialization() -> None: - setting0 = CompilationSetting( - compiler=CompilerExe(CompilerProject.GCC, Path("gcc"), "test"), - opt_level=OptLevel.O2, - include_paths=("a", "b"), - system_include_paths=("sa", "sb"), - macro_definitions=("M1",), - ) - setting1 = CompilationSetting( - compiler=CompilerExe(CompilerProject.LLVM, Path("llvm"), "test"), - opt_level=OptLevel.O0, - system_include_paths=("sb",), - ) - assert setting0 == CompilationSetting.from_json_dict(setting0.to_json_dict()) - assert setting1 == CompilationSetting.from_json_dict(setting1.to_json_dict())