Skip to content

Commit

Permalink
Add generation of string classes for cpp (#42)
Browse files Browse the repository at this point in the history
- Add generation of string classes (including Date, DateTime, MonthDay)
- Fix create_class_assign functions to prevent duplicate entries in
attributes with attribute_type list
- Simplify and unify templates
- Add BaseClass and fixed implementation for primitive classes (copied
from libcimpp)
- Unify BaseClass, Integer, Boolean with generated classes
  • Loading branch information
m-mirz authored Nov 23, 2024
2 parents 13af877 + 8507839 commit 9af3921
Show file tree
Hide file tree
Showing 18 changed files with 723 additions and 192 deletions.
197 changes: 120 additions & 77 deletions cimgen/languages/cpp/lang_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ def setup(output_path: str, cgmes_profile_details: list, cim_namespace: str): #
{"filename": "cpp_enum_header_template.mustache", "ext": ".hpp"},
{"filename": "cpp_enum_object_template.mustache", "ext": ".cpp"},
]
string_template_files = [
{"filename": "cpp_string_header_template.mustache", "ext": ".hpp"},
{"filename": "cpp_string_object_template.mustache", "ext": ".cpp"},
]


def get_class_location(class_name, class_map, version): # NOSONAR
Expand All @@ -60,10 +64,12 @@ def run_template(output_path, class_details):
templates = float_template_files
elif class_details["is_an_enum_class"]:
templates = enum_template_files
elif class_details["is_a_primitive_class"]:
templates = string_template_files
else:
templates = template_files

if class_details["class_name"] in ("String", "Integer", "Boolean", "Date"):
if class_details["class_name"] in ("Integer", "Boolean"):
# These classes are defined already
# We have to implement operators for them
return
Expand All @@ -75,7 +81,6 @@ def run_template(output_path, class_details):

