Skip to content

Commit 6356268

Browse files
authored
Merge pull request #744 from SGSSGene/patch/improved_template_export
Improved c++ code generation when template are involved
2 parents f098da4 + 60818b8 commit 6356268

File tree

1 file changed

+104
-14
lines changed

1 file changed

+104
-14
lines changed

schema_salad/cpp_codegen.py

Lines changed: 104 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,39 +58,55 @@ def safename2(name: Dict[str, str]) -> str:
5858
return safename(name["namespace"]) + "::" + safename(name["classname"])
5959

6060

61-
# Splits names like https://xyz.xyz/blub#cwl/class
62-
# into its class path and non class path
6361
def split_name(s: str) -> Tuple[str, str]:
62+
"""Split url name into its components.
63+
64+
Splits names like https://xyz.xyz/blub#cwl/class
65+
into its class path and non class path
66+
"""
6467
t = s.split("#")
6568
if len(t) != 2:
6669
raise ValueError("Expected field to be formatted as 'https://xyz.xyz/blub#cwl/class'.")
6770
return (t[0], t[1])
6871

6972

70-
# similar to split_name but for field names
7173
def split_field(s: str) -> Tuple[str, str, str]:
74+
"""Split field into its components.
75+
76+
similar to split_name but for field names
77+
"""
7278
(namespace, field) = split_name(s)
7379
t = field.split("/")
7480
if len(t) != 2:
7581
raise ValueError("Expected field to be formatted as 'https://xyz.xyz/blub#cwl/class'.")
7682
return (namespace, t[0], t[1])
7783

7884

79-
# Prototype of a class
8085
class ClassDefinition:
86+
"""Prototype of a class."""
87+
8188
def __init__(self, name: str):
89+
"""Initialize the class definition with a name."""
8290
self.fullName = name
8391
self.extends: List[Dict[str, str]] = []
92+
93+
# List of types from parent classes that have been specialized
94+
self.specializationTypes: List[str] = []
95+
96+
# this includes fields that are also inheritant
97+
self.allfields: List[FieldDefinition] = []
8498
self.fields: List[FieldDefinition] = []
8599
self.abstract = False
86100
(self.namespace, self.classname) = split_name(name)
87101
self.namespace = safename(self.namespace)
88102
self.classname = safename(self.classname)
89103

90104
def writeFwdDeclaration(self, target: IO[str], fullInd: str, ind: str) -> None:
105+
"""Write forward declaration."""
91106
target.write(f"{fullInd}namespace {self.namespace} {{ struct {self.classname}; }}\n")
92107

93108
def writeDefinition(self, target: IO[Any], fullInd: str, ind: str) -> None:
109+
"""Write definition of the class."""
94110
target.write(f"{fullInd}namespace {self.namespace} {{\n")
95111
target.write(f"{fullInd}struct {self.classname}")
96112
extends = list(map(safename2, self.extends))
@@ -113,6 +129,7 @@ def writeDefinition(self, target: IO[Any], fullInd: str, ind: str) -> None:
113129
target.write(f"{fullInd}}}\n\n")
114130

115131
def writeImplDefinition(self, target: IO[str], fullInd: str, ind: str) -> None:
132+
"""Write definition with implementation."""
116133
extends = list(map(safename2, self.extends))
117134

118135
if self.abstract:
@@ -131,20 +148,33 @@ def writeImplDefinition(self, target: IO[str], fullInd: str, ind: str) -> None:
131148

132149
for field in self.fields:
133150
fieldname = safename(field.name)
134-
target.write(
135-
f'{fullInd}{ind}addYamlField(n, "{field.name}", toYaml(*{fieldname}));\n' # noqa: B907
136-
)
151+
if field.remap != "":
152+
target.write(
153+
f"""{fullInd}{ind}addYamlField(n, "{field.name}",
154+
convertListToMap(toYaml(*{fieldname}), "{field.remap}"));\n""" # noqa: B907
155+
)
156+
else:
157+
target.write(
158+
f'{fullInd}{ind}addYamlField(n, "{field.name}", toYaml(*{fieldname}));\n' # noqa: B907
159+
)
137160
# target.write(f"{fullInd}{ind}addYamlIfNotEmpty(n, \"{field.name}\", toYaml(*{fieldname}));\n")
138161

139162
target.write(f"{fullInd}{ind}return n;\n{fullInd}}}\n")
140163

141164

