Added grib accessor CPP writer using Jinja

This commit is contained in:
kevstone 2024-01-16 15:55:46 +00:00
parent cb7d4a30fc
commit 97dd3bbc72
25 changed files with 655 additions and 111 deletions

View File

@ -25,9 +25,7 @@ class CppFunction(code_interface.CodeInterface):
# The whole function will be returned: signature and body (in braces)
def as_lines(self):
lines = self._funcsig.as_lines()
lines.append("{")
lines.extend(self._body)
lines.append("}")
return lines

View File

@ -7,11 +7,14 @@ import code_object.code_interface as code_interface
# Note: Internally the return type and name are stored as the Arg _func_arg, as this allows
# the return_type to be passed in as a string, with the Arg __init__ function providing
# consistent parsing
#
# Set is_declaration to true to render as a declaration (i.e. produce a string with a trailing ";")
class FuncSig(code_interface.CodeInterface):
def __init__(self, return_type, name, args, template=None) -> None:
self._func_arg = arg.Arg(return_type, name)
self._args = args
self._template = template
self._is_declaration = False
@property
def return_type(self):
@ -37,5 +40,17 @@ class FuncSig(code_interface.CodeInterface):
def template(self, value):
self._template = value
@property
def is_declaration(self):
return self._is_declaration
@is_declaration.setter
def is_declaration(self, value):
assert isinstance(value, bool), f"value must be bool, current type=[{type(value)}]"
self._is_declaration = value
def as_lines(self):
return [f"{self._func_arg.as_string()}({', '.join([a.as_string() for a in self.args if a])})"]
sig_string = f"{self._func_arg.as_string()}({', '.join([a.as_string() for a in self.args if a])})"
if self._is_declaration:
sig_string += ";"
return [sig_string]

View File

@ -129,7 +129,7 @@ class ArgConverter(code_interface_converter.CodeInterfaceConverter):
# Returns the equivalent C++ arg (name and type), which could be None
# Note there are some differences in what is produced if the arg is a function arg...
#
def to_cpp_code_object(self, conversion_data):
def create_cpp_code_object(self, conversion_data):
#def to_cpp_arg(self, transforms):
# For now, just change the name

View File

@ -11,8 +11,18 @@ class CodeInterfaceConverter:
self._ccode_object = ccode_object
# Convert the ccode_object to a cpp_code_object
# It must be overridden - this version just returns a copy of the ccode_object
#
# This is the function that *MUST* be called externally, as it ensures a copy
# of the converted object is returned to avoid reference issues (funcsig is particularly
# prone to this!)
#
# The real work is done in create_cpp_code_object() !!!
def to_cpp_code_object(self, conversion_data):
cpp_code_object = copy.deepcopy(self._ccode_object)
debug.line("to_cpp_code", f"Base version - just returning a copy of the C code object")
return cpp_code_object
cpp_code_object = self.create_cpp_code_object(conversion_data)
return copy.deepcopy(cpp_code_object)
# Actual implementation of to_cpp_code_object()
# It must be overridden - this version just returns the passed in ccode_object!
def create_cpp_code_object(self, conversion_data):
debug.line("to_cpp_code_object_internal", f"Base version - just returning the C code object")
return self._ccode_object

View File

@ -12,17 +12,23 @@ import debug
CodeInterfaceConverterClasses = {
arg.Arg: arg_converter.ArgConverter,
funcsig.FuncSig: funcsig_converter.FuncSigConverter,
# code_interface.CodeInterface: CodeInterfaceConverter,
}
# Convert a single C code_object into a C++ code_object
def convert_ccode_object(ccode_object, conversion_data):
converter_class = CodeInterfaceConverterClasses.get(type(ccode_object), code_interface_converter.CodeInterfaceConverter)
debug.line("convert_ccode_object", f"ccode_object type=[{type(ccode_object)}] converter_class=[{type(converter_class)}]")
converter = converter_class(ccode_object)
cpp_obj = converter.to_cpp_code_object(conversion_data)
return cpp_obj
# Convert a collection of C code_objects into C++ code_objects
def convert_ccode_objects(ccode_objects, conversion_data):
def convert_ccode_object_collection(ccode_objects, conversion_data):
cpp_code_objects = code_objects.CodeObjects()
for cobj in ccode_objects.code_objects:
converter_class = CodeInterfaceConverterClasses.get(type(cobj), code_interface_converter.CodeInterfaceConverter)
debug.line("convert_ccode_object", f"ccode_object type=[{type(cobj)}] converter_class=[{type(converter_class)}]")
converter = converter_class(cobj)
cpp_obj = converter.to_cpp_code_object(conversion_data)
cpp_obj = convert_ccode_object(cobj, conversion_data)
cpp_code_objects.add_code_object(cpp_obj)
return cpp_code_objects

