Skip to content

Commit b4d96cf

Browse files
committed
added cpp serializer
1 parent 2531d61 commit b4d96cf

File tree

7 files changed

+263
-61
lines changed

7 files changed

+263
-61
lines changed

python/rlc/renderer/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
1-
21
from rlc.renderer.factory import RendererFactory
2+
from .array_renderer import ArrayRenderer
3+
from .bint_renderer import BoundedIntRenderer
4+
from .vector_renderer import VectorRenderer
5+
from .struct_renderer import ContainerRenderer
6+
from .primitive_renderer import PrimitiveRenderer
7+
from .vector_renderer import VectorRenderer
8+
from .renderable import Renderable
9+
from .cpp_serializer import SerializationContext

python/rlc/renderer/bounded_vector_renderer.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ def build_layout(self, obj, direction=Direction.COLUMN,
2020
def update(self, layout, obj, elapsed_time=0.0):
2121
value = getattr(obj, "_data")
2222
self.vector_renderer.update(layout, value, elapsed_time)
23+
24+
def _iter_children(self) :
25+
return [self.vector_renderer]
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from functools import singledispatchmethod
2+
from .array_renderer import ArrayRenderer
3+
from .bint_renderer import BoundedIntRenderer
4+
from .vector_renderer import VectorRenderer
5+
from .struct_renderer import ContainerRenderer
6+
from .primitive_renderer import PrimitiveRenderer
7+
from .vector_renderer import VectorRenderer
8+
from .renderable import Renderable
9+
from dataclasses import dataclass
10+
from typing import TextIO
11+
12+
class Indenter:
13+
def __init__(self, ctx: 'SerializationContext'):
14+
self.ctx = ctx
15+
16+
def __enter__(self):
17+
self.ctx.current_indent = self.ctx.current_indent + 1
18+
19+
def __exit__(self, *args):
20+
self.ctx.current_indent = self.ctx.current_indent - 1
21+
22+
@dataclass
23+
class SerializationContext:
24+
outstream: TextIO
25+
current_indent: int = 0
26+
start_of_line: bool = False
27+
28+
def write(self, string):
29+
if self.start_of_line:
30+
self._do_indent()
31+
self.start_of_line = False
32+
self.outstream.write(string)
33+
34+
def writenl(self, string):
35+
self.outstream.write(string)
36+
self.endline()
37+
38+
def endline(self):
39+
self.outstream.write("\n")
40+
self.start_of_line = True
41+
42+
def _do_indent(self):
43+
for i in range(self.current_indent):
44+
self.outstream.write(" ")
45+
46+
def indent(self):
47+
return Indenter(self)
48+
49+
def write_type_unique_id(self, renderer_type: Renderable):
50+
if hasattr(renderer_type, "name"):
51+
if renderer_type.name != "":
52+
self.write(renderer_type.name + "Renderer")
53+
else:
54+
self.write("anon_" + str(id(renderer_type)))
55+
else:
56+
self.write(type(renderer_type).__name__)
57+
58+
59+
@singledispatchmethod
60+
def serialize_declaration(self, renderer_type: Renderable):
61+
self.write(str(renderer_type))
62+
raise NotImplementedError()
63+
64+
@serialize_declaration.register
65+
def _(self, renderer_type: ArrayRenderer):
66+
pass
67+
68+
@serialize_declaration.register
69+
def _(self, renderer_type: BoundedIntRenderer):
70+
pass
71+
72+
@serialize_declaration.register
73+
def _(self, renderer_type: PrimitiveRenderer):
74+
pass
75+
76+
@serialize_declaration.register
77+
def _(self, renderer_type: VectorRenderer):
78+
pass
79+
80+
@serialize_declaration.register
81+
def _(self, renderer_type: ContainerRenderer):
82+
self.write("class ")
83+
self.write_type_unique_id(renderer_type)
84+
self.writenl(" {")
85+
with self.indent() as indenter:
86+
for field_name, renderer in renderer_type.field_renderers.items():
87+
self.serialize_use(renderer)
88+
self.write(" ")
89+
self.write(field_name)
90+
self.write(";")
91+
self.endline()
92+
self.writenl("};");
93+
94+
@singledispatchmethod
95+
def serialize_use(self, renderer_type: Renderable):
96+
self.write(str(renderer_type))
97+
raise NotImplementedError()
98+
99+
@serialize_use.register
100+
def _(self, renderer_type: ArrayRenderer):
101+
self.write("ArrayRenderer<")
102+
self.serialize_use(renderer_type.element_renderer)
103+
self.write(", ")
104+
self.write(str(renderer_type.length))
105+
self.write(">")
106+
107+
@serialize_use.register
108+
def _(self, renderer_type: BoundedIntRenderer):
109+
self.write("BoundedIntRenderer")
110+
111+
@serialize_use.register
112+
def _(self, renderer_type: PrimitiveRenderer):
113+
self.write("PrimitiveRenderer")
114+
115+
@serialize_use.register
116+
def _(self, renderer_type: VectorRenderer):
117+
self.write("VectorRenderer<")
118+
self.serialize_use(renderer_type.element_renderer)
119+
self.write(">")
120+
121+
@serialize_use.register
122+
def _(self, renderer_type: ContainerRenderer):
123+
self.write_type_unique_id(renderer_type)

python/rlc/renderer/renderable.py

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,74 @@
11

22
from abc import ABC, abstractmethod
3+
from copy import deepcopy
34
from rlc.layout import Layout, Direction, FIT, Padding
45
from typing import Dict
5-
from dataclasses import dataclass, field
6+
from dataclasses import dataclass, field, fields, is_dataclass, MISSING
67
from rlc.text import Text
8+
import yaml
79

810
_renderer_registry = {} # maps class name → class
911

12+
class RenderableDumper(yaml.SafeDumper):
13+
index = 0
14+
def generate_anchor(self, node: yaml.Node):
15+
16+
self.index = self.index + 1
17+
return str(self.index)
18+
19+
class RenderableLoader(yaml.FullLoader):
20+
pass
21+
22+
def renderable_representer(dumper: RenderableDumper, obj: 'Renderable'):
23+
tag = obj.yaml_tag()
24+
mapping = []
25+
for f in fields(obj):
26+
value = getattr(obj, f.name)
27+
28+
if f.default is not MISSING and value == f.default:
29+
continue
30+
31+
if f.default_factory is not MISSING:
32+
try:
33+
default = f.default_factory()
34+
if value == default:
35+
continue
36+
except TypeError:
37+
pass
38+
39+
mapping.append((f.name, value))
40+
return dumper.represent_mapping(tag, mapping)
41+
42+
43+
def renderable_multi_constructor(loader: RenderableLoader, tag_suffix: str, node):
44+
"""
45+
tag_suffix is the part after the '!' when using add_multi_constructor("!", ...)
46+
e.g. YAML tag `!FooRenderer` → tag_suffix == "FooRenderer"
47+
"""
48+
cls = _renderer_registry[tag_suffix] # look up the class
49+
data = loader.construct_mapping(node, deep=True)
50+
return cls(**data)
51+
52+
yaml.add_multi_constructor("!", renderable_multi_constructor, Loader=RenderableLoader)
53+
1054
def register_renderer(cls):
1155
_renderer_registry[cls.__name__] = cls
1256
return cls
1357

14-
@dataclass(kw_only=True)
58+
@dataclass
1559
class Renderable(ABC):
1660
"""
1761
Base abstract renderer type.
1862
Each subclasss knows how to convert its types object into a Layout tree.
1963
"""
20-
rlc_type_name: str
21-
style_policy: Dict = field(default_factory=dict)
2264

2365
def make_layout(self, direction=Direction.COLUMN, color="white", sizing=(FIT(), FIT()), logger=None, padding=Padding(2,2,2,2), border=3, child_gap=5) -> Layout:
2466
layout = Layout(sizing=sizing, direction=direction, color=color, padding=padding, border=border, child_gap=child_gap)
2567

26-
for attr, value in self.style_policy.items():
27-
if value is not None and hasattr(layout, attr):
28-
setattr(layout, attr, value)
29-
3068
return layout
3169

3270
def make_text(self, txt, font_name, font_size, color) -> Text:
3371
text = Text(txt, font_name, font_size, color)
34-
35-
for attr, value in self.style_policy.items():
36-
if value is not None and hasattr(text, attr):
37-
setattr(text, attr, value)
38-
3972
return text
4073

4174
@abstractmethod
@@ -59,7 +92,6 @@ def __call__(self, obj, parent_binding=None, **kwds):
5992
# If build_layout didn't set binding, ensure we add it
6093
if layout.binding is None:
6194
layout.binding = {
62-
"type": self.rlc_type_name,
6395
"parent": parent_binding,
6496
}
6597
else:
@@ -78,7 +110,34 @@ def __call__(self, obj, parent_binding=None, **kwds):
78110

79111
return layout
80112

113+
def post_order_types(self):
114+
frontier = [self]
115+
seen = set()
116+
output = []
117+
while len(frontier) != 0:
118+
current = frontier.pop(0)
119+
if id(current) in seen:
120+
continue
121+
output.append(current)
122+
seen.add(id(current))
123+
for child in current._iter_children():
124+
frontier.append(child)
125+
return [x for x in reversed(output)]
126+
127+
128+
def to_yaml(self):
129+
return yaml.dump(self.post_order_types(), Dumper=RenderableDumper, sort_keys=False)
130+
131+
@classmethod
132+
def from_yaml(cls, yaml_text):
133+
return yaml.load(yaml_text, Loader=RenderableLoader)[-1]
134+
135+
@classmethod
136+
def yaml_tag(cls) -> str:
137+
return f"!{cls.__name__}"
81138

82139
def _iter_children(self):
83140
"""Return iterable of child renderers, if any. Override per subclass."""
84141
return []
142+
143+
yaml.add_multi_representer(Renderable, renderable_representer, Dumper=RenderableDumper)

python/rlc/renderer/struct_renderer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@register_renderer
88
@dataclass
99
class ContainerRenderer(Renderable):
10+
name: str
1011
field_renderers: List[Renderable]
1112

1213
def build_layout(self, obj, direction=Direction.COLUMN, color="white", sizing=(FIT(), FIT()), logger=None, padding=Padding(7,7,7,7)):

python/rlc/renderer_type_conversion.py

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,56 +9,58 @@
99
from rlc.renderer.bint_renderer import BoundedIntRenderer
1010

1111

12-
_renderer_cache = {}
1312
def create_renderer(rlc_type, config : Dict[type, type]):
14-
if rlc_type in _renderer_cache:
15-
return _renderer_cache[rlc_type]
13+
_renderer_cache = {}
14+
def _create_renderer(rlc_type, config : Dict[type, type]):
15+
if rlc_type in _renderer_cache:
16+
return _renderer_cache[rlc_type]
1617

17-
name = getattr(rlc_type, "__name__", str(rlc_type))
18+
name = getattr(rlc_type, "__name__", str(rlc_type))
1819

19-
if rlc_type in config:
20-
pytyp = config.get(rlc_type)
21-
if hasattr(rlc_type, "_fields_"):
22-
field_renderer = {
23-
name: create_renderer(field_type, config) for name, field_type in rlc_type._fields_
24-
}
25-
renderer = pytyp(rlc_type, field_renderer)
26-
return renderer
20+
if rlc_type in config:
21+
pytyp = config.get(rlc_type)
22+
if hasattr(rlc_type, "_fields_"):
23+
field_renderer = {
24+
name: _create_renderer(field_type, config) for name, field_type in rlc_type._fields_
25+
}
26+
renderer = pytyp(rlc_type, field_renderer)
27+
return renderer
2728

28-
# Vector
29-
elif "Vector" in name:
30-
# find the _data field to determine element type
31-
data_field = next((f for f in getattr(rlc_type, "_fields_", []) if f[0] == "_data"), None)
32-
if data_field:
33-
element_type = getattr(data_field[1], "_type_", None)
34-
if element_type is None:
35-
element_type = data_field[1]
36-
element_renderer = create_renderer(element_type, config)
37-
renderer = VectorRenderer(element_renderer, rlc_type_name=name)
38-
else:
39-
renderer = PrimitiveRenderer(rlc_type_name=name)
29+
# Vector
30+
elif "Vector" in name:
31+
# find the _data field to determine element type
32+
data_field = next((f for f in getattr(rlc_type, "_fields_", []) if f[0] == "_data"), None)
33+
if data_field:
34+
element_type = getattr(data_field[1], "_type_", None)
35+
if element_type is None:
36+
element_type = data_field[1]
37+
element_renderer = _create_renderer(element_type, config)
38+
renderer = VectorRenderer(element_renderer)
39+
else:
40+
renderer = PrimitiveRenderer()
4041

4142

42-
# Array
43-
elif hasattr(rlc_type, "_length_") and hasattr(rlc_type, "_type_"):
44-
element_rendere = create_renderer(rlc_type._type_, config)
45-
renderer = ArrayRenderer(rlc_type._length_, element_rendere, rlc_type_name=name)
43+
# Array
44+
elif hasattr(rlc_type, "_length_") and hasattr(rlc_type, "_type_"):
45+
element_rendere = _create_renderer(rlc_type._type_, config)
46+
renderer = ArrayRenderer(rlc_type._length_, element_rendere)
4647

47-
# Primitive
48-
elif rlc_type == c_bool or rlc_type == c_long:
49-
renderer = PrimitiveRenderer(rlc_type_name=name)
48+
# Primitive
49+
elif rlc_type == c_bool or rlc_type == c_long:
50+
renderer = PrimitiveRenderer()
5051

51-
# Bounded integer
52-
elif name.startswith("BInt"):
53-
renderer = BoundedIntRenderer(rlc_type_name=name)
52+
# Bounded integer
53+
elif name.startswith("BInt"):
54+
renderer = BoundedIntRenderer()
5455

55-
# Struct
56-
elif hasattr(rlc_type, "_fields_"):
57-
field_renderer = {
58-
name: create_renderer(field_type, config) for name, field_type in rlc_type._fields_
59-
}
60-
renderer = ContainerRenderer(field_renderer, rlc_type_name=name)
61-
else:
62-
renderer = PrimitiveRenderer(name)
63-
_renderer_cache[rlc_type] = renderer
64-
return renderer
56+
# Struct
57+
elif hasattr(rlc_type, "_fields_"):
58+
field_renderer = {
59+
name: _create_renderer(field_type, config) for name, field_type in rlc_type._fields_
60+
}
61+
renderer = ContainerRenderer(name, field_renderer)
62+
else:
63+
renderer = PrimitiveRenderer(name)
64+
_renderer_cache[rlc_type] = renderer
65+
return renderer
66+
return _create_renderer(rlc_type, config)

0 commit comments

Comments
 (0)