Skip to content

Commit db9ad51

Browse files
committed
[compiler] adds json serialization to more types
1 parent 3f159cf commit db9ad51

3 files changed

Lines changed: 311 additions & 21 deletions

File tree

diopter/compiler.py

Lines changed: 230 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,29 @@ def to_suffix(self) -> str:
115115
case Language.CPP:
116116
return ".cpp"
117117

118+
def to_json_dict(self) -> dict[str, Any]:
119+
"""Returns a dictionary that can be serialized to json.
120+
121+
Returns:
122+
dict[str, Any]:
123+
the dictionary
124+
"""
125+
return {"name": self.name}
126+
127+
@staticmethod
128+
def from_json_dict(d: dict[str, Any]) -> Language:
129+
"""Returns a language parsed from a json dictionary.
130+
131+
Args:
132+
d (dict[str, Any]):
133+
the dictionary
134+
135+
Returns:
136+
Language:
137+
the language
138+
"""
139+
return Language[d["name"]]
140+
118141

119142
@dataclass(frozen=True)
120143
class SourcePath:
@@ -131,12 +154,10 @@ def __del__(self) -> None:
131154

132155
@dataclass(frozen=True, kw_only=True)
133156
class Source(ABC):
134-
"""A C or C++ base class for source programs together
157+
"""A base class for C or C++source programs together
135158
with flags, includes and macro definitions.
136159
137160
Attributes:
138-
code (str):
139-
the source code
140161
language (Language):
141162
the program's language
142163
defined_macros (tuple[str,...]):
@@ -198,7 +219,122 @@ def get_file_suffix(self) -> str:
198219

199220
@abstractmethod
200221
def get_filename(self) -> SourcePath:
201-
pass
222+
raise NotImplementedError
223+
224+
def to_json_dict(self) -> dict[str, Any]:
225+
"""Returns a dictionary that can be serialized to json.
226+
227+
Can be re-parses with Source.from_json_dict.
228+
229+
Returns:
230+
dict[str, Any]:
231+
the dictionary
232+
"""
233+
j = {
234+
"language": self.language.to_json_dict(),
235+
"defined_macros": self.defined_macros,
236+
"include_paths": self.include_paths,
237+
"system_include_paths": self.system_include_paths,
238+
"flags": self.flags,
239+
}
240+
241+
j |= self.to_json_dict_impl()
242+
243+
assert set(j.keys()) == set(field.name for field in fields(self)) | set(
244+
("kind",)
245+
)
246+
247+
return j
248+
249+
@abstractmethod
250+
def to_json_dict_impl(self) -> dict[str, Any]:
251+
"""Returns a dictionary that can be serialized to json.
252+
253+
Subclasses should implement this method and serialize
254+
their specific attributes.
255+
256+
257+
Returns:
258+
dict[str, Any]:
259+
the dictionary
260+
"""
261+
raise NotImplementedError
262+
263+
@classmethod
264+
def from_json_dict(cls, d: dict[str, Any]) -> Source:
265+
"""Returns a source parsed from a json dictionary.
266+
267+
Args:
268+
d (dict[str, Any]):
269+
the dictionary
270+
271+
Returns:
272+
Source:
273+
the source
274+
"""
275+
if cls is Source:
276+
match d["kind"]:
277+
case "SourceFile":
278+
return SourceFile.from_json_dict_impl(
279+
d,
280+
language=Language.from_json_dict(d["language"]),
281+
defined_macros=tuple(d["defined_macros"]),
282+
include_paths=tuple(d["include_paths"]),
283+
system_include_paths=tuple(d["system_include_paths"]),
284+
flags=tuple(d["flags"]),
285+
)
286+
case "SourceProgram":
287+
return SourceProgram.from_json_dict_impl(
288+
d,
289+
language=Language.from_json_dict(d["language"]),
290+
defined_macros=tuple(d["defined_macros"]),
291+
include_paths=tuple(d["include_paths"]),
292+
system_include_paths=tuple(d["system_include_paths"]),
293+
flags=tuple(d["flags"]),
294+
)
295+
296+
case _:
297+
raise ValueError(f"Unknown kind: {d['kind']}")
298+
else:
299+
return cls.from_json_dict_impl(
300+
d,
301+
language=Language.from_json_dict(d["language"]),
302+
defined_macros=tuple(d["defined_macros"]),
303+
include_paths=tuple(d["include_paths"]),
304+
system_include_paths=tuple(d["system_include_paths"]),
305+
flags=tuple(d["flags"]),
306+
)
307+
308+
@staticmethod
309+
@abstractmethod
310+
def from_json_dict_impl(
311+
d: dict[str, Any],
312+
language: Language,
313+
defined_macros: tuple[str, ...],
314+
include_paths: tuple[str, ...],
315+
system_include_paths: tuple[str, ...],
316+
flags: tuple[str, ...],
317+
) -> Source:
318+
"""Returns a source parsed from a json dictionary.
319+
320+
Subclasses should implement this and parse their
321+
specific attributes from the dictionary.
322+
323+
Args:
324+
d (dict[str, Any]):
325+
the dictionary
326+
language (Language):
327+
the program's language
328+
defined_macros (tuple[str,...]):
329+
macros that will be defined when compiling this program
330+
include_paths (tuple[str,...]):
331+
include paths which will be passed to the compiler (with -I)
332+
system_include_paths (tuple[str,...]):
333+
system include paths which will be passed to the compiler (with -isystem)
334+
flags (tuple[str,...]):
335+
flags, prefixed with a dash ("-") that will be passed to the compiler
336+
"""
337+
raise NotImplementedError
202338