View File

@ -33,26 +33,26 @@ class FuncSigConverter(code_interface_converter.CodeInterfaceConverter):
self._conversions = []
# Returns both the new cpp funcsig and an updated c funcsig
def to_cpp_code_object(self, conversion_data):
def create_cpp_code_object(self, conversion_data):
self._conversion_data = conversion_data
# If we have a mapping already stored, just return that!
# If we have a mapping already stored, just use that!
cppfuncsig = self._conversion_data.cppfuncsig_for_cfuncsig(self._ccode_object)
if cppfuncsig:
return cppfuncsig
if not cppfuncsig:
cppfuncsig = funcsig.FuncSig(
self.to_cpp_return_type(),
self.to_cpp_name(),
self.to_cpp_args(),
self._ccode_object.template)
cppfuncsig = funcsig.FuncSig(
self.to_cpp_return_type(),
self.to_cpp_name(),
self.to_cpp_args(),
self._ccode_object.template)
#cppfuncsig.static = self.is_cpp_static()
#cppfuncsig.static = self.is_cpp_static()
# Add this to the conversion data mappings
mapping = funcsig_mapping.FuncSigMapping(self._ccode_object, cppfuncsig)
self._conversion_data.add_to_funcsig_mappings(mapping)
# Add this to the conversion data mappings
mapping = funcsig_mapping.FuncSigMapping(self._ccode_object, cppfuncsig)
self._conversion_data.add_to_funcsig_mappings(mapping)
# Update the settings that we don't need (want?) to store in the map
cppfuncsig.is_declaration = self._ccode_object.is_declaration
return cppfuncsig

View File

@ -29,6 +29,36 @@ logging.basicConfig(
datefmt="%Y-%m-%d %H:%M:%S",
)
# Call the appropriate converter and return a list of converted cppcode objects
def convert_cfiles(convert_type):
converter_lib_name = f"{convert_type}.{convert_type}_converter"
LOG.info("Type=[%s] converter_lib_name=[%s]", convert_type, converter_lib_name)
try:
converter_lib = importlib.import_module(converter_lib_name)
converter = converter_lib.CONVERTER_CLASS(LOG)
return converter.convert_files(ARGS.path)
except ModuleNotFoundError:
LOG.error("Import failed, converter_lib_name=[%s]", converter_lib_name)
exit()
def write_cpp_files(write_type, cppcode_entries):
cpp_writer_lib_name = f"{write_type}.{write_type}_cpp_writer"
LOG.info("Type=[%s] cpp_writer_lib_name=[%s]", write_type, cpp_writer_lib_name)
try:
cpp_writer_lib = importlib.import_module(cpp_writer_lib_name)
writer = cpp_writer_lib.CPP_WRITER_CLASS(ARGS.target, LOG)
writer.write_files(cppcode_entries)
except ModuleNotFoundError:
LOG.error("Import failed, cpp_writer_lib_name=[%s]", cpp_writer_lib_name)
exit()
# Convert the C input files to C++ and write to disk
def generate_cpp_files(convert_type):
cppcode_entries = convert_cfiles(convert_type)
write_cpp_files(convert_type, cppcode_entries)
def main():
if os.path.exists(ARGS.libclang):
clang.cindex.Config.set_library_file(ARGS.libclang)
@ -41,20 +71,11 @@ def main():
if not ARGS.type:
LOG.info("C code type not supplied, using default.\n")
LOG.info("Use --type to specify the type, e.g: convert.py --type grib_accessor\n")
converter_type = "default"
convert_type = "default"
else:
converter_type = ARGS.type
convert_type = ARGS.type
converter_lib_name = f"{converter_type}.{converter_type}_converter"
LOG.info("Type=[%s] converter_lib_name=[%s]", converter_type, converter_lib_name)
try:
converter_lib = importlib.import_module(converter_lib_name)
converter = converter_lib.CONVERTER_CLASS(LOG)
converter.convert_files(ARGS.path)
except ModuleNotFoundError:
LOG.error("Import failed, converter_lib_name=[%s]", converter_lib_name)
exit()
generate_cpp_files(convert_type)
if __name__ == "__main__":
main()

