mirror of https://github.com/ecmwf/eccodes.git
Added grib accessor CPP writer using Jinja
This commit is contained in:
parent
cb7d4a30fc
commit
97dd3bbc72
|
@ -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
|
||||
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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 }}
|
|
@ -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 }}
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
)
|
|
@ -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 }}
|
|
@ -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 }}
|
Loading…
Reference in New Issue