203339

204340
ProgramType = TypeVar("ProgramType", bound="SourceProgram")
@@ -274,6 +410,51 @@ def with_code(self: ProgramType, new_code: str) -> ProgramType:
274410
"""
275411
return replace(self, code=new_code)
276412

413+
@staticmethod
414+
def from_json_dict_impl(
415+
d: dict[str, Any],
416+
language: Language,
417+
defined_macros: tuple[str, ...],
418+
include_paths: tuple[str, ...],
419+
system_include_paths: tuple[str, ...],
420+
flags: tuple[str, ...],
421+
) -> SourceProgram:
422+
"""Returns a source program parsed from a json dictionary.
423+
424+
Args:
425+
d (dict[str, Any]):
426+
the dictionary
427+
language (Language):
428+
the program's language
429+
defined_macros (tuple[str,...]):
430+
macros that will be defined when compiling this program
431+
include_paths (tuple[str,...]):
432+
include paths which will be passed to the compiler (with -I)
433+
system_include_paths (tuple[str,...]):
434+
system include paths which will be passed to the compiler (with -isystem)
435+
flags (tuple[str,...]):
436+
flags, prefixed with a dash ("-") that will be passed to the compiler
437+
"""
438+
assert d["kind"] == "SourceProgram"
439+
return SourceProgram(
440+
code=d["code"],
441+
language=language,
442+
defined_macros=defined_macros,
443+
include_paths=include_paths,
444+
system_include_paths=system_include_paths,
445+
flags=flags,
446+
)
447+
448+
def to_json_dict_impl(self) -> dict[str, Any]:
449+
"""Returns a dictionary representation of this source program,
450+
only including the SourceProgram specific attributes.
451+
452+
Returns:
453+
dict[str, Any]:
454+
the dictionary
455+
"""
456+
return {"kind": "SourceProgram", "code": self.code}
457+
277458

278459
@dataclass(frozen=True, kw_only=True)
279460
class SourceFile(Source):
@@ -304,6 +485,51 @@ def __post_init__(self) -> None:
304485
def get_filename(self) -> SourcePath:
305486
return SourcePath(self.filename, None)
306487

488+
@staticmethod
489+
def from_json_dict_impl(
490+
d: dict[str, Any],
491+
language: Language,
492+
defined_macros: tuple[str, ...],
493+
include_paths: tuple[str, ...],
494+
system_include_paths: tuple[str, ...],
495+
flags: tuple[str, ...],
496+
) -> SourceFile:
497+
"""Returns a source file parsed from a json dictionary.
498+
499+
Args:
500+
d (dict[str, Any]):
501+
the dictionary
502+
language (Language):
503+
the program's language
504+
defined_macros (tuple[str,...]):
505+
macros that will be defined when compiling this program
506+
include_paths (tuple[str,...]):
507+
include paths which will be passed to the compiler (with -I)
508+
system_include_paths (tuple[str,...]):
509+
system include paths which will be passed to the compiler (with -isystem)
510+
flags (tuple[str,...]):
511+
flags, prefixed with a dash ("-") that will be passed to the compiler
512+
"""
513+
assert d["kind"] == "SourceFile"
514+
return SourceFile(
515+
filename=Path(d["filename"]),
516+
language=language,
517+
defined_macros=defined_macros,
518+
include_paths=include_paths,
519+
system_include_paths=system_include_paths,
520+
flags=flags,
521+
)
522+
523+
def to_json_dict_impl(self) -> dict[str, Any]:
524+
"""Returns a dictionary representation of this source program,
525+
only including the SourceFile specific attributes.
526+
527+
Returns:
528+
dict[str, Any]:
529+
the dictionary
530+
"""
531+
return {"kind": "SourceFile", "filename": self.filename}
532+
307533

308534
Revision = str
309535

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
5+
from diopter.compiler import (
6+
CompilationSetting,
7+
CompilerExe,
8+
CompilerProject,
9+
Language,
10+
OptLevel,
11+
Source,
12+
SourceFile,
13+
SourceProgram,
14+
)
15+
16+
17+
def test_setting_serialization() -> None:
18+
setting0 = CompilationSetting(
19+
compiler=CompilerExe(CompilerProject.GCC, Path("gcc"), "test"),
20+
opt_level=OptLevel.O2,
21+
include_paths=("a", "b"),
22+
system_include_paths=("sa", "sb"),
23+
macro_definitions=("M1",),
24+
)
25+
setting1 = CompilationSetting(
26+
compiler=CompilerExe(CompilerProject.LLVM, Path("llvm"), "test"),
27+
opt_level=OptLevel.O0,
28+
system_include_paths=("sb",),
29+
)
30+
assert setting0 == CompilationSetting.from_json_dict(setting0.to_json_dict())
31+
assert setting1 == CompilationSetting.from_json_dict(setting1.to_json_dict())
32+
33+
34+
def test_sourceprogram_serialization() -> None:
35+
p0 = SourceProgram(
36+
language=Language.C,
37+
defined_macros=("M1", "M2"),
38+
include_paths=("a", "b"),
39+
system_include_paths=("sa", "sb"),
40+
flags=("-fPIC", "-fno-omit-frame-pointer"),
41+
code="bla bla",
42+
)
43+
44+
assert p0 == SourceProgram.from_json_dict(p0.to_json_dict())
45+
assert p0 == Source.from_json_dict(p0.to_json_dict())
46+
47+
p1 = SourceProgram(
48+
language=Language.CPP,
49+
defined_macros=("M1",),
50+
system_include_paths=("sa",),
51+
code="bla bla blac",
52+
)
53+
54+
assert p1 == SourceProgram.from_json_dict(p1.to_json_dict())
55+
assert p1 == Source.from_json_dict(p1.to_json_dict())
56+
57+
with pytest.raises(AssertionError):
58+
SourceFile.from_json_dict(p1.to_json_dict())
59+
60+
61+
def test_sourcefile_serialization() -> None:
62+
p0 = SourceFile(
63+
language=Language.C,
64+
filename=Path("/bla/bla"),
65+
)
66+
67+
assert p0 == SourceFile.from_json_dict(p0.to_json_dict())
68+
assert p0 == Source.from_json_dict(p0.to_json_dict())
69+
70+
p1 = SourceFile(
71+
language=Language.CPP,
72+
defined_macros=("M1",),
73+
system_include_paths=("sa",),
74+
filename=Path("bla/bla/blac"),
75+
)
76+
77+
assert p1 == SourceFile.from_json_dict(p1.to_json_dict())
78+
assert p1 == Source.from_json_dict(p1.to_json_dict())
79+
80+
with pytest.raises(AssertionError):
81+
SourceProgram.from_json_dict(p1.to_json_dict())

tests/compiler_test.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -287,20 +287,3 @@ def test_opt() -> None:
287287
opt = get_opt()
288288
opt_res = opt.run_on_input(res.output.filename, ("-S",))
289289
assert llvm_ir.strip() != opt_res.stdout.strip()
290-
291-
292-
def test_serialization() -> None:
293-
setting0 = CompilationSetting(
294-
compiler=CompilerExe(CompilerProject.GCC, Path("gcc"), "test"),
295-
opt_level=OptLevel.O2,
296-
include_paths=("a", "b"),
297-
system_include_paths=("sa", "sb"),
298-
macro_definitions=("M1",),
299-
)
300-
setting1 = CompilationSetting(
301-
compiler=CompilerExe(CompilerProject.LLVM, Path("llvm"), "test"),
302-
opt_level=OptLevel.O0,
303-
system_include_paths=("sb",),
304-
)
305-
assert setting0 == CompilationSetting.from_json_dict(setting0.to_json_dict())
306-
assert setting1 == CompilationSetting.from_json_dict(setting1.to_json_dict())

0 commit comments

Comments
 (0)