View File

@ -3,7 +3,7 @@ from datetime import datetime
func_pad = 40
debug_enabled = True
debug_filter_include = ["convert_global_declarations", "dump_function"] #["to_accessor_data"]
debug_filter_include = [] #["convert_global_declarations", "dump_function"]
debug_filter_exclude = []
show_time = False

View File

@ -290,13 +290,11 @@ class DefaultCASTParser:
# Assume this is just a declaration - the function body will be converted via a function class...
def parse_FUNCTION_DECL(self, node):
cfuncsig = cnode_utils.create_cfuncsig(node)
cfuncsig.is_declaration = True
return cfuncsig
def parse_STRUCT_DECL(self, node):
cstruct_arg = cnode_utils.create_cstruct_arg(node)
#TODO - Convert to C++
return cstruct_arg
def parse_PARM_DECL(self, node):

View File

@ -79,6 +79,24 @@ class DefaultCCode:
assert cmember not in self._data_members, f"member [{cmember.as_string()}] already defined for [{self._class_name}]"
self._data_members.append(cmember)
def is_constructor(self, cfuncsig):
return False
def is_destructor(self, cfuncsig):
return False
def is_virtual_member_function(self, cfuncsig):
return False
def is_member_function(self, cfuncsig):
return False
def is_class_member_function(self, cfuncsig):
return self.is_constructor(cfuncsig) \
or self.is_destructor(cfuncsig) \
or self.is_virtual_member_function(cfuncsig) \
or self.is_member_function(cfuncsig)
# Class representation - END ##############################
@property
@ -90,11 +108,21 @@ class DefaultCCode:
return ""
# Add a function object from the funcsig and body (AST)
# This version assumes it is a free function
# Override to identify class member functions
# Override is_xxx functions to identify class member functions
def add_function(self, cfuncsig, body):
cfunc = cfunction.CFunction(cfuncsig, body)
self._functions.append(cfunc)
if self.is_constructor(cfuncsig):
self._constructor = cfunc
elif self.is_destructor(cfuncsig):
self._destructor = cfunc
elif self.is_virtual_member_function(cfuncsig):
self._virtual_member_functions.append(cfunc)
elif self.is_member_function(cfuncsig):
self._member_functions.append(cfunc)
else:
# Must be a "free"" function
self._functions.append(cfunc)
# Debug Support
def dump_global_declarations(self):

View File