def _write_templated_file(class_file, class_details, template_filename):
with open(class_file, "w", encoding="utf-8") as file:
class_details["setDefault"] = _set_default
templates = files("cimgen.languages.cpp.templates")
with templates.joinpath(template_filename).open(encoding="utf-8") as f:
args = {
Expand Down Expand Up @@ -109,7 +114,7 @@ def insert_assign_fn(text, render):
label = attribute_json["label"]
class_name = attribute_json["domain"]
return (
'assign_map.insert(std::make_pair(std::string("cim:'
' assign_map.insert(std::make_pair(std::string("cim:'
+ class_name
+ "."
+ label
Expand All @@ -129,7 +134,7 @@ def insert_class_assign_fn(text, render):
label = attribute_json["label"]
class_name = attribute_json["domain"]
return (
'assign_map.insert(std::make_pair(std::string("cim:'
' assign_map.insert(std::make_pair(std::string("cim:'
+ class_name
+ "."
+ label
Expand All @@ -147,13 +152,13 @@ def create_nullptr_assigns(text, render):
return ""
else:
attributes_json = eval(attributes_txt)
nullptr_init_string = ": "
nullptr_init_string = ""
for attribute in attributes_json:
if attribute["is_class_attribute"]:
nullptr_init_string += "LABEL(nullptr), ".replace("LABEL", attribute["label"])

if len(nullptr_init_string) > 2:
return nullptr_init_string[:-2]
return " : " + nullptr_init_string[:-2]
else:
return ""

Expand All @@ -168,34 +173,72 @@ def create_class_assign(text, render):
if _attribute_is_primitive_or_datatype_or_enum(attribute_json):
return ""
if attribute_json["is_list_attribute"]:
assign = (
"""
bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) {
if(OBJECT_CLASS* element = dynamic_cast<OBJECT_CLASS*>(BaseClass_ptr1)) {
if(dynamic_cast<ATTRIBUTE_CLASS*>(BaseClass_ptr2) != nullptr) {
element->LABEL.push_back(dynamic_cast<ATTRIBUTE_CLASS*>(BaseClass_ptr2));
if "inverseRole" in attribute_json:
inverse = attribute_json["inverseRole"].split(".")
assign = (
"""
bool assign_INVERSEC_INVERSEL(BaseClass*, BaseClass*);
bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2)
{
OBJECT_CLASS* element = dynamic_cast<OBJECT_CLASS*>(BaseClass_ptr1);
ATTRIBUTE_CLASS* element2 = dynamic_cast<ATTRIBUTE_CLASS*>(BaseClass_ptr2);
if (element != nullptr && element2 != nullptr)
{
if (std::find(element->LABEL.begin(), element->LABEL.end(), element2) == element->LABEL.end())
{
element->LABEL.push_back(element2);
return assign_INVERSEC_INVERSEL(BaseClass_ptr2, BaseClass_ptr1);
}
return true;
}
return false;
}""".replace( # noqa: E101,W191
"OBJECT_CLASS", attribute_json["domain"]
)
.replace("ATTRIBUTE_CLASS", attribute_class)
.replace("LABEL", attribute_json["label"])
.replace("INVERSEC", inverse[0])
.replace("INVERSEL", inverse[1])
)
else:
assign = (
"""
bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2)
{
if (OBJECT_CLASS* element = dynamic_cast<OBJECT_CLASS*>(BaseClass_ptr1))
{
if (dynamic_cast<ATTRIBUTE_CLASS*>(BaseClass_ptr2) != nullptr)
{
element->LABEL.push_back(dynamic_cast<ATTRIBUTE_CLASS*>(BaseClass_ptr2));
return true;
}
}
return false;
}""".replace( # noqa: E101,W191
"OBJECT_CLASS", attribute_json["domain"]
"OBJECT_CLASS", attribute_json["domain"]
)
.replace("ATTRIBUTE_CLASS", attribute_class)
.replace("LABEL", attribute_json["label"])
)
.replace("ATTRIBUTE_CLASS", attribute_class)
.replace("LABEL", attribute_json["label"])
)
elif "inverseRole" in attribute_json and attribute_json["is_used"]:
elif "inverseRole" in attribute_json:
inverse = attribute_json["inverseRole"].split(".")
assign = (
"""
bool assign_INVERSEC_INVERSEL(BaseClass*, BaseClass*);
bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) {
if(OBJECT_CLASS* element = dynamic_cast<OBJECT_CLASS*>(BaseClass_ptr1)) {
element->LABEL = dynamic_cast<ATTRIBUTE_CLASS*>(BaseClass_ptr2);
if(element->LABEL != nullptr)
return assign_INVERSEC_INVERSEL(BaseClass_ptr2, BaseClass_ptr1);
}
return false;
bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2)
{
OBJECT_CLASS* element = dynamic_cast<OBJECT_CLASS*>(BaseClass_ptr1);
ATTRIBUTE_CLASS* element2 = dynamic_cast<ATTRIBUTE_CLASS*>(BaseClass_ptr2);
if (element != nullptr && element2 != nullptr)
{
if (element->LABEL != element2)
{
element->LABEL = element2;
return assign_INVERSEC_INVERSEL(BaseClass_ptr2, BaseClass_ptr1);
}
return true;
}
return false;
}""".replace( # noqa: E101,W191
"OBJECT_CLASS", attribute_json["domain"]
)
Expand All @@ -207,13 +250,17 @@ def create_class_assign(text, render):
else:
assign = (
"""
bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2) {
if(OBJECT_CLASS* element = dynamic_cast<OBJECT_CLASS*>(BaseClass_ptr1)) {
element->LABEL = dynamic_cast<ATTRIBUTE_CLASS*>(BaseClass_ptr2);
if(element->LABEL != nullptr)
return true;
}
return false;
bool assign_OBJECT_CLASS_LABEL(BaseClass* BaseClass_ptr1, BaseClass* BaseClass_ptr2)
{
if(OBJECT_CLASS* element = dynamic_cast<OBJECT_CLASS*>(BaseClass_ptr1))
{
element->LABEL = dynamic_cast<ATTRIBUTE_CLASS*>(BaseClass_ptr2);
if (element->LABEL != nullptr)
{
return true;
}
}
return false;
}""".replace( # noqa: E101,W191
"OBJECT_CLASS", attribute_json["domain"]
)
Expand All @@ -228,48 +275,31 @@ def create_assign(text, render):
attribute_txt = render(text)
attribute_json = eval(attribute_txt)
assign = ""
_class = attribute_json["attribute_class"]
if not _attribute_is_primitive_or_datatype_or_enum(attribute_json):
return ""
label_without_keyword = attribute_json["label"]
if label_without_keyword == "switch":
label_without_keyword = "_switch"

if _class != "String":
assign = (
"""
bool assign_CLASS_LABEL(std::stringstream &buffer, BaseClass* BaseClass_ptr1) {
if(CLASS* element = dynamic_cast<CLASS*>(BaseClass_ptr1)) {
buffer >> element->LBL_WO_KEYWORD;
if(buffer.fail())
return false;
else
return true;
}
else
return false;
}""".replace( # noqa: E101,W191
"CLASS", attribute_json["domain"]
)
.replace("LABEL", attribute_json["label"])
.replace("LBL_WO_KEYWORD", label_without_keyword)
)
else:
assign = """
bool assign_CLASS_LABEL(std::stringstream &buffer, BaseClass* BaseClass_ptr1) {
if(CLASS* element = dynamic_cast<CLASS*>(BaseClass_ptr1)) {
element->LABEL = buffer.str();
if(buffer.fail())
assign = (
"""
bool assign_CLASS_LABEL(std::stringstream &buffer, BaseClass* BaseClass_ptr1)
{
if (CLASS* element = dynamic_cast<CLASS*>(BaseClass_ptr1))
{
buffer >> element->LBL_WO_KEYWORD;
if (buffer.fail())
return false;
else
return true;
}
return false;
}""".replace( # noqa: E101,W191
"CLASS", attribute_json["domain"]
).replace(
"LABEL", attribute_json["label"]
)
.replace("LABEL", attribute_json["label"])
.replace("LBL_WO_KEYWORD", label_without_keyword)
)

return assign

Expand Down Expand Up @@ -301,8 +331,8 @@ def _create_attribute_includes(text, render):
for attribute in attributes:
if _attribute_is_primitive_or_datatype_or_enum(attribute):
unique[attribute["attribute_class"]] = True
for clarse in unique:
include_string += '\n#include "' + clarse + '.hpp"'
for clarse in sorted(unique):
include_string += '#include "' + clarse + '.hpp"\n'
return include_string


Expand All @@ -317,8 +347,8 @@ def _create_attribute_class_declarations(text, render):
for attribute in attributes:
if attribute["is_class_attribute"] or attribute["is_list_attribute"]:
unique[attribute["attribute_class"]] = True
for clarse in unique:
include_string += "\nclass " + clarse + ";"
for clarse in sorted(unique):
include_string += " class " + clarse + ";\n"
return include_string


Expand Down Expand Up @@ -372,34 +402,31 @@ def _attribute_is_primitive_or_datatype(attribute: dict) -> bool:
"Factory",
"Folders",
"IEC61970",
"String",
"Task",
"UnknownType",
]

iec61970_blacklist = ["CIMClassList", "CIMNamespaces", "Folders", "Task", "IEC61970"]


def _is_enum_class(filepath):
def _is_primitive_or_enum_class(filepath):
with open(filepath, encoding="utf-8") as f:
try:
for line in f:
if "enum class" in line:
return True
if "static const BaseClassDefiner declare();" in line:
return False
except UnicodeDecodeError as error:
print("Warning: UnicodeDecodeError parsing {0}: {1}".format(filepath, error))
return False
return True


def _create_header_include_file(directory, header_include_filename, header, footer, before, after, blacklist):

lines = []

for filename in sorted(os.listdir(directory)):
filepath = os.path.join(directory, filename)
basepath, ext = os.path.splitext(filepath)
basename = os.path.basename(basepath)
if ext == ".hpp" and not _is_enum_class(filepath) and basename not in blacklist:
if ext == ".hpp" and not _is_primitive_or_enum_class(filepath) and basename not in blacklist:
lines.append(before + basename + after)
for line in lines:
header.append(line)
Expand All @@ -414,12 +441,18 @@ def resolve_headers(path: str, version: str): # NOSONAR
class_list_header = [
"#ifndef CIMCLASSLIST_H\n",
"#define CIMCLASSLIST_H\n",
"using namespace CIMPP;\n",
"/*\n",
"Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen\n",
"*/\n",
"#include <list>\n",
"static std::list<BaseClassDefiner> CIMClassList = {\n",
'#include "IEC61970.hpp"\n',
"using namespace CIMPP;\n",
"static std::list<BaseClassDefiner> CIMClassList =\n",
"{\n",
]
class_list_footer = [
" UnknownType::declare() };\n",
" UnknownType::declare(),\n",
"};\n",
"#endif // CIMCLASSLIST_H\n",
]

Expand All @@ -428,13 +461,23 @@ def resolve_headers(path: str, version: str): # NOSONAR
"CIMClassList.hpp",
class_list_header,
class_list_footer,
" ",
" ",
"::declare(),\n",
class_blacklist,
)

iec61970_header = ["#ifndef IEC61970_H\n", "#define IEC61970_H\n"]
iec61970_footer = ['#include "UnknownType.hpp"\n', "#endif"]
iec61970_header = [
"#ifndef IEC61970_H\n",
"#define IEC61970_H\n",
"/*\n",
"Generated from the CGMES files via cimgen: https://github.com/sogno-platform/cimgen\n",
"*/\n",
"\n",
]
iec61970_footer = [
'#include "UnknownType.hpp"\n',
"#endif",
]

_create_header_include_file(
path,
Expand Down
20 changes: 20 additions & 0 deletions cimgen/languages/cpp/static/BaseClass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "BaseClass.hpp"

using namespace CIMPP;

BaseClass::~BaseClass() {}

const char BaseClass::debugName[] = "BaseClass";
const char* BaseClass::debugString() const
{
return BaseClass::debugName;
}

void BaseClass::addConstructToMap(std::unordered_map<std::string, BaseClass* (*)()>& factory_map) {}
void BaseClass::addPrimitiveAssignFnsToMap(std::unordered_map<std::string, assign_function>& assign_map) {}
void BaseClass::addClassAssignFnsToMap(std::unordered_map<std::string, class_assign_function>& assign_map) {}

const BaseClassDefiner BaseClass::declare()
{
return BaseClassDefiner(BaseClass::addConstructToMap, BaseClass::addPrimitiveAssignFnsToMap, BaseClass::addClassAssignFnsToMap, BaseClass::debugName);
}
1 change: 1 addition & 0 deletions cimgen/languages/cpp/static/BaseClass.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "BaseClass.hpp"
Loading

0 comments on commit 9af3921

Please sign in to comment.