142-
# Prototype of a single field of a class
143165
class FieldDefinition:
144-
def __init__(self, name: str, typeStr: str, optional: bool):
166+
"""Prototype of a single field from a class definition."""
167+
168+
def __init__(self, name: str, typeStr: str, optional: bool, remap: str):
169+
"""Initialize field definition.
170+
171+
Creates a new field with name, its type, optional and which field to use to convert
172+
from list to map (or empty if it is not possible)
173+
"""
145174
self.name = name
146175
self.typeStr = typeStr
147176
self.optional = optional
177+
self.remap = remap
148178

149179
def writeDefinition(self, target: IO[Any], fullInd: str, ind: str, namespace: str) -> None:
150180
"""Write a C++ definition for the class field."""
@@ -153,13 +183,16 @@ def writeDefinition(self, target: IO[Any], fullInd: str, ind: str, namespace: st
153183
target.write(f"{fullInd}heap_object<{typeStr}> {name};\n")
154184

155185

156-
# Prototype of an enum definition
157186
class EnumDefinition:
187+
"""Prototype of a enum."""
188+
158189
def __init__(self, name: str, values: List[str]):
190+
"""Initialize enum definition with a name and possible values."""
159191
self.name = name
160192
self.values = values
161193

162194
def writeDefinition(self, target: IO[str], ind: str) -> None:
195+
"""Write enum definition to output."""
163196
namespace = ""
164197
if len(self.name.split("#")) == 2:
165198
(namespace, classname) = split_name(self.name)
@@ -201,12 +234,14 @@ def writeDefinition(self, target: IO[str], ind: str) -> None:
201234

202235
# !TODO way tot many functions, most of these shouldn't exists
203236
def isPrimitiveType(v: Any) -> bool:
237+
"""Check if v is a primitve type."""
204238
if not isinstance(v, str):
205239
return False
206240
return v in ["null", "boolean", "int", "long", "float", "double", "string"]
207241

208242

209243
def hasFieldValue(e: Any, f: str, v: Any) -> bool:
244+
"""Check if e has a field f value."""
210245
if not isinstance(e, dict):
211246
return False
212247
if f not in e:
@@ -215,10 +250,12 @@ def hasFieldValue(e: Any, f: str, v: Any) -> bool:
215250

216251

217252
def isRecordSchema(v: Any) -> bool:
253+
"""Check if v is of type record schema."""
218254
return hasFieldValue(v, "type", "record")
219255

220256

221257
def isEnumSchema(v: Any) -> bool:
258+
"""Check if v is of type enum schema."""
222259
if not hasFieldValue(v, "type", "enum"):
223260
return False
224261
if "symbols" not in v:
@@ -229,6 +266,7 @@ def isEnumSchema(v: Any) -> bool:
229266

230267

231268
def isArray(v: Any) -> bool:
269+
"""Check if v is of type array."""
232270
if not isinstance(v, list):
233271
return False
234272
for i in v:
@@ -238,6 +276,7 @@ def isArray(v: Any) -> bool:
238276

239277

240278
def pred(i: Any) -> bool:
279+
"""Check if v is any of the simple types."""
241280
return (
242281
isPrimitiveType(i)
243282
or isRecordSchema(i)
@@ -248,6 +287,7 @@ def pred(i: Any) -> bool:
248287

249288

250289
def isArraySchema(v: Any) -> bool:
290+
"""Check if v is of type array schema."""
251291
if not hasFieldValue(v, "type", "array"):
252292
return False
253293
if "items" not in v:
@@ -272,6 +312,7 @@ def __init__(
272312
package: str,
273313
copyright: Optional[str],
274314
) -> None:
315+
"""Initialize the C++ code generator."""
275316
super().__init__()
276317
self.base_uri = base
277318
self.target = target
@@ -376,8 +417,8 @@ def convertTypeToCpp(self, type_declaration: Union[List[Any], Dict[str, Any], st
376417
type_declaration = ", ".join(type_declaration)
377418
return f"std::variant<{type_declaration}>"
378419

379-
# start of our generated file
380420
def epilogue(self, root_loader: Optional[TypeDef]) -> None:
421+
"""Generate final part of our cpp file."""
381422
self.target.write(
382423
"""#pragma once
383424
@@ -428,12 +469,23 @@ def epilogue(self, root_loader: Optional[TypeDef]) -> None:
428469
return YAML::Node{v};
429470
}
430471
431-
inline void addYamlField(YAML::Node node, std::string const& key, YAML::Node value) {
472+
inline void addYamlField(YAML::Node& node, std::string const& key, YAML::Node value) {
432473
if (value.IsDefined()) {
433474
node[key] = value;
434475
}
435476
}
436477
478+
inline auto convertListToMap(YAML::Node list, std::string const& key_name) {
479+
if (list.size() == 0) return list;
480+
auto map = YAML::Node{};
481+
for (YAML::Node n : list) {
482+
auto key = n[key_name].as<std::string>();
483+
n.remove(key_name);
484+
map[key] = n;
485+
}
486+
return map;
487+
}
488+
437489
// fwd declaring toYaml
438490
template <typename T>
439491
auto toYaml(std::vector<T> const& v) -> YAML::Node;
@@ -505,6 +557,27 @@ class heap_object {
505557
for key in self.classDefinitions:
506558
self.classDefinitions[key].writeFwdDeclaration(self.target, "", " ")
507559

560+
# remove parent classes, that are specialized/templated versions
561+
for key in self.classDefinitions:
562+
if len(self.classDefinitions[key].specializationTypes) > 0:
563+
self.classDefinitions[key].extends = []
564+
565+
# remove fields that are available in a parent class
566+
for key in self.classDefinitions:
567+
for field in self.classDefinitions[key].allfields:
568+
found = False
569+
for parent_key in self.classDefinitions[key].extends:
570+
fullKey = parent_key["namespace"] + "#" + parent_key["classname"]
571+
for f in self.classDefinitions[fullKey].allfields:
572+
if f.name == field.name:
573+
found = True
574+
break
575+
if found:
576+
break
577+
578+
if not found:
579+
self.classDefinitions[key].fields.append(field)
580+
508581
for key in self.enumDefinitions:
509582
self.enumDefinitions[key].writeDefinition(self.target, " ")
510583
for key in self.classDefinitions:
@@ -542,7 +615,13 @@ class heap_object {
542615
)
543616

544617
def parseRecordField(self, field: Dict[str, Any]) -> FieldDefinition:
618+
"""Parse a record field."""
545619
(namespace, classname, fieldname) = split_field(field["name"])
620+
remap = ""
621+
if "jsonldPredicate" in field:
622+
if "mapSubject" in field["jsonldPredicate"]:
623+
remap = field["jsonldPredicate"]["mapSubject"]
624+
546625
if isinstance(field["type"], dict):
547626
if field["type"]["type"] == "enum":
548627
fieldtype = "Enum"
@@ -553,9 +632,10 @@ def parseRecordField(self, field: Dict[str, Any]) -> FieldDefinition:
553632
fieldtype = field["type"]
554633
fieldtype = self.convertTypeToCpp(fieldtype)
555634

556-
return FieldDefinition(name=fieldname, typeStr=fieldtype, optional=False)
635+
return FieldDefinition(name=fieldname, typeStr=fieldtype, optional=False, remap=remap)
557636

558637
def parseRecordSchema(self, stype: Dict[str, Any]) -> None:
638+
"""Parse a record schema."""
559639
cd = ClassDefinition(name=stype["name"])
560640
cd.abstract = stype.get("abstract", False)
561641

@@ -565,13 +645,18 @@ def parseRecordSchema(self, stype: Dict[str, Any]) -> None:
565645
ext = {"namespace": base_namespace, "classname": base_classname}
566646
cd.extends.append(ext)
567647

648+
if "specialize" in stype:
649+
for e in aslist(stype["specialize"]):
650+
cd.specializationTypes.append(e["specializeFrom"])
651+
568652
if "fields" in stype:
569653
for field in stype["fields"]:
570-
cd.fields.append(self.parseRecordField(field))
654+
cd.allfields.append(self.parseRecordField(field))
571655

572656
self.classDefinitions[stype["name"]] = cd
573657

574658
def parseEnum(self, stype: Dict[str, Any]) -> str:
659+
"""Parse a schema salad enum."""
575660
name = cast(str, stype["name"])
576661
if name not in self.enumDefinitions:
577662
self.enumDefinitions[name] = EnumDefinition(
@@ -580,6 +665,11 @@ def parseEnum(self, stype: Dict[str, Any]) -> str:
580665
return name
581666

582667
def parse(self, items: List[Dict[str, Any]]) -> None:
668+
"""Parse sechema salad items.
669+
670+
This function is being called from the outside and drives
671+
the whole code generation.
672+
"""
583673
for stype in items:
584674
if "type" in stype and stype["type"] == "documentation":
585675
continue

0 commit comments

Comments
 (0)