@ -18,15 +18,17 @@ class DefaultCCodeConverter:
def cast_parser_class(self):
return default_cast_parser.DefaultCASTParser
# Override to set the correct CppCode class as required by derived classes
@property
def cpp_code_class(self):
return default_cppcode.DefaultCppCode
# Override to create the required CppCode class
def create_cpp_code(self):
cpp_filename = self._ccode.cfilename
cpp_class_name = self._ccode.class_name
self._cppcode = default_cppcode.DefaultCppCode(cpp_filename, cpp_class_name)
def convert(self):
self.create_cpp_code()
self.create_conversion_data()
self.convert_global_declarations()
self.convert_data_members()
self.convert_functions()
self.convert_constructor_function()
self.convert_destructor_function()
@ -34,9 +36,6 @@ class DefaultCCodeConverter:
self.convert_virtual_member_functions()
return self._cppcode
def create_cpp_code(self):
self._cppcode = self.cpp_code_class()
def create_conversion_data(self):
self._conversion_data = conversion_data.ConversionData()
@ -47,18 +46,19 @@ class DefaultCCodeConverter:
global_decl_ast_parser = self.cast_parser_class()
global_decl_ccode_objects = global_decl_ast_parser.to_ccode_objects(self._ccode.global_declarations, self._conversion_data, self._ccode.macro_details)
debug.line("convert_global_declarations", global_decl_ccode_objects.as_lines())
global_decl_cpp_code_objects = conversion_funcs.convert_ccode_objects(global_decl_ccode_objects, self._conversion_data)
debug.line("convert_global_declarations", f"Converted C++ code...")
global_decl_cpp_code_objects = conversion_funcs.convert_ccode_object_collection(global_decl_ccode_objects, self._conversion_data)
self._cppcode.add_global_declaration(global_decl_cpp_code_objects)
debug.line("convert_global_declarations", f"Converted C++ code [as_lines]...")
debug.line("convert_global_declarations", global_decl_cpp_code_objects.as_lines())
def convert_data_members(self):
for cmember in self._ccode.data_members:
pass # TO DO
# Helper to create the funcsig mapping and update the transforms
def convert_cfunction_funcsig(self, cfunc):
cppfuncsig = self._conversion_data.cppfuncsig_for_cfuncsig(cfunc.funcsig)
if not cppfuncsig:
funcsig_conv = funcsig_converter.FuncSigConverter(cfunc.funcsig)
cppfuncsig = funcsig_conv.to_cpp_code_object(self._conversion_data)
assert cppfuncsig, f"Error creating cppfuncsig from cfuncsig=[{cfunc.funcsig.as_string()}]"
cppfuncsig = conversion_funcs.convert_ccode_object(cfunc.funcsig, self._conversion_data)
assert cppfuncsig, f"Error creating cppfuncsig from cfuncsig=[{cfunc.funcsig.as_string()}]"
return cppfuncsig
def convert_cfunction_body(self, cfunc):
@ -66,8 +66,8 @@ class DefaultCCodeConverter:
function_body_ccode_objects = function_body_ast_parser.to_ccode_objects(cfunc.body, self._conversion_data, self._ccode.macro_details)
return function_body_ccode_objects.as_lines()
# Default conversion routine
def convert_cfunction(self, cfunc):
# Use this if no other version provided...
def default_convert_function(self, cfunc):
cppfuncsig = self.convert_cfunction_funcsig(cfunc)
cppbody = self.convert_cfunction_body(cfunc)
cppfunc = cppfunction.CppFunction(cppfuncsig, cppbody)
@ -75,33 +75,34 @@ class DefaultCCodeConverter:
def convert_functions(self):
for func in self._ccode.functions:
cppfunc = self.convert_cfunction(func)
cppfunc = self.default_convert_function(func)
self._cppcode.add_function(cppfunc)
self.dump_function("Default convert_functions", cppfunc)
# Override as required...
def convert_constructor_function(self):
if self._ccode.constructor :
constructor = self.convert_cfunction(self._ccode.constructor)
constructor = self.default_convert_function(self._ccode.constructor)
self._cppcode.add_constructor(constructor)
self.dump_function("Default convert_constructor_function", constructor)
# Override as required...
def convert_destructor_function(self):
if self._ccode.destructor :
destructor = self.convert_cfunction(self._ccode.destructor)
destructor = self.default_convert_function(self._ccode.destructor)
self._cppcode.add_destructor(destructor)
self.dump_function("Default convert_destructor_function", destructor)
# Override as required...
def convert_member_functions(self):
debug.line("convert_member_functions", f"Converting member functions...")
for entry in self._ccode.member_functions:
member_func = self.convert_cfunction(entry)
member_func = self.default_convert_function(entry)
self._cppcode.add_member_function(member_func)
self.dump_function("Default convert_member_functions", member_func)
# Override as required...
def convert_virtual_member_functions(self):
debug.line("convert_virtual_member_functions", f"Converting virtual member functions...")
for entry in self._ccode.virtual_member_functions:
virtual_member_func = self.convert_cfunction(entry)
virtual_member_func = self.default_convert_function(entry)
self._cppcode.add_virtual_member_function(virtual_member_func)
self.dump_function("Default convert_virtual_member_functions", virtual_member_func)
# Helper for consistent debug output!

View File

@ -27,9 +27,18 @@ class DefaultCFileParser:
def create_ccode(self):
self._ccode = default_ccode.DefaultCCode(self._cfilename)
# Default behaviour is to just add to the ccode object
# Default behaviour is to just add to the ccode object, UNLESS it is a
# class member function
#
# This includes function definitions so we can determine whether to add a
# forward declaration (nb all nodes are references so it isn't a big overhead)
def parse_global_declaration(self, node):
self._ccode.add_global_declaration(node)
if node.kind == clang.cindex.CursorKind.FUNCTION_DECL:
cfuncsig = cnode_utils.create_cfuncsig(node)
if not self._ccode.is_class_member_function(cfuncsig):
self._ccode.add_global_declaration(node)
else:
self._ccode.add_global_declaration(node)
def parse_function_definition(self, node):
cfuncsig = cnode_utils.create_cfuncsig(node)
@ -63,9 +72,7 @@ class DefaultCFileParser:
if node.kind == clang.cindex.CursorKind.FUNCTION_DECL and node.is_definition():
self.parse_function_definition(node)
# Add *ALL* nodes to the global declaration.
# This includes function definitions so we can determine whether to add a
# forward declaration (nb all nodes are references so it isn't a big overhead)
# Parse *ALL* nodes to determine whether to add to the global declaration.
self.parse_global_declaration(node)
else:

View File

@ -23,12 +23,17 @@ class DefaultConverter:
return []
# This is the main entry point (should only need to call this!)
# Returns a list of cppcode objects that can be written to files
def convert_files(self, files):
cppcode_entries = []
for ccode_entry in self.parse_files(files):
self._cli_logger.info("Converting C to C++ for source %s", ccode_entry.cfilename)
ccode_conv = self.ccode_converter_class(ccode_entry)
ccode_conv.convert()
cppcode_entry = ccode_conv.convert()
cppcode_entries.append(cppcode_entry)
return cppcode_entries
# Called by convert_files()...
def parse_files(self, files):

View File

@ -0,0 +1,88 @@
from jinja2 import Environment, FileSystemLoader, StrictUndefined
import debug
import os
# Write the supplied cppcode objects to disk
class DefaultCppWriter:
def __init__(self, target_path, cli_logger):
self._target_path = target_path
self._cli_logger = cli_logger
# Override these as required
self._j2_template_path = f"{os.path.dirname(__file__)}/j2"
self._header_template = "default_header.h.j2"
self._source_template = "default_source.cc.j2"
self._cmakelists_template = "default_cmakelists.txt.j2"
def setup_jinja(self):
debug.line("__init__", f"j2 template path=[{self._j2_template_path}]")
self._env = Environment(
loader=FileSystemLoader(self._j2_template_path),
undefined=StrictUndefined,
)
# Main entry point to write out the files
def write_files(self, cppcode_entries):
self.setup_jinja()
for cppcode_entry in cppcode_entries:
self.write_header_file(cppcode_entry)
self.write_source_file(cppcode_entry)
filenames = [cpp.file_stem for cpp in cppcode_entries]
self.write_makefile(filenames)
def write_header_file(self, cppcode):
file_stem = cppcode.file_stem
debug.line("write_header_file", f"file_stem = {file_stem}")
template = self._env.get_template(self._header_template)
self.save(file_stem, "h", template.render(c=cppcode))
def write_source_file(self, cppcode):
file_stem = cppcode.file_stem
debug.line("write_source_file", f"file_stem = {file_stem}")
template = self._env.get_template(self._source_template)
self.save(file_stem, "cc", template.render(c=cppcode))
def write_makefile(self, filenames):
template = self._env.get_template(self._cmakelists_template)
content = template.render(c=filenames)
target = os.path.join(self._target_path, "CMakeLists.txt")
self._cli_logger.info("Writing %s", target)
os.makedirs(os.path.dirname(target), exist_ok=True)
with open(target, "w") as f:
f.write(content)
def save(self, file_stem, ext, content):
target = os.path.join(self._target_path, f"{file_stem}.{ext}")
self._cli_logger.info("Writing %s", target)
tmp = os.path.join(self._target_path, f"{file_stem}-tmp.{ext}")
os.makedirs(os.path.dirname(target), exist_ok=True)
with open(tmp, "w") as f:
f.write(content)
ret = os.system(f"clang-format -i {tmp}")
assert ret == 0
# So we don't recompile everything
if os.path.exists(target):
with open(target) as f:
old = f.read()
with open(tmp) as f:
new = f.read()
if old == new:
self._cli_logger.info("No change")
os.unlink(tmp)
return
self._cli_logger.info("Updating %s", target)
os.rename(tmp, target)
# Read from convert.py to get the correct class name
CPP_WRITER_CLASS=DefaultCppWriter

View File

@ -1,20 +1,56 @@
import debug
import os
import code_object.cppfunction as cppfunction
import code_object.code_objects as code_objects
# Represents the conversion of a CCode class to C++
# Represents the C++ conversion of a CCode instance
# Stored as CodeInterface objects
class DefaultCppCode:
def __init__(self) -> None:
self._global_declarations = []
def __init__(self, filename, class_name="", super_class_name="") -> None:
self._global_declarations = code_objects.CodeObjects()
self._functions = []
self._filename = filename
self._class_name = class_name
self._super_class_name = super_class_name
self._constructor = None
self._destructor = None
self._member_functions = []
self._virtual_member_functions = []
self._data_members = []
self._header_file_includes = []
self._source_file_includes = []
@property
def filename(self):
return self._filename
@property
def file_stem(self):
return os.path.splitext(os.path.basename(self._filename))[0]
@property
def class_name(self):
return self._class_name
@property
def super_class_name(self):
return self._super_class_name
@property
def nested_namespaces(self):
return "NAMESPACE1::NAMESPACE2"
@property
def forward_declarations(self):
return []
@property
def global_declarations(self):
return self._global_declarations
def add_global_declaration(self, line):
self._global_declarations.append(line)
def add_global_declaration(self, global_decl_ccode_objects):
self._global_declarations = global_decl_ccode_objects
@property
def functions(self):
@ -23,3 +59,59 @@ class DefaultCppCode:
def add_function(self, cppfunc):
self._functions.append(cppfunc)
@property
def constructor(self):
return self._constructor
def add_constructor(self, constructor):
self._constructor = constructor
@property
def destructor(self):
return self._destructor
def add_destructor(self, destructor):
self._destructor = destructor
@property
def member_functions(self):
return self._member_functions
def add_member_function(self, member_function):
self._member_functions.append(member_function)
@property
def template_member_functions(self):
return self._member_functions
@property
def virtual_member_functions(self):
return self._virtual_member_functions
def add_virtual_member_function(self, virtual_member_function):
self._virtual_member_functions.append(virtual_member_function)
@property
def virtual_member_functions_using_list(self):
return []
@property
def data_members(self):
return self._data_members
def add_data_member_function(self, data_member):
self._data_members.append(data_member)
@property
def header_file_includes(self):
return self._header_file_includes
def add_header_file_include(self, file):
self._header_file_includes.append(file)
@property
def source_file_includes(self):
return self._source_file_includes
def add_source_file_include(self, file):
self._source_file_includes.append(file)

View File

@ -0,0 +1,23 @@
#
# (C) Copyright 2005- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
#
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.
#
set(converted_accessor_data_dir ${CMAKE_CURRENT_LIST_DIR}/eccodes/accessor)
# Define all the source files to build for the C++ accessors
# Note the PARENT_SCOPE option sets the variable in the calling CMakeLists.txt
# file - it remains undefined in this file: https://cmake.org/cmake/help/latest/command/set.html
set(converted_cpp_src_files
{% for file in c-%}
${CMAKE_CURRENT_LIST_DIR}/{{ file }}.cc
${CMAKE_CURRENT_LIST_DIR}/{{ file }}.h
{% endfor -%}
PARENT_SCOPE
)

View File

@ -0,0 +1,50 @@
/*
* (C) Copyright 2005- ECMWF.
*
* This software is licensed under the terms of the Apache Licence Version 2.0
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
*
* In applying this licence, ECMWF does not waive the privileges and immunities granted to it by
* virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction.
*/
#pragma once
{% for i in c._header_file_includes -%}
#include "{{ i }}"
{%- endfor %}
namespace {{ c.nested_namespaces }} {
// Forward declarations
{%- for f in c.forward_declarations %}
{{ f }}
{%- endfor %}
{% if c.class_name %}
class {{ c.class_name }} : public {{ c.super_class_name }} {
public:
{{ c.class_name }}({{c.constructor.funcsig.as_string()}});
~{{ c.class_name }}() = default;
protected:
// Virtual member functions
{%- for u in c.virtual_member_functions_using_list %}
using c.class_name::{{u}};{% endfor %}
{%- for m in c.virtual_member_functions %}
{{ m.funcsig.as_string() }};{% endfor %}
// Data members
{% for m in c.data_members %} {{ m.as_declaration() }}{{ m.default_value }};
{%- endfor %}
private:
// Member functions
{%- for m in c.template_member_functions %}
{{ m.funcsig.as_string() }};{% endfor %}
{%- for m in c.member_functions %}
{{ m.funcsig.as_string() }};{% endfor %}
};
{% endif %}
} // namespace {{ c.nested_namespaces }}

View File

@ -0,0 +1,47 @@
/*
* (C) Copyright 2005- ECMWF.
*
* This software is licensed under the terms of the Apache Licence Version 2.0
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
*
* In applying this licence, ECMWF does not waive the privileges and immunities granted to it by
* virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction.
*/
{%- for i in c.source_file_includes %}
#include {{ i }}
{%- endfor %}
namespace {{ c.nested_namespaces }} {
// Global declarations - START
{{ c.global_declarations.as_string() }}
// Global declarations - END
{% for func in c.functions %}
{{ func.as_string() }}
{% endfor %}
{% if c.class_name %}
{{ c.constructor.as_string() }}
{% if c.destructor %}
{{ c.destructor.as_string() }}
{% endif %}
{% for func in c.template_member_functions %}
{{ func.as_string() }}
{% endfor %}
{% for func in c.virtual_member_functions %}
{{ func.as_string() }}
{% endfor %}
{% for func in c.member_functions %}
{{ func.as_string() }}
{% endfor %}
{% endif %}
} // namespace {{ c.nested_namespaces }}

View File

@ -59,28 +59,23 @@ class GribAccessorCCode(default_ccode.DefaultCCode):
else:
return super().parent_cfilename
# Overridden to classify the functions being added
def add_function(self, cfuncsig, body):
cfunc = cfunction.CFunction(cfuncsig, body)
def is_constructor(self, cfuncsig):
return cfuncsig.name == "init"
if cfuncsig.name == "init":
self._constructor = cfunc
return
elif cfuncsig.name == "destroy":
self._destructor = cfunc
return
elif cfuncsig.name in grib_accessor_virtual_member_functions:
self._virtual_member_functions.append(cfunc)
return
# If any arg starts with a "ptr type name", then it's a member function (as we've already extracted virtual functions)
for arg_entry in cfuncsig.args:
if re.search(r"grib_accessor(\w*)?\s*\*", arg_entry.decl_spec.as_string()):
self._member_functions.append(cfunc)
return
def is_destructor(self, cfuncsig):
return cfuncsig.name == "destroy"
# Must be a "free"" function
self._functions.append(cfunc)
def is_virtual_member_function(self, cfuncsig):
return cfuncsig.name in grib_accessor_virtual_member_functions
def is_member_function(self, cfuncsig):
if not self.is_virtual_member_function(cfuncsig):
# If any arg starts with a "ptr type name", then it's a member function (as we've already extracted virtual functions)
for arg_entry in cfuncsig.args:
if re.search(r"grib_accessor(\w*)?\s*\*", arg_entry.decl_spec.as_string()):
return True
return False
def dump_class_info(self):
super().dump_class_info()

View File

@ -1,15 +1,39 @@
import debug
import os
import default.default_ccode_converter as default_ccode_converter
import default.default_cppcode as default_cppcode
import code_object.cppfunction as cppfunction
import standard_transforms
prefix = "grib_accessor_class_"
rename = {
"Gen": "Accessor", # "Generic",
"Md5": "Md5Sum", # We rename because of macos case insensitive file system
"Assert": "Assertion", # Name clash with assert.h
}
# Converter for grib_accessor_ccode classes...
class GribAccessorCCodeConverter(default_ccode_converter.DefaultCCodeConverter):
def __init__(self, ccode_instance) -> None:
super().__init__(ccode_instance)
# TODO when the class is available...
@property
def cpp_code_class(self):
return super().cpp_code_class
# Convert e.g. grib_accessor_class_proj_string.cc to ProjStringData.cc
def transform_file_name(self, name):
name_stem, name_ext = os.path.splitext(os.path.basename(name))
name_stem = self.transform_class_name(name_stem)
return f"{name_stem}{name_ext}"
# Convert e.g. grib_accessor_class_proj_string to ProjStringData
def transform_class_name(self, name):
name = name.replace(prefix, "")
name = standard_transforms.transform_type_name(name)
return rename.get(name, name) + "Data"
# TODO update when the class is available...
def create_cpp_code(self):
cpp_filename = self.transform_file_name(self._ccode.cfilename)
cpp_class_name = self.transform_class_name(self._ccode.class_name)
cpp_super_class_name = self.transform_class_name(self._ccode.super_class_name)
debug.line("create_cpp_code", f"cpp_filename=[{cpp_filename}] cpp_class_name=[{cpp_class_name}] cpp_super_class_name=[{cpp_super_class_name}]")
self._cppcode = default_cppcode.DefaultCppCode(cpp_filename, cpp_class_name, cpp_super_class_name)

View File

@ -48,5 +48,4 @@ class GribAccessorCParser(default_cfile_parser.DefaultCFileParser):
elif node.spelling == self.base_class:
return # ignore!
else:
# Add everything else for now, we'll filter it when converting to C++!
self._ccode.add_global_declaration(node)
super().parse_global_declaration(node)

View File

@ -0,0 +1,16 @@
import default.default_cpp_writer as default_cpp_writer
import os
# Write the supplied GRIB Accessor cppcode objects to disk
class GribAccessorCppWriter(default_cpp_writer.DefaultCppWriter):
def __init__(self, target_path, cli_logger):
super().__init__(target_path, cli_logger)
self._j2_template_path = f"{os.path.dirname(__file__)}/j2"
self._header_template = "grib_accessor_header.h.j2"
self._source_template = "grib_accessor_source.cc.j2"
self._cmakelists_template = "grib_accessor_cmakelists.txt.j2"
# Read from convert.py to get the correct class name
CPP_WRITER_CLASS=GribAccessorCppWriter

View File

@ -0,0 +1,23 @@
#
# (C) Copyright 2005- ECMWF.
#
# This software is licensed under the terms of the Apache Licence Version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
#
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.
#
set(converted_accessor_data_dir ${CMAKE_CURRENT_LIST_DIR}/eccodes/accessor)
# Define all the source files to build for the C++ accessors
# Note the PARENT_SCOPE option sets the variable in the calling CMakeLists.txt
# file - it remains undefined in this file: https://cmake.org/cmake/help/latest/command/set.html
set(converted_accessor_data_src_files
{% for file in c-%}
${CMAKE_CURRENT_LIST_DIR}/eccodes/accessor/{{ file }}.cc
${CMAKE_CURRENT_LIST_DIR}/eccodes/accessor/{{ file }}.h
{% endfor -%}
PARENT_SCOPE
)

View File

@ -0,0 +1,51 @@
/*
* (C) Copyright 2005- ECMWF.
*
* This software is licensed under the terms of the Apache Licence Version 2.0
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
*
* In applying this licence, ECMWF does not waive the privileges and immunities granted to it by
* virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction.
*/
#pragma once
// DEBUG - REMOVE!
#include "grib_api_internal.h"
{% for i in c._header_file_includes -%}
#include "{{ i }}"
{%- endfor %}
namespace {{ c.nested_namespaces }} {
// Forward declarations
{%- for f in c.forward_declarations %}
{{ f }}
{%- endfor %}
class {{ c.class_name }} : public {{ c.super_class_name }} {
public:
{{ c.class_name }}({{c.constructor.funcsig.as_string()}});
~{{ c.class_name }}() = default;
protected:
// Virtual member functions
{%- for u in c.virtual_member_functions_using_list %}
using c.class_name::{{u}};{% endfor %}
{%- for m in c.virtual_member_functions %}
{{ m.funcsig.as_string() }};{% endfor %}
// Data members
{% for m in c.data_members %} {{ m.as_declaration() }}{{ m.default_value }};
{%- endfor %}
private:
// Member functions
{%- for m in c.template_member_functions %}
{{ m.funcsig.as_string() }};{% endfor %}
{%- for m in c.member_functions %}
{{ m.funcsig.as_string() }};{% endfor %}
};
} // namespace {{ c.nested_namespaces }}

View File

@ -0,0 +1,47 @@
/*
* (C) Copyright 2005- ECMWF.
*
* This software is licensed under the terms of the Apache Licence Version 2.0
* which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
*
* In applying this licence, ECMWF does not waive the privileges and immunities granted to it by
* virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction.
*/
// DEBUG - REMOVE!
#include "ProjStringData.h"
{%- for i in c.source_file_includes %}
#include {{ i }}
{%- endfor %}
namespace {{ c.nested_namespaces }} {
// Global declarations - START
{{ c.global_declarations.as_string() }}
// Global declarations - END
{% for func in c.functions %}
{{ func.as_string() }}
{% endfor %}
{{ c.constructor.as_string() }}
{% if c.destructor %}
{{ c.destructor.as_string() }}
{% endif %}
{% for func in c.template_member_functions %}
{{ func.as_string() }}
{% endfor %}
{% for func in c.virtual_member_functions %}
{{ func.as_string() }}
{% endfor %}
{% for func in c.member_functions %}
{{ func.as_string() }}
{% endfor %}
} // namespace {{ c.nested_namespaces }}