From 803668064a8cd512a7df78aa26b6a0e5daa8de43 Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Tue, 9 Jan 2018 17:33:04 -0800 Subject: [PATCH 01/19] Simplify LibraryNode and ClassNode They now only deal with one level of variables in node dictionary. i.e. LibraryNode will only create a LibraryNode, it will not populate it with classes and functions. Added routine create_library_from_dictionary to do that. --- shroud/ast.py | 198 +++++++++++++++++++++++++++------------------- shroud/main.py | 2 +- tests/test_ast.py | 16 ++-- 3 files changed, 126 insertions(+), 90 deletions(-) diff --git a/shroud/ast.py b/shroud/ast.py index bd1d9ffe3..cc0277846 100644 --- a/shroud/ast.py +++ b/shroud/ast.py @@ -85,56 +85,12 @@ def eval_template(self, name, tname='', fmt=None): tname = name + tname + '_template' setattr(fmt, name, util.wformat(self.options[tname], fmt)) - def check_options_only(self, node, parent): - """Process an options only entry in a list. - - functions: - - options: - a = b - - decl: - options: - - Return True if node only has options. - Return Options instance to use. - node is assumed to be a dictionary. - Update current set of options from node['options']. + def add_function(self, node, options=None): + """Add a function from dictionary node. """ - if len(node) != 1: - return False, parent - if 'options' not in node: - return False, parent - options = node['options'] - if not options: - return False, parent - if not isinstance(options, dict): - raise TypeError("options must be a dictionary") - - new = util.Options(parent=parent) - new.update(node['options']) - return True, new - - def add_functions(self, node, cls_name, member): - """ Add functions from dictionary 'node'. - - Used with class methods and functions. - """ - if member not in node: - return - functions = node[member] - if not isinstance(functions, list): - raise TypeError("functions must be a list") - - options = self.options - for func in functions: - only, options = self.check_options_only(func, options) - if not only: - self.functions.append(FunctionNode(func, self, cls_name, options)) - - def _to_dict(self): - """Convert to dictionary. - Used by util.ExpandedEncoder. - """ - return dict() + fcnnode = FunctionNode(node, self, options) + self.functions.append(fcnnode) + return fcnnode ###################################################################### @@ -161,7 +117,6 @@ def __init__(self, node=None): if node is None: node = dict() - self.node = node self.library = node.get('library', 'default_library') self.copyright = node.setdefault('copyright', []) @@ -202,9 +157,6 @@ def __init__(self, node=None): # YAML treats blank string as None self.namespace = node['namespace'] - self.add_classes(node) - self.add_functions(node, None, 'functions') - def default_options(self): """default options.""" def_options = util.Options( @@ -367,30 +319,12 @@ def default_format(self, node): fmt_library.stdlib = 'std::' - def add_classes(self, node): - """Add classes from a dictionary. - - classes: - - name: Class1 - - name: Class2 + def add_class(self, node): + """Add a class from dictionary node. """ - if 'classes' not in node: - return - classes = node['classes'] - if not isinstance(classes, list): - raise TypeError("classes must be a list") - - # Add all class types to parser first - # Emulate forward declarations of classes. - for cls in classes: - if not isinstance(cls, dict): - raise TypeError("classes[n] must be a dictionary") - if 'name' not in cls: - raise TypeError("class does not define name") - declast.add_type(cls['name']) - - for cls in classes: - self.classes.append(ClassNode(cls['name'], self, cls)) + clsnode = ClassNode(node['name'], self, node) + self.classes.append(clsnode) + return clsnode def _to_dict(self): """Convert to dictionary. @@ -458,8 +392,6 @@ def __init__(self, name, parent, node=None): self.eval_template('F_module_name', '_class') self.eval_template('F_impl_filename', '_class') - self.add_functions(node, name, 'methods') - def _to_dict(self): """Convert to dictionary. Used by util.ExpandedEncoder. @@ -511,10 +443,12 @@ class FunctionNode(AstNode): - def __init__(self, node, parent, cls_name, options): - self.options = util.Options(parent=options) + def __init__(self, node, parent, options=None): + if options is None: + self.options = util.Options(parent=parent.options) + else: + self.options = util.Options(parent=options) self.update_options_from_dict(node) - options = self.options self._fmt = util.Options(parent._fmt) self.option_to_fmt() @@ -587,6 +521,11 @@ def __init__(self, node, parent, cls_name, options): if 'decl' in node: # parse decl and add to dictionary + if isinstance(parent,ClassNode): + cls_name = parent.name + else: + cls_name = None + self.decl = node['decl'] ast = declast.check_decl(node['decl'], current_class=cls_name, @@ -665,3 +604,100 @@ def _to_dict(self): if value is not None: # '' is OK d[key] = value return d + + def add_function(self, node, options=None): + # inherited from AstNode + raise RuntimeError("Cannot add a function to a FunctionNode") + + +def check_options_only(node, parent): + """Process an options only entry in a list. + + functions: + - options: + a = b + - decl: + options: + + Return True if node only has options. + Return Options instance to use. + node is assumed to be a dictionary. + Update current set of options from node['options']. + """ + if len(node) != 1: + return False, parent + if 'options' not in node: + return False, parent + options = node['options'] + if not options: + return False, parent + if not isinstance(options, dict): + raise TypeError("options must be a dictionary") + + new = util.Options(parent=parent) + new.update(node['options']) + return True, new + +def add_functions(parent, functions): + """ Add functions from list 'functions'. + Look for 'options' only entries. + + functions = [ + { + 'options': + },{ + 'decl': 'void func1()' + },{ + 'decl': 'void func2()' + } + ] + + Used with class methods and functions. + """ + if not isinstance(functions, list): + raise TypeError("functions must be a list") + + options = parent.options + for func in functions: + only, options = check_options_only(func, options) + if not only: + parent.add_function(func, options) + +def create_library_from_dictionary(node): + """Create a library and add classes and functions from node. + Typically, node is defined via YAML. + + library: name + classes: + - name: Class1 + functions: + - decl: void func1() + + Do some checking on the input. + Every class must have a name. + """ + library = LibraryNode(node) + + if 'classes' in node: + classes = node['classes'] + if not isinstance(classes, list): + raise TypeError("classes must be a list") + + # Add all class types to parser first + # Emulate forward declarations of classes. + for cls in classes: + if not isinstance(cls, dict): + raise TypeError("classes[n] must be a dictionary") + if 'name' not in cls: + raise TypeError("class does not define name") + declast.add_type(cls['name']) + + for cls in classes: + clsnode = library.add_class(cls) + if 'methods' in cls: + add_functions(clsnode, cls['methods']) + + if 'functions' in node: + add_functions(library, node['functions']) + + return library diff --git a/shroud/main.py b/shroud/main.py index 33d4d17fe..bcf6a95cb 100644 --- a/shroud/main.py +++ b/shroud/main.py @@ -132,7 +132,7 @@ def check_schema(self): def_types[key] = typemap.Typedef(key, **value) typemap.typedef_wrapped_defaults(def_types[key]) - newlibrary = ast.LibraryNode(node) + newlibrary = ast.create_library_from_dictionary(node) return newlibrary diff --git a/tests/test_ast.py b/tests/test_ast.py index 0155e0ba2..ab7018278 100644 --- a/tests/test_ast.py +++ b/tests/test_ast.py @@ -79,10 +79,10 @@ def test_a_library1(self): def test_b_function1(self): """Add a function to library""" - node = dict( - functions=[ dict(decl='void func1()') ] - ) - library = ast.LibraryNode(node) + function1=dict(decl='void func1()') + + library = ast.LibraryNode() + library.add_function(function1) self.assertEqual(len(library.functions), 1) @@ -113,7 +113,7 @@ def test_b_function2(self): }, ], ) - library = ast.LibraryNode(node) + library = ast.create_library_from_dictionary(node) self.assertEqual(len(library.functions), 2) self.assertEqual(library.options.testa, 'a') @@ -138,7 +138,7 @@ def test_c_class1(self): } ] ) - library = ast.LibraryNode(node) + library = ast.create_library_from_dictionary(node) self.assertEqual(len(library.classes), 1) @@ -165,7 +165,7 @@ def test_c_class2(self): }, ], ) - library = ast.LibraryNode(node) + library = ast.create_library_from_dictionary(node) self.assertEqual(len(library.classes), 2) self.assertEqual(len(library.classes[0].functions), 2) @@ -199,7 +199,7 @@ def test_c_class2(self): }, ], ) - library = ast.LibraryNode(node) + library = ast.create_library_from_dictionary(node) self.assertEqual(len(library.classes), 1) self.assertEqual(len(library.classes[0].functions), 2) From f429f40722f2d99bf724c1b50ad99712f67354b4 Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Tue, 9 Jan 2018 18:36:39 -0800 Subject: [PATCH 02/19] Replace node argument with **node Allows a keyword interface to constructors: FunctionNode(decl='void func1()') --- shroud/ast.py | 56 ++++++++++++++++------------------------------- tests/test_ast.py | 7 ++---- 2 files changed, 21 insertions(+), 42 deletions(-) diff --git a/shroud/ast.py b/shroud/ast.py index cc0277846..e413d23de 100644 --- a/shroud/ast.py +++ b/shroud/ast.py @@ -45,15 +45,6 @@ from . import declast class AstNode(object): - def update_options_from_dict(self, node): - """Update options from node. - """ - if 'options' in node and \ - node['options'] is not None: - if not isinstance(node['options'], dict): - raise TypeError("options must be a dictionary") - self.options.update(node['options'], replace=True) - def option_to_fmt(self): """Set fmt based on options dictionary. """ @@ -85,17 +76,17 @@ def eval_template(self, name, tname='', fmt=None): tname = name + tname + '_template' setattr(fmt, name, util.wformat(self.options[tname], fmt)) - def add_function(self, node, options=None): + def add_function(self, parentoptions=None, **node): """Add a function from dictionary node. """ - fcnnode = FunctionNode(node, self, options) + fcnnode = FunctionNode(self, parentoptions, **node) self.functions.append(fcnnode) return fcnnode ###################################################################### class LibraryNode(AstNode): - def __init__(self, node=None): + def __init__(self, options=None, **node): """Populate LibraryNode from a dictionary. fields = value @@ -113,10 +104,10 @@ def __init__(self, node=None): self.language = 'c++' # input language: c or c++ self.namespace = '' self.options = self.default_options() - self.F_module_dependencies = [] # unused + if options: + self.options.update(options, replace=True) - if node is None: - node = dict() + self.F_module_dependencies = [] # unused self.library = node.get('library', 'default_library') self.copyright = node.setdefault('copyright', []) @@ -135,8 +126,6 @@ def __init__(self, node=None): raise RuntimeError("language must be 'c' or 'c++'") self.language = node['language'] - self.update_options_from_dict(node) - self.default_format(node) self.option_to_fmt() @@ -319,10 +308,10 @@ def default_format(self, node): fmt_library.stdlib = 'std::' - def add_class(self, node): + def add_class(self, name, **node): """Add a class from dictionary node. """ - clsnode = ClassNode(node['name'], self, node) + clsnode = ClassNode(name, self, **node) self.classes.append(clsnode) return clsnode @@ -346,14 +335,11 @@ def _to_dict(self): ###################################################################### class ClassNode(AstNode): - def __init__(self, name, parent, node=None): + def __init__(self, name, parent, options=None, **node): self.name = name self.functions = [] self.cpp_header = '' - if node is None: - node = {} - # default cpp_header to blank if 'cpp_header' in node and node['cpp_header']: # YAML treats blank string as None @@ -374,7 +360,8 @@ def __init__(self, name, parent, node=None): setattr(self, n, node.get(n, None)) self.options = util.Options(parent=parent.options) - self.update_options_from_dict(node) + if options: + self.options.update(options, replace=True) options = self.options self._fmt = util.Options(parent._fmt) @@ -440,15 +427,10 @@ class FunctionNode(AstNode): } """ - - - - def __init__(self, node, parent, options=None): - if options is None: - self.options = util.Options(parent=parent.options) - else: - self.options = util.Options(parent=options) - self.update_options_from_dict(node) + def __init__(self, parent, parentoptions=None, options=None, **node): + self.options = util.Options(parent= parentoptions or parent.options) + if options: + self.options.update(options, replace=True) self._fmt = util.Options(parent._fmt) self.option_to_fmt() @@ -605,7 +587,7 @@ def _to_dict(self): d[key] = value return d - def add_function(self, node, options=None): + def add_function(self, options=None, **node): # inherited from AstNode raise RuntimeError("Cannot add a function to a FunctionNode") @@ -661,7 +643,7 @@ def add_functions(parent, functions): for func in functions: only, options = check_options_only(func, options) if not only: - parent.add_function(func, options) + parent.add_function(parentoptions=options, **func) def create_library_from_dictionary(node): """Create a library and add classes and functions from node. @@ -676,7 +658,7 @@ def create_library_from_dictionary(node): Do some checking on the input. Every class must have a name. """ - library = LibraryNode(node) + library = LibraryNode(**node) if 'classes' in node: classes = node['classes'] @@ -693,7 +675,7 @@ def create_library_from_dictionary(node): declast.add_type(cls['name']) for cls in classes: - clsnode = library.add_class(cls) + clsnode = library.add_class(**cls) if 'methods' in cls: add_functions(clsnode, cls['methods']) diff --git a/tests/test_ast.py b/tests/test_ast.py index ab7018278..ffbd49de0 100644 --- a/tests/test_ast.py +++ b/tests/test_ast.py @@ -61,14 +61,13 @@ def test_a_library1(self): def test_a_library1(self): """Update LibraryNode""" - node = dict( + library = ast.LibraryNode( language='c', options=dict( wrap_c=False, C_prefix='XXX_', ) ) - library = ast.LibraryNode(node) self.assertEqual(library.language, 'c') # updated from dict self.assertEqual(library.options.wrap_c, False) # updated from dict @@ -79,10 +78,8 @@ def test_a_library1(self): def test_b_function1(self): """Add a function to library""" - function1=dict(decl='void func1()') - library = ast.LibraryNode() - library.add_function(function1) + library.add_function(decl='void func1()') self.assertEqual(len(library.functions), 1) From fb2530a1d9307b66683ff151dc5c758d4e81ac11 Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Tue, 9 Jan 2018 18:48:01 -0800 Subject: [PATCH 03/19] Rename **node as **kwargs --- shroud/ast.py | 126 +++++++++++++++++++++++++------------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/shroud/ast.py b/shroud/ast.py index e413d23de..c86398b85 100644 --- a/shroud/ast.py +++ b/shroud/ast.py @@ -76,18 +76,18 @@ def eval_template(self, name, tname='', fmt=None): tname = name + tname + '_template' setattr(fmt, name, util.wformat(self.options[tname], fmt)) - def add_function(self, parentoptions=None, **node): - """Add a function from dictionary node. + def add_function(self, parentoptions=None, **kwargs): + """Add a function. """ - fcnnode = FunctionNode(self, parentoptions, **node) + fcnnode = FunctionNode(self, parentoptions, **kwargs) self.functions.append(fcnnode) return fcnnode ###################################################################### class LibraryNode(AstNode): - def __init__(self, options=None, **node): - """Populate LibraryNode from a dictionary. + def __init__(self, options=None, **kwargs): + """Create LibraryNode. fields = value options: @@ -109,24 +109,24 @@ def __init__(self, options=None, **node): self.F_module_dependencies = [] # unused - self.library = node.get('library', 'default_library') - self.copyright = node.setdefault('copyright', []) - self.patterns = node.setdefault('patterns', []) + self.library = kwargs.get('library', 'default_library') + self.copyright = kwargs.setdefault('copyright', []) + self.patterns = kwargs.setdefault('patterns', []) for n in ['C_header_filename', 'C_impl_filename', 'F_module_name', 'F_impl_filename', 'LUA_module_name', 'LUA_module_reg', 'LUA_module_filename', 'LUA_header_filename', 'PY_module_filename', 'PY_header_filename', 'PY_helper_filename', 'YAML_type_filename']: - setattr(self, n, node.get(n, None)) + setattr(self, n, kwargs.get(n, None)) - if 'language' in node: - language = node['language'].lower() + if 'language' in kwargs: + language = kwargs['language'].lower() if language not in ['c', 'c++']: raise RuntimeError("language must be 'c' or 'c++'") - self.language = node['language'] + self.language = kwargs['language'] - self.default_format(node) + self.default_format(kwargs) self.option_to_fmt() # default some options based on other options @@ -138,13 +138,13 @@ def __init__(self, options=None, **node): self.eval_template('F_impl_filename', '_library') # default cpp_header to blank - if 'cpp_header' in node and node['cpp_header']: + if 'cpp_header' in kwargs and kwargs['cpp_header']: # YAML treats blank string as None - self.cpp_header = node['cpp_header'] + self.cpp_header = kwargs['cpp_header'] - if 'namespace' in node and node['namespace']: + if 'namespace' in kwargs and kwargs['namespace']: # YAML treats blank string as None - self.namespace = node['namespace'] + self.namespace = kwargs['namespace'] def default_options(self): """default options.""" @@ -308,10 +308,10 @@ def default_format(self, node): fmt_library.stdlib = 'std::' - def add_class(self, name, **node): - """Add a class from dictionary node. + def add_class(self, name, **kwargs): + """Add a class. """ - clsnode = ClassNode(name, self, **node) + clsnode = ClassNode(name, self, **kwargs) self.classes.append(clsnode) return clsnode @@ -335,21 +335,21 @@ def _to_dict(self): ###################################################################### class ClassNode(AstNode): - def __init__(self, name, parent, options=None, **node): + def __init__(self, name, parent, options=None, **kwargs): self.name = name self.functions = [] self.cpp_header = '' # default cpp_header to blank - if 'cpp_header' in node and node['cpp_header']: + if 'cpp_header' in kwargs and kwargs['cpp_header']: # YAML treats blank string as None - self.cpp_header = node['cpp_header'] + self.cpp_header = kwargs['cpp_header'] self.namespace = '' - if 'namespace' in node and node['namespace']: + if 'namespace' in kwargs and kwargs['namespace']: # YAML treats blank string as None - self.namespace = node['namespace'] - self.python = node.get('python', {}) + self.namespace = kwargs['namespace'] + self.python = kwargs.get('python', {}) for n in ['C_header_filename', 'C_impl_filename', 'F_derived_name', 'F_impl_filename', 'F_module_name', @@ -357,7 +357,7 @@ def __init__(self, name, parent, options=None, **node): 'LUA_metadata', 'LUA_ctor_name', 'PY_PyTypeObject', 'PY_PyObject', 'PY_type_filename', 'class_prefix']: - setattr(self, n, node.get(n, None)) + setattr(self, n, kwargs.get(n, None)) self.options = util.Options(parent=parent.options) if options: @@ -427,7 +427,7 @@ class FunctionNode(AstNode): } """ - def __init__(self, parent, parentoptions=None, options=None, **node): + def __init__(self, parent, parentoptions=None, options=None, **kwargs): self.options = util.Options(parent= parentoptions or parent.options) if options: self.options.update(options, replace=True) @@ -456,38 +456,38 @@ def __init__(self, parent, parentoptions=None, options=None, **node): # self.function_index = [] # Only needed for json diff - self.attrs = node.get('attrs', None) + self.attrs = kwargs.get('attrs', None) - # Move fields from node into instance + # Move fields from kwargs into instance for n in [ 'C_error_pattern', 'C_name', 'C_post_call', 'C_post_call_buf', 'F_name_function', 'LUA_name', 'LUA_name_impl', 'PY_name_impl' ]: - setattr(self, n, node.get(n, None)) + setattr(self, n, kwargs.get(n, None)) - self.default_arg_suffix = node.get('default_arg_suffix', []) - self.docs = node.get('docs', '') - self.cpp_template = node.get('cpp_template', {}) - self.doxygen = node.get('doxygen', {}) - self.fortran_generic = node.get('fortran_generic', {}) - self.return_this = node.get('return_this', False) + self.default_arg_suffix = kwargs.get('default_arg_suffix', []) + self.docs = kwargs.get('docs', '') + self.cpp_template = kwargs.get('cpp_template', {}) + self.doxygen = kwargs.get('doxygen', {}) + self.fortran_generic = kwargs.get('fortran_generic', {}) + self.return_this = kwargs.get('return_this', False) - self.F_C_name = node.get('F_C_name', None) - self.F_name_generic = node.get('F_name_generic', None) - self.F_name_impl = node.get('F_name_impl', None) - self.PY_error_pattern = node.get('PY_error_pattern', '') + self.F_C_name = kwargs.get('F_C_name', None) + self.F_name_generic = kwargs.get('F_name_generic', None) + self.F_name_impl = kwargs.get('F_name_impl', None) + self.PY_error_pattern = kwargs.get('PY_error_pattern', '') # referenced explicity (not via fmt) - self.C_code = node.get('C_code', None) - self.C_return_code = node.get('C_return_code', None) - self.C_return_type = node.get('C_return_type', None) - self.F_code = node.get('F_code', None) + self.C_code = kwargs.get('C_code', None) + self.C_return_code = kwargs.get('C_return_code', None) + self.C_return_type = kwargs.get('C_return_type', None) + self.F_code = kwargs.get('F_code', None) -# self.function_suffix = node.get('function_suffix', None) # '' is legal value, None=unset - if 'function_suffix' in node: - self.function_suffix = node['function_suffix'] +# self.function_suffix = kwargs.get('function_suffix', None) # '' is legal value, None=unset + if 'function_suffix' in kwargs: + self.function_suffix = kwargs['function_suffix'] if self.function_suffix is None: # YAML turns blanks strings into None # mark as explicitly set to empty @@ -496,27 +496,27 @@ def __init__(self, parent, parentoptions=None, options=None, **node): # Mark as unset self.function_suffix = None - if 'cpp_template' in node: - template_types = node['cpp_template'].keys() + if 'cpp_template' in kwargs: + template_types = kwargs['cpp_template'].keys() else: template_types = [] - if 'decl' in node: + if 'decl' in kwargs: # parse decl and add to dictionary if isinstance(parent,ClassNode): cls_name = parent.name else: cls_name = None - self.decl = node['decl'] - ast = declast.check_decl(node['decl'], + self.decl = kwargs['decl'] + ast = declast.check_decl(kwargs['decl'], current_class=cls_name, template_types=template_types) self._ast = ast # add any attributes from YAML files to the ast - if 'attrs' in node: - attrs = node['attrs'] + if 'attrs' in kwargs: + attrs = kwargs['attrs'] if 'result' in attrs: ast.attrs.update(attrs['result']) for arg in ast.params: @@ -527,18 +527,18 @@ def __init__(self, parent, parentoptions=None, options=None, **node): else: raise RuntimeError("Missing decl") - if ('function_suffix' in node and - node['function_suffix'] is None): + if ('function_suffix' in kwargs and + kwargs['function_suffix'] is None): # YAML turns blanks strings into None - node['function_suffix'] = '' - if 'default_arg_suffix' in node: - default_arg_suffix = node['default_arg_suffix'] + kwargs['function_suffix'] = '' + if 'default_arg_suffix' in kwargs: + default_arg_suffix = kwargs['default_arg_suffix'] if not isinstance(default_arg_suffix, list): raise RuntimeError('default_arg_suffix must be a list') - for i, value in enumerate(node['default_arg_suffix']): + for i, value in enumerate(kwargs['default_arg_suffix']): if value is None: # YAML turns blanks strings to None - node['default_arg_suffix'][i] = '' + kwargs['default_arg_suffix'][i] = '' # XXX - do some error checks on ast # if 'name' not in result: @@ -587,7 +587,7 @@ def _to_dict(self): d[key] = value return d - def add_function(self, options=None, **node): + def add_function(self, options=None, **kwargs): # inherited from AstNode raise RuntimeError("Cannot add a function to a FunctionNode") From 7e8547f584fb5257359bc8f6699d263fe9211d84 Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Tue, 9 Jan 2018 19:06:32 -0800 Subject: [PATCH 04/19] Remove old initialization --- shroud/main.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/shroud/main.py b/shroud/main.py index bcf6a95cb..71851f44c 100644 --- a/shroud/main.py +++ b/shroud/main.py @@ -972,12 +972,7 @@ def main_with_args(args): config.ffiles = [] # list of Fortran files created # accumulated input - all = dict( - library='default_library', - cpp_header='', - namespace='', - language='c++', - ) + all = {} splicers = dict(c={}, f={}, py={}, lua={}) for filename in args.filename: From ac8c4d936f43e91a7d5a8f12b26364dc32d1cf2f Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Tue, 9 Jan 2018 19:36:58 -0800 Subject: [PATCH 05/19] Start adding keyword arguments --- shroud/ast.py | 131 +++++++++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 61 deletions(-) diff --git a/shroud/ast.py b/shroud/ast.py index c86398b85..479a8064d 100644 --- a/shroud/ast.py +++ b/shroud/ast.py @@ -79,14 +79,19 @@ def eval_template(self, name, tname='', fmt=None): def add_function(self, parentoptions=None, **kwargs): """Add a function. """ - fcnnode = FunctionNode(self, parentoptions, **kwargs) + fcnnode = FunctionNode(self, parentoptions=parentoptions, **kwargs) self.functions.append(fcnnode) return fcnnode ###################################################################### class LibraryNode(AstNode): - def __init__(self, options=None, **kwargs): + def __init__(self, + cpp_header='', + library='default_library', + namespace='', + options=None, + **kwargs): """Create LibraryNode. fields = value @@ -95,21 +100,22 @@ def __init__(self, options=None, **kwargs): functions: """ + # From arguments + self.cpp_header = cpp_header + self.library = library + self.namespace = namespace self.classes = [] - self.cpp_header = '' self.functions = [] # Each is given a _function_index when created. self.function_index = [] self.language = 'c++' # input language: c or c++ - self.namespace = '' self.options = self.default_options() if options: self.options.update(options, replace=True) self.F_module_dependencies = [] # unused - self.library = kwargs.get('library', 'default_library') self.copyright = kwargs.setdefault('copyright', []) self.patterns = kwargs.setdefault('patterns', []) @@ -126,7 +132,7 @@ def __init__(self, options=None, **kwargs): raise RuntimeError("language must be 'c' or 'c++'") self.language = kwargs['language'] - self.default_format(kwargs) + self.default_format() self.option_to_fmt() # default some options based on other options @@ -137,15 +143,6 @@ def __init__(self, options=None, **kwargs): self.eval_template('F_module_name', '_library') self.eval_template('F_impl_filename', '_library') - # default cpp_header to blank - if 'cpp_header' in kwargs and kwargs['cpp_header']: - # YAML treats blank string as None - self.cpp_header = kwargs['cpp_header'] - - if 'namespace' in kwargs and kwargs['namespace']: - # YAML treats blank string as None - self.namespace = kwargs['namespace'] - def default_options(self): """default options.""" def_options = util.Options( @@ -233,26 +230,23 @@ def default_options(self): ) return def_options - def default_format(self, node): + def default_format(self): """Set format dictionary. """ self._fmt = util.Options(None) fmt_library = self._fmt - if 'library' in node: - fmt_library.library = node['library'] - else: - fmt_library.library = 'default_library' + fmt_library.library = self.library fmt_library.library_lower = fmt_library.library.lower() fmt_library.library_upper = fmt_library.library.upper() fmt_library.function_suffix = '' # assume no suffix fmt_library.C_prefix = self.options.get( 'C_prefix', fmt_library.library_upper[:3] + '_') fmt_library.F_C_prefix = self.options['F_C_prefix'] - if 'namespace' in node and node['namespace']: + if self.namespace: fmt_library.namespace_scope = ( - '::'.join(node['namespace'].split()) + '::') + '::'.join(self.namespace.split()) + '::') else: fmt_library.namespace_scope = '' @@ -335,20 +329,20 @@ def _to_dict(self): ###################################################################### class ClassNode(AstNode): - def __init__(self, name, parent, options=None, **kwargs): + def __init__(self, name, parent, + cpp_header='', + namespace='', + options=None, + **kwargs): + """Create ClassNode. + """ + # From arguments self.name = name - self.functions = [] - self.cpp_header = '' + self.cpp_header = cpp_header + self.namespace = namespace - # default cpp_header to blank - if 'cpp_header' in kwargs and kwargs['cpp_header']: - # YAML treats blank string as None - self.cpp_header = kwargs['cpp_header'] + self.functions = [] - self.namespace = '' - if 'namespace' in kwargs and kwargs['namespace']: - # YAML treats blank string as None - self.namespace = kwargs['namespace'] self.python = kwargs.get('python', {}) for n in ['C_header_filename', 'C_impl_filename', @@ -362,7 +356,6 @@ def __init__(self, name, parent, options=None, **kwargs): self.options = util.Options(parent=parent.options) if options: self.options.update(options, replace=True) - options = self.options self._fmt = util.Options(parent._fmt) fmt_class = self._fmt @@ -375,7 +368,7 @@ def __init__(self, name, parent, options=None, **kwargs): self.eval_template('C_header_filename', '_class') self.eval_template('C_impl_filename', '_class') - if options.F_module_per_class: + if self.options.F_module_per_class: self.eval_template('F_module_name', '_class') self.eval_template('F_impl_filename', '_class') @@ -427,7 +420,11 @@ class FunctionNode(AstNode): } """ - def __init__(self, parent, parentoptions=None, options=None, **kwargs): + def __init__(self, parent, + decl=None, + parentoptions=None, + options=None, + **kwargs): self.options = util.Options(parent= parentoptions or parent.options) if options: self.options.update(options, replace=True) @@ -501,32 +498,32 @@ def __init__(self, parent, parentoptions=None, options=None, **kwargs): else: template_types = [] - if 'decl' in kwargs: - # parse decl and add to dictionary - if isinstance(parent,ClassNode): - cls_name = parent.name - else: - cls_name = None - - self.decl = kwargs['decl'] - ast = declast.check_decl(kwargs['decl'], - current_class=cls_name, - template_types=template_types) - self._ast = ast - - # add any attributes from YAML files to the ast - if 'attrs' in kwargs: - attrs = kwargs['attrs'] - if 'result' in attrs: - ast.attrs.update(attrs['result']) - for arg in ast.params: - name = arg.name - if name in attrs: - arg.attrs.update(attrs[name]) - # XXX - waring about unused fields in attrs - else: + if not decl: raise RuntimeError("Missing decl") - + + # parse decl and add to dictionary + if isinstance(parent,ClassNode): + cls_name = parent.name + else: + cls_name = None + + self.decl = decl + ast = declast.check_decl(decl, + current_class=cls_name, + template_types=template_types) + self._ast = ast + + # add any attributes from YAML files to the ast + if 'attrs' in kwargs: + attrs = kwargs['attrs'] + if 'result' in attrs: + ast.attrs.update(attrs['result']) + for arg in ast.params: + name = arg.name + if name in attrs: + arg.attrs.update(attrs[name]) + # XXX - waring about unused fields in attrs + if ('function_suffix' in kwargs and kwargs['function_suffix'] is None): # YAML turns blanks strings into None @@ -592,6 +589,14 @@ def add_function(self, options=None, **kwargs): raise RuntimeError("Cannot add a function to a FunctionNode") +def clean_dictionary(dd): + """YAML converts some blank fields to None, + but we want blank. + """ + for key in ['cpp_header', 'namespace']: + if key in dd and dd[key] is None: + dd[key] = '' + def check_options_only(node, parent): """Process an options only entry in a list. @@ -643,6 +648,7 @@ def add_functions(parent, functions): for func in functions: only, options = check_options_only(func, options) if not only: + clean_dictionary(func) parent.add_function(parentoptions=options, **func) def create_library_from_dictionary(node): @@ -658,6 +664,8 @@ def create_library_from_dictionary(node): Do some checking on the input. Every class must have a name. """ + + clean_dictionary(node) library = LibraryNode(**node) if 'classes' in node: @@ -672,6 +680,7 @@ def create_library_from_dictionary(node): raise TypeError("classes[n] must be a dictionary") if 'name' not in cls: raise TypeError("class does not define name") + clean_dictionary(cls) declast.add_type(cls['name']) for cls in classes: From c160a19a0fd9249963b4bf87aec4b774aa03af0e Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Tue, 9 Jan 2018 19:46:02 -0800 Subject: [PATCH 06/19] Cleanup FunctionNode --- shroud/ast.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/shroud/ast.py b/shroud/ast.py index 479a8064d..0f18953c7 100644 --- a/shroud/ast.py +++ b/shroud/ast.py @@ -431,7 +431,6 @@ def __init__(self, parent, self._fmt = util.Options(parent._fmt) self.option_to_fmt() - fmt_func = self._fmt # working variables self._CPP_return_templated = False @@ -457,11 +456,13 @@ def __init__(self, parent, # Move fields from kwargs into instance for n in [ - 'C_error_pattern', 'C_name', + 'C_code', 'C_error_pattern', 'C_name', 'C_post_call', 'C_post_call_buf', - 'F_name_function', + 'C_return_code', 'C_return_type', + 'F_C_name', 'F_code', + 'F_name_function', 'F_name_generic', 'F_name_impl', 'LUA_name', 'LUA_name_impl', - 'PY_name_impl' ]: + 'PY_error_pattern', 'PY_name_impl' ]: setattr(self, n, kwargs.get(n, None)) self.default_arg_suffix = kwargs.get('default_arg_suffix', []) @@ -471,16 +472,8 @@ def __init__(self, parent, self.fortran_generic = kwargs.get('fortran_generic', {}) self.return_this = kwargs.get('return_this', False) - self.F_C_name = kwargs.get('F_C_name', None) - self.F_name_generic = kwargs.get('F_name_generic', None) - self.F_name_impl = kwargs.get('F_name_impl', None) - self.PY_error_pattern = kwargs.get('PY_error_pattern', '') - # referenced explicity (not via fmt) - self.C_code = kwargs.get('C_code', None) - self.C_return_code = kwargs.get('C_return_code', None) - self.C_return_type = kwargs.get('C_return_type', None) - self.F_code = kwargs.get('F_code', None) + # C_code, C_return_code, C_return_type, F_code # self.function_suffix = kwargs.get('function_suffix', None) # '' is legal value, None=unset if 'function_suffix' in kwargs: @@ -493,11 +486,6 @@ def __init__(self, parent, # Mark as unset self.function_suffix = None - if 'cpp_template' in kwargs: - template_types = kwargs['cpp_template'].keys() - else: - template_types = [] - if not decl: raise RuntimeError("Missing decl") @@ -506,6 +494,7 @@ def __init__(self, parent, cls_name = parent.name else: cls_name = None + template_types = self.cpp_template.keys() self.decl = decl ast = declast.check_decl(decl, @@ -546,6 +535,7 @@ def __init__(self, parent, if ast.params is None: raise RuntimeError("Missing arguments:", ast.gen_decl()) + fmt_func = self._fmt fmt_func.function_name = ast.name fmt_func.underscore_name = util.un_camel(fmt_func.function_name) From aa983563db77d6326d532cf5d253f33a187722ee Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Tue, 9 Jan 2018 19:54:33 -0800 Subject: [PATCH 07/19] Cleanup FunctionNode --- shroud/ast.py | 48 ++++++++++++++---------------------------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/shroud/ast.py b/shroud/ast.py index 0f18953c7..2376da9a8 100644 --- a/shroud/ast.py +++ b/shroud/ast.py @@ -462,30 +462,18 @@ def __init__(self, parent, 'F_C_name', 'F_code', 'F_name_function', 'F_name_generic', 'F_name_impl', 'LUA_name', 'LUA_name_impl', - 'PY_error_pattern', 'PY_name_impl' ]: + 'PY_error_pattern', 'PY_name_impl', + 'docs', 'function_suffix', 'return_this']: setattr(self, n, kwargs.get(n, None)) self.default_arg_suffix = kwargs.get('default_arg_suffix', []) - self.docs = kwargs.get('docs', '') self.cpp_template = kwargs.get('cpp_template', {}) self.doxygen = kwargs.get('doxygen', {}) self.fortran_generic = kwargs.get('fortran_generic', {}) - self.return_this = kwargs.get('return_this', False) # referenced explicity (not via fmt) # C_code, C_return_code, C_return_type, F_code -# self.function_suffix = kwargs.get('function_suffix', None) # '' is legal value, None=unset - if 'function_suffix' in kwargs: - self.function_suffix = kwargs['function_suffix'] - if self.function_suffix is None: - # YAML turns blanks strings into None - # mark as explicitly set to empty - self.function_suffix = '' - else: - # Mark as unset - self.function_suffix = None - if not decl: raise RuntimeError("Missing decl") @@ -513,26 +501,8 @@ def __init__(self, parent, arg.attrs.update(attrs[name]) # XXX - waring about unused fields in attrs - if ('function_suffix' in kwargs and - kwargs['function_suffix'] is None): - # YAML turns blanks strings into None - kwargs['function_suffix'] = '' - if 'default_arg_suffix' in kwargs: - default_arg_suffix = kwargs['default_arg_suffix'] - if not isinstance(default_arg_suffix, list): - raise RuntimeError('default_arg_suffix must be a list') - for i, value in enumerate(kwargs['default_arg_suffix']): - if value is None: - # YAML turns blanks strings to None - kwargs['default_arg_suffix'][i] = '' - -# XXX - do some error checks on ast -# if 'name' not in result: -# raise RuntimeError("Missing result.name") -# if 'type' not in result: -# raise RuntimeError("Missing result.type") - if ast.params is None: + # 'void foo' instead of 'void foo()' raise RuntimeError("Missing arguments:", ast.gen_decl()) fmt_func = self._fmt @@ -583,10 +553,20 @@ def clean_dictionary(dd): """YAML converts some blank fields to None, but we want blank. """ - for key in ['cpp_header', 'namespace']: + for key in ['cpp_header', 'namespace', + 'function_suffix']: if key in dd and dd[key] is None: dd[key] = '' + if 'default_arg_suffix' in dd: + default_arg_suffix = dd['default_arg_suffix'] + if not isinstance(default_arg_suffix, list): + raise RuntimeError('default_arg_suffix must be a list') + for i, value in enumerate(dd['default_arg_suffix']): + if value is None: + dd['default_arg_suffix'][i] = '' + + def check_options_only(node, parent): """Process an options only entry in a list. From 2f19428ce39f84dfa6b1eec8e6da919bb5ed8e13 Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Tue, 9 Jan 2018 20:00:29 -0800 Subject: [PATCH 08/19] Remove attrs from json output This field just duplicates the YAML input. It's values have been copied into _ast. --- shroud/ast.py | 5 +---- tests/example/example.json | 30 ------------------------------ 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/shroud/ast.py b/shroud/ast.py index 2376da9a8..1521d8a9d 100644 --- a/shroud/ast.py +++ b/shroud/ast.py @@ -451,9 +451,6 @@ def __init__(self, parent, # self.function_index = [] - # Only needed for json diff - self.attrs = kwargs.get('attrs', None) - # Move fields from kwargs into instance for n in [ 'C_code', 'C_error_pattern', 'C_name', @@ -520,7 +517,7 @@ def _to_dict(self): decl=self.decl, options=self.options, ) - for key in ['attrs', 'cpp_template', 'default_arg_suffix', 'docs', 'doxygen', + for key in ['cpp_template', 'default_arg_suffix', 'docs', 'doxygen', 'fortran_generic', 'return_this', 'C_code', 'C_error_pattern', 'C_name', 'C_post_call', 'C_post_call_buf', diff --git a/tests/example/example.json b/tests/example/example.json index 0ba873262..6d859257d 100644 --- a/tests/example/example.json +++ b/tests/example/example.json @@ -526,11 +526,6 @@ }, "_function_index": 4, "_subprogram": "function", - "attrs": { - "result": { - "len": "aa_exclass1_get_name_length({F_this}%{F_derived_member})" - } - }, "decl": "const string& getName() const", "options": {} }, @@ -618,11 +613,6 @@ "_function_index": 15, "_generated": "arg_to_buffer", "_subprogram": "subroutine", - "attrs": { - "result": { - "len": "aa_exclass1_get_name_length({F_this}%{F_derived_member})" - } - }, "decl": "const string& getName() const", "options": { "wrap_c": true, @@ -1603,11 +1593,6 @@ }, "_function_index": 19, "_subprogram": "function", - "attrs": { - "name": { - "random": 2 - } - }, "decl": "ExClass2(const string *name)", "doxygen": { "brief": "constructor" @@ -1690,11 +1675,6 @@ "_function_index": 39, "_generated": "arg_to_buffer", "_subprogram": "function", - "attrs": { - "name": { - "random": 2 - } - }, "decl": "ExClass2(const string *name)", "doxygen": { "brief": "constructor" @@ -1817,11 +1797,6 @@ }, "_function_index": 21, "_subprogram": "function", - "attrs": { - "result": { - "len": "aa_exclass2_get_name_length({F_this}%{F_derived_member})" - } - }, "decl": "const string& getName() const", "options": {} }, @@ -1907,11 +1882,6 @@ "_function_index": 40, "_generated": "arg_to_buffer", "_subprogram": "subroutine", - "attrs": { - "result": { - "len": "aa_exclass2_get_name_length({F_this}%{F_derived_member})" - } - }, "decl": "const string& getName() const", "options": { "wrap_c": true, From 2bc8fa214f62dbbfba495b286547ab1d28a33ca4 Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Tue, 9 Jan 2018 20:11:48 -0800 Subject: [PATCH 09/19] Refactor add_functions --- shroud/ast.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/shroud/ast.py b/shroud/ast.py index 1521d8a9d..7f88790f4 100644 --- a/shroud/ast.py +++ b/shroud/ast.py @@ -564,8 +564,8 @@ def clean_dictionary(dd): dd['default_arg_suffix'][i] = '' -def check_options_only(node, parent): - """Process an options only entry in a list. +def is_options_only(node): + """Detect an options only node. functions: - options: @@ -574,23 +574,14 @@ def check_options_only(node, parent): options: Return True if node only has options. - Return Options instance to use. - node is assumed to be a dictionary. - Update current set of options from node['options']. """ if len(node) != 1: - return False, parent + return False if 'options' not in node: - return False, parent - options = node['options'] - if not options: - return False, parent - if not isinstance(options, dict): + return False + if not isinstance(node['options'], dict): raise TypeError("options must be a dictionary") - - new = util.Options(parent=parent) - new.update(node['options']) - return True, new + return True def add_functions(parent, functions): """ Add functions from list 'functions'. @@ -612,11 +603,12 @@ def add_functions(parent, functions): raise TypeError("functions must be a list") options = parent.options - for func in functions: - only, options = check_options_only(func, options) - if not only: - clean_dictionary(func) - parent.add_function(parentoptions=options, **func) + for node in functions: + if is_options_only(node): + options = util.Options(options, **node['options']) + else: + clean_dictionary(node) + parent.add_function(parentoptions=options, **node) def create_library_from_dictionary(node): """Create a library and add classes and functions from node. From 535288cc6795d302712f30e2536ef827cb523b97 Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Tue, 9 Jan 2018 20:20:59 -0800 Subject: [PATCH 10/19] distribute add_function --- CHANGELOG.md | 4 ++++ shroud/ast.py | 36 ++++++++++++++++++------------------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc5de83ba..87e10519c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Improved API for creating LibraryNode instances by using keyword arguments. +- Factored out routine create_library_from_dictionary for dealing with YAML + generated dictionary. ## v0.5.0 - 2018-01-09 ### Added diff --git a/shroud/ast.py b/shroud/ast.py index 7f88790f4..f2d181b2e 100644 --- a/shroud/ast.py +++ b/shroud/ast.py @@ -76,18 +76,12 @@ def eval_template(self, name, tname='', fmt=None): tname = name + tname + '_template' setattr(fmt, name, util.wformat(self.options[tname], fmt)) - def add_function(self, parentoptions=None, **kwargs): - """Add a function. - """ - fcnnode = FunctionNode(self, parentoptions=parentoptions, **kwargs) - self.functions.append(fcnnode) - return fcnnode - ###################################################################### class LibraryNode(AstNode): def __init__(self, cpp_header='', + language='c++', library='default_library', namespace='', options=None, @@ -102,6 +96,9 @@ def __init__(self, """ # From arguments self.cpp_header = cpp_header + self.language = language.lower() + if self.language not in ['c', 'c++']: + raise RuntimeError("language must be 'c' or 'c++'") self.library = library self.namespace = namespace @@ -109,7 +106,6 @@ def __init__(self, self.functions = [] # Each is given a _function_index when created. self.function_index = [] - self.language = 'c++' # input language: c or c++ self.options = self.default_options() if options: self.options.update(options, replace=True) @@ -126,12 +122,6 @@ def __init__(self, 'YAML_type_filename']: setattr(self, n, kwargs.get(n, None)) - if 'language' in kwargs: - language = kwargs['language'].lower() - if language not in ['c', 'c++']: - raise RuntimeError("language must be 'c' or 'c++'") - self.language = kwargs['language'] - self.default_format() self.option_to_fmt() @@ -302,6 +292,13 @@ def default_format(self): fmt_library.stdlib = 'std::' + def add_function(self, parentoptions=None, **kwargs): + """Add a function. + """ + fcnnode = FunctionNode(self, parentoptions=parentoptions, **kwargs) + self.functions.append(fcnnode) + return fcnnode + def add_class(self, name, **kwargs): """Add a class. """ @@ -372,6 +369,13 @@ def __init__(self, name, parent, self.eval_template('F_module_name', '_class') self.eval_template('F_impl_filename', '_class') + def add_function(self, parentoptions=None, **kwargs): + """Add a function. + """ + fcnnode = FunctionNode(self, parentoptions=parentoptions, **kwargs) + self.functions.append(fcnnode) + return fcnnode + def _to_dict(self): """Convert to dictionary. Used by util.ExpandedEncoder. @@ -541,10 +545,6 @@ def _to_dict(self): d[key] = value return d - def add_function(self, options=None, **kwargs): - # inherited from AstNode - raise RuntimeError("Cannot add a function to a FunctionNode") - def clean_dictionary(dd): """YAML converts some blank fields to None, From 121048cf30486be71f362b28e1dbbc05fe60893b Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Tue, 9 Jan 2018 20:23:19 -0800 Subject: [PATCH 11/19] Tweeked CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87e10519c..881a0ba07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] -### Added +### Changed - Improved API for creating LibraryNode instances by using keyword arguments. - Factored out routine create_library_from_dictionary for dealing with YAML generated dictionary. From be86ea78ae719d46a588062b733ec656550d8f3d Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Wed, 10 Jan 2018 12:56:04 -0800 Subject: [PATCH 12/19] Add tests for API --- tests/test_ast.py | 46 ++++++++++++++-------------------------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/tests/test_ast.py b/tests/test_ast.py index ffbd49de0..fa35374bc 100644 --- a/tests/test_ast.py +++ b/tests/test_ast.py @@ -128,46 +128,28 @@ def test_b_function2(self): def test_c_class1(self): """Add a class to library""" - node = dict( - classes=[ - { - 'name': 'Class1', - } - ] - ) - library = ast.create_library_from_dictionary(node) + library = ast.LibraryNode() + library.add_class('Class1') self.assertEqual(len(library.classes), 1) def test_c_class2(self): - """Add a class to library""" - node = dict( - classes=[ - { - 'name': 'Class1', - 'methods': [ - { - 'decl': 'void c1func1()', - },{ - 'decl': 'void c1func2()', - } - ], - },{ - 'name': 'Class2', - 'methods': [ - { - 'decl': 'void c2func1()', - } - ], - }, - ], - ) - library = ast.create_library_from_dictionary(node) + """Add a classes with functions to library""" + library = ast.LibraryNode() + + cls1 = library.add_class('Class1') + cls1.add_function(decl='void c1func1()') + cls1.add_function(decl='void c1func2()') + + cls2 = library.add_class('Class2') + cls2.add_function(decl='void c2func1()') self.assertEqual(len(library.classes), 2) self.assertEqual(len(library.classes[0].functions), 2) + self.assertEqual(library.classes[0].functions[0]._ast.name, 'c1func1') + self.assertEqual(library.classes[0].functions[1]._ast.name, 'c1func2') self.assertEqual(len(library.classes[1].functions), 1) - + self.assertEqual(library.classes[1].functions[0]._ast.name, 'c2func1') def test_c_class2(self): """Test class options""" From 09258eff93d64434a12c0d51c923df4f9989a86b Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Wed, 10 Jan 2018 15:16:46 -0800 Subject: [PATCH 13/19] Create types from create_library_from_dictionary --- shroud/ast.py | 26 ++++++++++++++++++++++++++ shroud/main.py | 26 +------------------------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/shroud/ast.py b/shroud/ast.py index f2d181b2e..c26a58cfa 100644 --- a/shroud/ast.py +++ b/shroud/ast.py @@ -43,6 +43,7 @@ from . import util from . import declast +from . import typemap class AstNode(object): def option_to_fmt(self): @@ -624,6 +625,31 @@ def create_library_from_dictionary(node): Every class must have a name. """ + if 'types' in node: + types_dict = node['types'] + if not isinstance(types_dict, dict): + raise TypeError("types must be a dictionary") + def_types, def_types_alias = typemap.Typedef.get_global_types() + for key, value in types_dict.items(): + if not isinstance(value, dict): + raise TypeError("types '%s' must be a dictionary" % key) + declast.add_type(key) # Add to parser + + if 'typedef' in value: + copy_type = value['typedef'] + orig = def_types.get(copy_type, None) + if not orig: + raise RuntimeError( + "No type for typedef {}".format(copy_type)) + def_types[key] = typemap.Typedef(key) + def_types[key].update(def_types[copy_type]._to_dict()) + + if key in def_types: + def_types[key].update(value) + else: + def_types[key] = typemap.Typedef(key, **value) + typemap.typedef_wrapped_defaults(def_types[key]) + clean_dictionary(node) library = LibraryNode(**node) diff --git a/shroud/main.py b/shroud/main.py index 71851f44c..dda47a651 100644 --- a/shroud/main.py +++ b/shroud/main.py @@ -102,36 +102,12 @@ def check_schema(self): def_types, def_types_alias = typemap.initialize() declast.add_typemap() - # Write out as YAML if requested + # Write out native types as YAML if requested if self.config.yaml_types: with open(os.path.join(self.config.yaml_dir, self.config.yaml_types), 'w') as yaml_file: yaml.dump(def_types, yaml_file, default_flow_style=False) print("Wrote", self.config.yaml_types) - if 'types' in node: - types_dict = node['types'] - if not isinstance(types_dict, dict): - raise TypeError("types must be a dictionary") - for key, value in types_dict.items(): - if not isinstance(value, dict): - raise TypeError("types '%s' must be a dictionary" % key) - declast.add_type(key) # Add to parser - - if 'typedef' in value: - copy_type = value['typedef'] - orig = def_types.get(copy_type, None) - if not orig: - raise RuntimeError( - "No type for typedef {}".format(copy_type)) - def_types[key] = typemap.Typedef(key) - def_types[key].update(def_types[copy_type]._to_dict()) - - if key in def_types: - def_types[key].update(value) - else: - def_types[key] = typemap.Typedef(key, **value) - typemap.typedef_wrapped_defaults(def_types[key]) - newlibrary = ast.create_library_from_dictionary(node) return newlibrary From b2531772623e94540a5553b620a1e05ac380438d Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Wed, 10 Jan 2018 15:33:09 -0800 Subject: [PATCH 14/19] Move VerifyAttrs, GenFunctions, and Namify into generate.py --- shroud/generate.py | 723 +++++++++++++++++++++++++++++++++++++++++++++ shroud/main.py | 677 +----------------------------------------- 2 files changed, 725 insertions(+), 675 deletions(-) create mode 100644 shroud/generate.py diff --git a/shroud/generate.py b/shroud/generate.py new file mode 100644 index 000000000..eb5530be0 --- /dev/null +++ b/shroud/generate.py @@ -0,0 +1,723 @@ +# Copyright (c) 2017, Lawrence Livermore National Security, LLC. +# Produced at the Lawrence Livermore National Laboratory +# +# LLNL-CODE-738041. +# All rights reserved. +# +# This file is part of Shroud. For details, see +# https://github.com/LLNL/shroud. Please also read shroud/LICENSE. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the disclaimer below. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the disclaimer (as noted below) +# in the documentation and/or other materials provided with the +# distribution. +# +# * Neither the name of the LLNS/LLNL nor the names of its contributors +# may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL LAWRENCE +# LIVERMORE NATIONAL SECURITY, LLC, THE U.S. DEPARTMENT OF ENERGY OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +######################################################################## +""" +Generate additional functions required to create wrappers. +""" + +from . import typemap +from . import util # copy function node + + +class VerifyAttrs(object): + """ + Check attributes and set some defaults. + Generate types for classes. + """ + def __init__(self, newlibrary, config): + self.newlibrary = newlibrary + self.config = config + + def verify_attrs(self): + newlibrary = self.newlibrary + + for cls in newlibrary.classes: + typemap.create_class_typedef(cls) + + for cls in newlibrary.classes: + for func in cls.functions: + self.check_arg_attrs(func) + + for func in newlibrary.functions: + self.check_arg_attrs(func) + + def check_arg_attrs(self, node): + """Regularize attributes + intent: lower case, no parens, must be in, out, or inout + value: if pointer, default to False (pass-by-reference; + else True (pass-by-value). + """ + options = node.options + if not options.wrap_fortran and not options.wrap_c: + return + + # cache subprogram type + ast = node._ast + result_type = ast.typename + result_is_ptr = ast.is_pointer() + # 'void'=subroutine 'void *'=function + if result_type == 'void' and not result_is_ptr: + node._subprogram = 'subroutine' + else: + node._subprogram = 'function' + + found_default = False + for arg in ast.params: + argname = arg.name + argtype = arg.typename + typedef = typemap.Typedef.lookup(argtype) + if typedef is None: + # if the type does not exist, make sure it is defined by cpp_template + #- decl: void Function7(ArgType arg) + # cpp_template: + # ArgType: + # - int + # - double + if argtype not in node.cpp_template: + raise RuntimeError("No such type %s: %s" % ( + argtype, arg.gen_decl())) + + is_ptr = arg.is_indirect() + attrs = arg.attrs + + # intent + intent = attrs.get('intent', None) + if intent is None: + if not is_ptr: + attrs['intent'] = 'in' + elif arg.const: + attrs['intent'] = 'in' + elif typedef.base == 'string': + attrs['intent'] = 'inout' + elif typedef.base == 'vector': + attrs['intent'] = 'inout' + else: + # void * + attrs['intent'] = 'in' # XXX must coordinate with VALUE + else: + intent = intent.lower() + if intent in ['in', 'out', 'inout']: + attrs['intent'] = intent + else: + raise RuntimeError( + "Bad value for intent: " + attrs['intent']) + + # value + value = attrs.get('value', None) + if value is None: + if is_ptr: + if (typedef.f_c_type or typedef.f_type) == 'type(C_PTR)': + # This causes Fortran to dereference the C_PTR + # Otherwise a void * argument becomes void ** + attrs['value'] = True + else: + attrs['value'] = False + else: + attrs['value'] = True + + # dimension + dimension = attrs.get('dimension', None) + if dimension: + if attrs.get('value', False): + raise RuntimeError("argument must not have value=True") + if not is_ptr: + raise RuntimeError("dimension attribute can only be " + "used on pointer and references") + if dimension is True: + # No value was provided, provide default + attrs['dimension'] = '(*)' + else: + # Put parens around dimension + attrs['dimension'] = '(' + attrs['dimension'] + ')' + elif typedef and typedef.base == 'vector': + # default to 1-d assumed shape + attrs['dimension'] = '(:)' + + if arg.init is not None: + found_default = True + node._has_default_arg = True + elif found_default is True: + raise RuntimeError("Expected default value for %s" % argname) + + # compute argument names for some attributes + # XXX make sure they don't conflict with other names + len_name = attrs.get('len', False) + if len_name is True: + attrs['len'] = options.C_var_len_template.format(c_var=argname) + len_name = attrs.get('len_trim', False) + if len_name is True: + attrs['len_trim'] = options.C_var_trim_template.format(c_var=argname) + size_name = attrs.get('size', False) + if size_name is True: + attrs['size'] = options.C_var_size_template.format(c_var=argname) + + # Check template attribute + temp = attrs.get('template', None) + if typedef and typedef.base == 'vector': + if not temp: + raise RuntimeError("std::vector must have template argument: %s" % ( + arg.gen_decl())) + typedef = typemap.Typedef.lookup(temp) + if typedef is None: + raise RuntimeError("No such type %s for template: %s" % ( + temp, arg.gen_decl())) + elif temp is not None: + raise RuntimeError("Type '%s' may not supply template argument: %s" % ( + argtype, arg.gen_decl())) + + +class GenFunctions(object): + """ + Generate types from class. + Generate functions based on overload/template/generic/attributes + Computes fmt.function_suffix. + """ + + def __init__(self, newlibrary, config): + self.newlibrary = newlibrary + self.config = config + + def gen_library(self): + """Entry routine to generate functions for a library. + """ + newlibrary = self.newlibrary + + self.function_index = newlibrary.function_index + + for cls in newlibrary.classes: + cls.functions = self.define_function_suffix(cls.functions) + newlibrary.functions = self.define_function_suffix(newlibrary.functions) + +# No longer need this, but keep code for now in case some other dependency checking is needed +# for cls in newlibrary.classes: +# self.check_class_dependencies(cls) + + def append_function_index(self, node): + """append to function_index, set index into node. + """ + ilist = self.function_index + node._function_index = len(ilist) +# node._fmt.function_index = str(len(ilist)) # debugging + ilist.append(node) + + def define_function_suffix(self, functions): + """ + Return a new list with generated function inserted. + """ + + # Look for overloaded functions + cpp_overload = {} + for function in functions: + if function.function_suffix is not None: + function._fmt.function_suffix = function.function_suffix + self.append_function_index(function) + cpp_overload. \ + setdefault(function._ast.name, []). \ + append(function._function_index) + + # keep track of which function are overloaded in C++. + for key, value in cpp_overload.items(): + if len(value) > 1: + for index in value: + self.function_index[index]._cpp_overload = value + + # Create additional functions needed for wrapping + ordered_functions = [] + for method in functions: + if method._has_default_arg: + self.has_default_args(method, ordered_functions) + ordered_functions.append(method) + if method.cpp_template: + method._overloaded = True + self.template_function(method, ordered_functions) + + # Look for overloaded functions + overloaded_functions = {} + for function in ordered_functions: + # if not function.options.wrap_c: + # continue + if function.cpp_template: + continue + overloaded_functions.setdefault( + function._ast.name, []).append(function) + + # look for function overload and compute function_suffix + for mname, overloads in overloaded_functions.items(): + if len(overloads) > 1: + for i, function in enumerate(overloads): + function._overloaded = True + if not function._fmt.inlocal('function_suffix'): + function._fmt.function_suffix = '_{}'.format(i) + + # Create additional C bufferify functions. + ordered3 = [] + for method in ordered_functions: + ordered3.append(method) + self.arg_to_buffer(method, ordered3) + + # Create multiple generic Fortran wrappers to call a + # single C functions + ordered4 = [] + for method in ordered3: + ordered4.append(method) + if not method.options.wrap_fortran: + continue + if method.fortran_generic: + method._overloaded = True + self.generic_function(method, ordered4) + + self.gen_functions_decl(ordered4) + + return ordered4 + + def template_function(self, node, ordered_functions): + """ Create overloaded functions for each templated argument. + """ + if len(node.cpp_template) != 1: + # In the future it may be useful to have multiple templates + # That the would start creating more permutations + raise NotImplementedError("Only one cpp_templated type for now") + for typename, types in node.cpp_template.items(): + for type in types: + new = util.copy_function_node(node) + ordered_functions.append(new) + self.append_function_index(new) + + new._generated = 'cpp_template' + fmt = new._fmt + fmt.function_suffix = fmt.function_suffix + '_' + type + new.cpp_template = {} + options = new.options + options.wrap_c = True + options.wrap_fortran = True + options.wrap_python = False + options.wrap_lua = False + # Convert typename to type + fmt.CPP_template = '<{}>'.format(type) + if new._ast.typename == typename: + new._ast.typename = type + new._CPP_return_templated = True + for arg in new._ast.params: + if arg.typename == typename: + arg.typename = type + + # Do not process templated node, instead process + # generated functions above. + options = node.options + options.wrap_c = False + options.wrap_fortran = False + options.wrap_python = False + options.wrap_lua = False + + def generic_function(self, node, ordered_functions): + """ Create overloaded functions for each generic method. + """ + if len(node.fortran_generic) != 1: + # In the future it may be useful to have multiple generic arguments + # That the would start creating more permutations + raise NotImplemented("Only one generic arg for now") + for argname, types in node.fortran_generic.items(): + for type in types: + new = util.copy_function_node(node) + ordered_functions.append(new) + self.append_function_index(new) + + new._generated = 'fortran_generic' + new._PTR_F_C_index = node._function_index + fmt = new._fmt + # XXX append to existing suffix + fmt.function_suffix = fmt.function_suffix + '_' + type + new.fortran_generic = {} + options = new.options + options.wrap_c = False + options.wrap_fortran = True + options.wrap_python = False + options.wrap_lua = False + # Convert typename to type + for arg in new._ast.params: + if arg.name == argname: + # Convert any typedef to native type with f_type + argtype = arg.typename + typedef = typemap.Typedef.lookup(argtype) + typedef = typemap.Typedef.lookup(typedef.f_type) + if not typedef.f_cast: + raise RuntimeError( + "unable to cast type {} in fortran_generic" + .format(argtype)) + arg.typename = type + + # Do not process templated node, instead process + # generated functions above. + options = node.options +# options.wrap_c = False + options.wrap_fortran = False +# options.wrap_python = False + + def has_default_args(self, node, ordered_functions): + """ + For each function which has a default argument, generate + a version for each possible call. + void func(int i = 0, int j = 0) + generates + void func() + void func(int i) + void func(int i, int j) + """ + default_funcs = [] + + default_arg_suffix = node.default_arg_suffix + ndefault = 0 + + min_args = 0 + for i, arg in enumerate(node._ast.params): + if arg.init is None: + min_args += 1 + continue + new = util.copy_function_node(node) + self.append_function_index(new) + new._generated = 'has_default_arg' + del new._ast.params[i:] # remove trailing arguments + new._has_default_arg = False + options = new.options + options.wrap_c = True + options.wrap_fortran = True + options.wrap_python = False + options.wrap_lua = False + fmt = new._fmt + try: + fmt.function_suffix = default_arg_suffix[ndefault] + except IndexError: + # XXX fmt.function_suffix = + # XXX fmt.function_suffix + '_nargs%d' % (i + 1) + pass + default_funcs.append(new._function_index) + ordered_functions.append(new) + ndefault += 1 + + # keep track of generated default value functions + node._default_funcs = default_funcs + node._nargs = (min_args, len(node._ast.params)) + # The last name calls with all arguments (the original decl) + try: + node._fmt.function_suffix = default_arg_suffix[ndefault] + except IndexError: + pass + + def arg_to_buffer(self, node, ordered_functions): + """Look for function which have implied arguments. + This includes functions with string or vector arguments. + If found then create a new C function that + will convert argument into a buffer and length. + """ + options = node.options + fmt = node._fmt + + # If a C++ function returns a std::string instance, + # the default wrapper will not compile since the wrapper + # will be declared as char. It will also want to return the + # c_str of a stack variable. Warn and turn off the wrapper. + ast = node._ast + result_type = ast.typename + result_typedef = typemap.Typedef.lookup(result_type) + # wrapped classes have not been added yet. + # Only care about string here. + attrs = ast.attrs + result_is_ptr = ast.is_indirect() + if result_typedef and result_typedef.base in ['string', 'vector'] and \ + result_type != 'char' and \ + not result_is_ptr: + options.wrap_c = False +# options.wrap_fortran = False + self.config.log.write("Skipping {}, unable to create C wrapper " + "for function returning {} instance" + " (must return a pointer or reference).\n" + .format(result_typedef.cpp_type, + ast.name)) + + if options.wrap_fortran is False: + return + if options.F_string_len_trim is False: # XXX what about vector + return + + # Is result or any argument a string? + has_implied_arg = False + for arg in ast.params: + argtype = arg.typename + typedef = typemap.Typedef.lookup(argtype) + if typedef.base == 'string': + is_ptr = arg.is_indirect() + if is_ptr: + has_implied_arg = True + else: + arg.typename = 'char_scalar' + elif typedef.base == 'vector': + has_implied_arg = True + + has_string_result = False + result_as_arg = '' # only applies to string functions + is_pure = ast.fattrs.get('pure', False) + if result_typedef.base == 'vector': + raise NotImplemented("vector result") + elif result_typedef.base == 'string': + if result_type == 'char' and not result_is_ptr: + # char functions cannot be wrapped directly in intel 15. + ast.typename = 'char_scalar' + has_string_result = True + result_as_arg = fmt.F_string_result_as_arg + result_name = result_as_arg or fmt.C_string_result_as_arg + + if not (has_string_result or has_implied_arg): + return + + # XXX options = node['options'] + # XXX options.wrap_fortran = False + # Preserve wrap_c. + # This keep a version which accepts char * arguments. + + # Create a new C function and change arguments + # to add len_trim attribute + C_new = util.copy_function_node(node) + ordered_functions.append(C_new) + self.append_function_index(C_new) + + C_new._generated = 'arg_to_buffer' + C_new._error_pattern_suffix = '_as_buffer' + fmt = C_new._fmt + fmt.function_suffix = fmt.function_suffix + options.C_bufferify_suffix + + options = C_new.options + options.wrap_c = True + options.wrap_fortran = False + options.wrap_python = False + options.wrap_lua = False + C_new._PTR_C_CPP_index = node._function_index + + newargs = [] + for arg in C_new._ast.params: + attrs = arg.attrs + argtype = arg.typename + arg_typedef = typemap.Typedef.lookup(argtype) + if arg_typedef.base == 'vector': + # Do not wrap the orignal C function with vector argument. + # Meaningless to call without the size argument. + # TODO: add an option where char** length is determined by looking + # for trailing NULL pointer. { "foo", "bar", NULL }; + node.options.wrap_c = False + node.options.wrap_python = False # NotImplemented + node.options.wrap_lua = False # NotImplemented + arg_typedef, c_statements = typemap.lookup_c_statements(arg) + + # set names for implied buffer arguments + stmts = 'intent_' + attrs['intent'] + '_buf' + intent_blk = c_statements.get(stmts, {}) + for buf_arg in intent_blk.get('buf_args', []): + if buf_arg in attrs: + # do not override user specified variable name + continue + if buf_arg == 'size': + attrs['size'] = options.C_var_size_template.format( + c_var=arg.name) + elif buf_arg == 'len_trim': + attrs['len_trim'] = options.C_var_trim_template.format( + c_var=arg.name) + elif buf_arg == 'len': + attrs['len'] = options.C_var_len_template.format( + c_var=arg.name) + + ## base typedef + + # Copy over some buffer specific fields to their generic name. + C_new.C_post_call = C_new.C_post_call_buf + + if has_string_result: + # Add additional argument to hold result + ast = C_new._ast + result_as_string = ast.result_as_arg(result_name) + attrs = result_as_string.attrs + attrs['len'] = options.C_var_len_template.format(c_var=result_name) + attrs['intent'] = 'out' + attrs['_is_result'] = True + # convert to subroutine + C_new._subprogram = 'subroutine' + + if is_pure: + # pure functions which return a string have result_pure defined. + pass + elif result_as_arg: + # Create Fortran function without bufferify function_suffix but + # with len attributes on string arguments. + F_new = util.copy_function_node(C_new) + ordered_functions.append(F_new) + self.append_function_index(F_new) + + # Fortran function should wrap the new C function + F_new._PTR_F_C_index = C_new._function_index + options = F_new.options + options.wrap_c = False + options.wrap_fortran = True + options.wrap_python = False + options.wrap_lua = False + # Do not add '_bufferify' + F_new._fmt.function_suffix = node._fmt.function_suffix + + # Do not wrap original function (does not have result argument) + node.options.wrap_fortran = False + else: + # Fortran function may call C subroutine if string result + node._PTR_F_C_index = C_new._function_index + + def XXXcheck_class_dependencies(self, node): + """ + Check used_types and find which header and module files + to use for this class + """ + # keep track of types which are used by methods arguments + used_types = {} + for method in node['methods']: + self.check_function_dependencies(method, used_types) + + modules = {} + for typ in used_types.values(): + if typ.f_module: + for mname, only in typ.f_module.items(): + module = modules.setdefault(mname, {}) + if only: # Empty list means no ONLY clause + for oname in only: + module[oname] = True + + # Always add C_PTR, needed for class F_derived_member + modules.setdefault('iso_c_binding', {})['C_PTR'] = True + + F_modules = [] # array of tuples ( name, (only1, only2) ) + for mname in sorted(modules): + F_modules.append((mname, sorted(modules[mname]))) + node.F_module_dependencies = F_modules + + def XXXcheck_function_dependencies(self, node, used_types): + """Record which types are used by a function. + """ + if node.cpp_template: + # The templated type will raise an error. + # XXX - Maybe dummy it out + # XXX - process templated types + return + ast = node._ast + rv_type = ast.typename + typedef = typemap.Typedef.lookup(rv_type) + if typedef is None: + raise RuntimeError( + "Unknown type {} for function decl: {}" + .format(rv_type, node['decl'])) + result_typedef = typemap.Typedef.lookup(rv_type) + # XXX - make sure it exists + used_types[rv_type] = result_typedef + for arg in ast.params: + argtype = arg.typename + typedef = typemap.Typedef.lookup(argtype) + if typedef is None: + raise RuntimeError("%s not defined" % argtype) + if typedef.base == 'wrapped': + used_types[argtype] = typedef + + def gen_functions_decl(self, functions): + """ Generate _decl for generated all functions. + """ + for node in functions: + node._decl = node._ast.gen_decl() + + +class Namify(object): + """Compute names of functions in library. + Need to compute F_name and F_C_name since they interact. + Compute all C names first, then Fortran. + A Fortran function may call a generated C function via + _PTR_F_C_index + Also compute number which may be controlled by options. + + C_name - Name of C function + F_C_name - Fortran function for C interface + F_name_impl - Name of Fortran function implementation + """ + def __init__(self, newlibrary, config): + self.newlibrary = newlibrary + self.config = config + + def name_library(self): + self.name_language(self.name_function_c) + self.name_language(self.name_function_fortran) + + def name_language(self, handler): + newlibrary = self.newlibrary + for cls in newlibrary.classes: + for func in cls.functions: + handler(cls, func) + + options = cls.options + fmt_class = cls._fmt + if 'F_this' in options: + fmt_class.F_this = options.F_this + + for func in newlibrary.functions: + handler(None, func) + + def name_function_c(self, cls, node): + options = node.options + if not options.wrap_c: + return + fmt_func = node._fmt + + node.eval_template('C_name') + node.eval_template('F_C_name') + fmt_func.F_C_name = fmt_func.F_C_name.lower() + + if 'C_this' in options: + fmt_func.C_this = options.C_this + + def name_function_fortran(self, cls, node): + """ Must process C functions to generate their names. + """ + options = node.options + if not options.wrap_fortran: + return + fmt_func = node._fmt + + node.eval_template('F_name_impl') + node.eval_template('F_name_function') + node.eval_template('F_name_generic') + + if 'F_this' in options: + fmt_func.F_this = options.F_this + if 'F_result' in options: + fmt_func.F_result = options.F_result + + +def generate_functions(library, config): + VerifyAttrs(library, config).verify_attrs() + GenFunctions(library, config).gen_library() + Namify(library, config).name_library() diff --git a/shroud/main.py b/shroud/main.py index dda47a651..c8f61acfb 100644 --- a/shroud/main.py +++ b/shroud/main.py @@ -68,6 +68,7 @@ from . import ast from . import declast +from . import generate from . import splicer from . import typemap from . import util @@ -113,678 +114,6 @@ def check_schema(self): return newlibrary -class GenFunctions(object): - """ - Generate types from class. - Generate functions based on overload/template/generic/attributes - Computes fmt.function_suffix. - """ - - def __init__(self, newlibrary, config): - self.newlibrary = newlibrary - self.config = config - - def gen_library(self): - """Entry routine to generate functions for a library. - """ - newlibrary = self.newlibrary - - self.function_index = newlibrary.function_index - - for cls in newlibrary.classes: - cls.functions = self.define_function_suffix(cls.functions) - newlibrary.functions = self.define_function_suffix(newlibrary.functions) - -# No longer need this, but keep code for now in case some other dependency checking is needed -# for cls in newlibrary.classes: -# self.check_class_dependencies(cls) - - def append_function_index(self, node): - """append to function_index, set index into node. - """ - ilist = self.function_index - node._function_index = len(ilist) -# node._fmt.function_index = str(len(ilist)) # debugging - ilist.append(node) - - def define_function_suffix(self, functions): - """ - Return a new list with generated function inserted. - """ - - # Look for overloaded functions - cpp_overload = {} - for function in functions: - if function.function_suffix is not None: - function._fmt.function_suffix = function.function_suffix - self.append_function_index(function) - cpp_overload. \ - setdefault(function._ast.name, []). \ - append(function._function_index) - - # keep track of which function are overloaded in C++. - for key, value in cpp_overload.items(): - if len(value) > 1: - for index in value: - self.function_index[index]._cpp_overload = value - - # Create additional functions needed for wrapping - ordered_functions = [] - for method in functions: - if method._has_default_arg: - self.has_default_args(method, ordered_functions) - ordered_functions.append(method) - if method.cpp_template: - method._overloaded = True - self.template_function(method, ordered_functions) - - # Look for overloaded functions - overloaded_functions = {} - for function in ordered_functions: - # if not function.options.wrap_c: - # continue - if function.cpp_template: - continue - overloaded_functions.setdefault( - function._ast.name, []).append(function) - - # look for function overload and compute function_suffix - for mname, overloads in overloaded_functions.items(): - if len(overloads) > 1: - for i, function in enumerate(overloads): - function._overloaded = True - if not function._fmt.inlocal('function_suffix'): - function._fmt.function_suffix = '_{}'.format(i) - - # Create additional C bufferify functions. - ordered3 = [] - for method in ordered_functions: - ordered3.append(method) - self.arg_to_buffer(method, ordered3) - - # Create multiple generic Fortran wrappers to call a - # single C functions - ordered4 = [] - for method in ordered3: - ordered4.append(method) - if not method.options.wrap_fortran: - continue - if method.fortran_generic: - method._overloaded = True - self.generic_function(method, ordered4) - - self.gen_functions_decl(ordered4) - - return ordered4 - - def template_function(self, node, ordered_functions): - """ Create overloaded functions for each templated argument. - """ - if len(node.cpp_template) != 1: - # In the future it may be useful to have multiple templates - # That the would start creating more permutations - raise NotImplementedError("Only one cpp_templated type for now") - for typename, types in node.cpp_template.items(): - for type in types: - new = util.copy_function_node(node) - ordered_functions.append(new) - self.append_function_index(new) - - new._generated = 'cpp_template' - fmt = new._fmt - fmt.function_suffix = fmt.function_suffix + '_' + type - new.cpp_template = {} - options = new.options - options.wrap_c = True - options.wrap_fortran = True - options.wrap_python = False - options.wrap_lua = False - # Convert typename to type - fmt.CPP_template = '<{}>'.format(type) - if new._ast.typename == typename: - new._ast.typename = type - new._CPP_return_templated = True - for arg in new._ast.params: - if arg.typename == typename: - arg.typename = type - - # Do not process templated node, instead process - # generated functions above. - options = node.options - options.wrap_c = False - options.wrap_fortran = False - options.wrap_python = False - options.wrap_lua = False - - def generic_function(self, node, ordered_functions): - """ Create overloaded functions for each generic method. - """ - if len(node.fortran_generic) != 1: - # In the future it may be useful to have multiple generic arguments - # That the would start creating more permutations - raise NotImplemented("Only one generic arg for now") - for argname, types in node.fortran_generic.items(): - for type in types: - new = util.copy_function_node(node) - ordered_functions.append(new) - self.append_function_index(new) - - new._generated = 'fortran_generic' - new._PTR_F_C_index = node._function_index - fmt = new._fmt - # XXX append to existing suffix - fmt.function_suffix = fmt.function_suffix + '_' + type - new.fortran_generic = {} - options = new.options - options.wrap_c = False - options.wrap_fortran = True - options.wrap_python = False - options.wrap_lua = False - # Convert typename to type - for arg in new._ast.params: - if arg.name == argname: - # Convert any typedef to native type with f_type - argtype = arg.typename - typedef = typemap.Typedef.lookup(argtype) - typedef = typemap.Typedef.lookup(typedef.f_type) - if not typedef.f_cast: - raise RuntimeError( - "unable to cast type {} in fortran_generic" - .format(argtype)) - arg.typename = type - - # Do not process templated node, instead process - # generated functions above. - options = node.options -# options.wrap_c = False - options.wrap_fortran = False -# options.wrap_python = False - - def has_default_args(self, node, ordered_functions): - """ - For each function which has a default argument, generate - a version for each possible call. - void func(int i = 0, int j = 0) - generates - void func() - void func(int i) - void func(int i, int j) - """ - default_funcs = [] - - default_arg_suffix = node.default_arg_suffix - ndefault = 0 - - min_args = 0 - for i, arg in enumerate(node._ast.params): - if arg.init is None: - min_args += 1 - continue - new = util.copy_function_node(node) - self.append_function_index(new) - new._generated = 'has_default_arg' - del new._ast.params[i:] # remove trailing arguments - new._has_default_arg = False - options = new.options - options.wrap_c = True - options.wrap_fortran = True - options.wrap_python = False - options.wrap_lua = False - fmt = new._fmt - try: - fmt.function_suffix = default_arg_suffix[ndefault] - except IndexError: - # XXX fmt.function_suffix = - # XXX fmt.function_suffix + '_nargs%d' % (i + 1) - pass - default_funcs.append(new._function_index) - ordered_functions.append(new) - ndefault += 1 - - # keep track of generated default value functions - node._default_funcs = default_funcs - node._nargs = (min_args, len(node._ast.params)) - # The last name calls with all arguments (the original decl) - try: - node._fmt.function_suffix = default_arg_suffix[ndefault] - except IndexError: - pass - - def arg_to_buffer(self, node, ordered_functions): - """Look for function which have implied arguments. - This includes functions with string or vector arguments. - If found then create a new C function that - will convert argument into a buffer and length. - """ - options = node.options - fmt = node._fmt - - # If a C++ function returns a std::string instance, - # the default wrapper will not compile since the wrapper - # will be declared as char. It will also want to return the - # c_str of a stack variable. Warn and turn off the wrapper. - ast = node._ast - result_type = ast.typename - result_typedef = typemap.Typedef.lookup(result_type) - # wrapped classes have not been added yet. - # Only care about string here. - attrs = ast.attrs - result_is_ptr = ast.is_indirect() - if result_typedef and result_typedef.base in ['string', 'vector'] and \ - result_type != 'char' and \ - not result_is_ptr: - options.wrap_c = False -# options.wrap_fortran = False - self.config.log.write("Skipping {}, unable to create C wrapper " - "for function returning {} instance" - " (must return a pointer or reference).\n" - .format(result_typedef.cpp_type, - ast.name)) - - if options.wrap_fortran is False: - return - if options.F_string_len_trim is False: # XXX what about vector - return - - # Is result or any argument a string? - has_implied_arg = False - for arg in ast.params: - argtype = arg.typename - typedef = typemap.Typedef.lookup(argtype) - if typedef.base == 'string': - is_ptr = arg.is_indirect() - if is_ptr: - has_implied_arg = True - else: - arg.typename = 'char_scalar' - elif typedef.base == 'vector': - has_implied_arg = True - - has_string_result = False - result_as_arg = '' # only applies to string functions - is_pure = ast.fattrs.get('pure', False) - if result_typedef.base == 'vector': - raise NotImplemented("vector result") - elif result_typedef.base == 'string': - if result_type == 'char' and not result_is_ptr: - # char functions cannot be wrapped directly in intel 15. - ast.typename = 'char_scalar' - has_string_result = True - result_as_arg = fmt.F_string_result_as_arg - result_name = result_as_arg or fmt.C_string_result_as_arg - - if not (has_string_result or has_implied_arg): - return - - # XXX options = node['options'] - # XXX options.wrap_fortran = False - # Preserve wrap_c. - # This keep a version which accepts char * arguments. - - # Create a new C function and change arguments - # to add len_trim attribute - C_new = util.copy_function_node(node) - ordered_functions.append(C_new) - self.append_function_index(C_new) - - C_new._generated = 'arg_to_buffer' - C_new._error_pattern_suffix = '_as_buffer' - fmt = C_new._fmt - fmt.function_suffix = fmt.function_suffix + options.C_bufferify_suffix - - options = C_new.options - options.wrap_c = True - options.wrap_fortran = False - options.wrap_python = False - options.wrap_lua = False - C_new._PTR_C_CPP_index = node._function_index - - newargs = [] - for arg in C_new._ast.params: - attrs = arg.attrs - argtype = arg.typename - arg_typedef = typemap.Typedef.lookup(argtype) - if arg_typedef.base == 'vector': - # Do not wrap the orignal C function with vector argument. - # Meaningless to call without the size argument. - # TODO: add an option where char** length is determined by looking - # for trailing NULL pointer. { "foo", "bar", NULL }; - node.options.wrap_c = False - node.options.wrap_python = False # NotImplemented - node.options.wrap_lua = False # NotImplemented - arg_typedef, c_statements = typemap.lookup_c_statements(arg) - - # set names for implied buffer arguments - stmts = 'intent_' + attrs['intent'] + '_buf' - intent_blk = c_statements.get(stmts, {}) - for buf_arg in intent_blk.get('buf_args', []): - if buf_arg in attrs: - # do not override user specified variable name - continue - if buf_arg == 'size': - attrs['size'] = options.C_var_size_template.format( - c_var=arg.name) - elif buf_arg == 'len_trim': - attrs['len_trim'] = options.C_var_trim_template.format( - c_var=arg.name) - elif buf_arg == 'len': - attrs['len'] = options.C_var_len_template.format( - c_var=arg.name) - - ## base typedef - - # Copy over some buffer specific fields to their generic name. - C_new.C_post_call = C_new.C_post_call_buf - - if has_string_result: - # Add additional argument to hold result - ast = C_new._ast - result_as_string = ast.result_as_arg(result_name) - attrs = result_as_string.attrs - attrs['len'] = options.C_var_len_template.format(c_var=result_name) - attrs['intent'] = 'out' - attrs['_is_result'] = True - # convert to subroutine - C_new._subprogram = 'subroutine' - - if is_pure: - # pure functions which return a string have result_pure defined. - pass - elif result_as_arg: - # Create Fortran function without bufferify function_suffix but - # with len attributes on string arguments. - F_new = util.copy_function_node(C_new) - ordered_functions.append(F_new) - self.append_function_index(F_new) - - # Fortran function should wrap the new C function - F_new._PTR_F_C_index = C_new._function_index - options = F_new.options - options.wrap_c = False - options.wrap_fortran = True - options.wrap_python = False - options.wrap_lua = False - # Do not add '_bufferify' - F_new._fmt.function_suffix = node._fmt.function_suffix - - # Do not wrap original function (does not have result argument) - node.options.wrap_fortran = False - else: - # Fortran function may call C subroutine if string result - node._PTR_F_C_index = C_new._function_index - - def XXXcheck_class_dependencies(self, node): - """ - Check used_types and find which header and module files - to use for this class - """ - # keep track of types which are used by methods arguments - used_types = {} - for method in node['methods']: - self.check_function_dependencies(method, used_types) - - modules = {} - for typ in used_types.values(): - if typ.f_module: - for mname, only in typ.f_module.items(): - module = modules.setdefault(mname, {}) - if only: # Empty list means no ONLY clause - for oname in only: - module[oname] = True - - # Always add C_PTR, needed for class F_derived_member - modules.setdefault('iso_c_binding', {})['C_PTR'] = True - - F_modules = [] # array of tuples ( name, (only1, only2) ) - for mname in sorted(modules): - F_modules.append((mname, sorted(modules[mname]))) - node.F_module_dependencies = F_modules - - def XXXcheck_function_dependencies(self, node, used_types): - """Record which types are used by a function. - """ - if node.cpp_template: - # The templated type will raise an error. - # XXX - Maybe dummy it out - # XXX - process templated types - return - ast = node._ast - rv_type = ast.typename - typedef = typemap.Typedef.lookup(rv_type) - if typedef is None: - raise RuntimeError( - "Unknown type {} for function decl: {}" - .format(rv_type, node['decl'])) - result_typedef = typemap.Typedef.lookup(rv_type) - # XXX - make sure it exists - used_types[rv_type] = result_typedef - for arg in ast.params: - argtype = arg.typename - typedef = typemap.Typedef.lookup(argtype) - if typedef is None: - raise RuntimeError("%s not defined" % argtype) - if typedef.base == 'wrapped': - used_types[argtype] = typedef - - def gen_functions_decl(self, functions): - """ Generate _decl for generated all functions. - """ - for node in functions: - node._decl = node._ast.gen_decl() - - -class VerifyAttrs(object): - """ - Check attributes and set some defaults. - Generate types for classes. - """ - def __init__(self, newlibrary, config): - self.newlibrary = newlibrary - self.config = config - - def verify_attrs(self): - newlibrary = self.newlibrary - - for cls in newlibrary.classes: - typemap.create_class_typedef(cls) - - for cls in newlibrary.classes: - for func in cls.functions: - self.check_arg_attrs(func) - - for func in newlibrary.functions: - self.check_arg_attrs(func) - - def check_arg_attrs(self, node): - """Regularize attributes - intent: lower case, no parens, must be in, out, or inout - value: if pointer, default to False (pass-by-reference; - else True (pass-by-value). - """ - options = node.options - if not options.wrap_fortran and not options.wrap_c: - return - - # cache subprogram type - ast = node._ast - result_type = ast.typename - result_is_ptr = ast.is_pointer() - # 'void'=subroutine 'void *'=function - if result_type == 'void' and not result_is_ptr: - node._subprogram = 'subroutine' - else: - node._subprogram = 'function' - - found_default = False - for arg in ast.params: - argname = arg.name - argtype = arg.typename - typedef = typemap.Typedef.lookup(argtype) - if typedef is None: - # if the type does not exist, make sure it is defined by cpp_template - #- decl: void Function7(ArgType arg) - # cpp_template: - # ArgType: - # - int - # - double - if argtype not in node.cpp_template: - raise RuntimeError("No such type %s: %s" % ( - argtype, arg.gen_decl())) - - is_ptr = arg.is_indirect() - attrs = arg.attrs - - # intent - intent = attrs.get('intent', None) - if intent is None: - if not is_ptr: - attrs['intent'] = 'in' - elif arg.const: - attrs['intent'] = 'in' - elif typedef.base == 'string': - attrs['intent'] = 'inout' - elif typedef.base == 'vector': - attrs['intent'] = 'inout' - else: - # void * - attrs['intent'] = 'in' # XXX must coordinate with VALUE - else: - intent = intent.lower() - if intent in ['in', 'out', 'inout']: - attrs['intent'] = intent - else: - raise RuntimeError( - "Bad value for intent: " + attrs['intent']) - - # value - value = attrs.get('value', None) - if value is None: - if is_ptr: - if (typedef.f_c_type or typedef.f_type) == 'type(C_PTR)': - # This causes Fortran to dereference the C_PTR - # Otherwise a void * argument becomes void ** - attrs['value'] = True - else: - attrs['value'] = False - else: - attrs['value'] = True - - # dimension - dimension = attrs.get('dimension', None) - if dimension: - if attrs.get('value', False): - raise RuntimeError("argument must not have value=True") - if not is_ptr: - raise RuntimeError("dimension attribute can only be " - "used on pointer and references") - if dimension is True: - # No value was provided, provide default - attrs['dimension'] = '(*)' - else: - # Put parens around dimension - attrs['dimension'] = '(' + attrs['dimension'] + ')' - elif typedef and typedef.base == 'vector': - # default to 1-d assumed shape - attrs['dimension'] = '(:)' - - if arg.init is not None: - found_default = True - node._has_default_arg = True - elif found_default is True: - raise RuntimeError("Expected default value for %s" % argname) - - # compute argument names for some attributes - # XXX make sure they don't conflict with other names - len_name = attrs.get('len', False) - if len_name is True: - attrs['len'] = options.C_var_len_template.format(c_var=argname) - len_name = attrs.get('len_trim', False) - if len_name is True: - attrs['len_trim'] = options.C_var_trim_template.format(c_var=argname) - size_name = attrs.get('size', False) - if size_name is True: - attrs['size'] = options.C_var_size_template.format(c_var=argname) - - # Check template attribute - temp = attrs.get('template', None) - if typedef and typedef.base == 'vector': - if not temp: - raise RuntimeError("std::vector must have template argument: %s" % ( - arg.gen_decl())) - typedef = typemap.Typedef.lookup(temp) - if typedef is None: - raise RuntimeError("No such type %s for template: %s" % ( - temp, arg.gen_decl())) - elif temp is not None: - raise RuntimeError("Type '%s' may not supply template argument: %s" % ( - argtype, arg.gen_decl())) - - -class Namify(object): - """Compute names of functions in library. - Need to compute F_name and F_C_name since they interact. - Compute all C names first, then Fortran. - A Fortran function may call a generated C function via - _PTR_F_C_index - Also compute number which may be controlled by options. - - C_name - Name of C function - F_C_name - Fortran function for C interface - F_name_impl - Name of Fortran function implementation - """ - def __init__(self, newlibrary, config): - self.newlibrary = newlibrary - self.config = config - - def name_library(self): - self.name_language(self.name_function_c) - self.name_language(self.name_function_fortran) - - def name_language(self, handler): - newlibrary = self.newlibrary - for cls in newlibrary.classes: - for func in cls.functions: - handler(cls, func) - - options = cls.options - fmt_class = cls._fmt - if 'F_this' in options: - fmt_class.F_this = options.F_this - - for func in newlibrary.functions: - handler(None, func) - - def name_function_c(self, cls, node): - options = node.options - if not options.wrap_c: - return - fmt_func = node._fmt - - node.eval_template('C_name') - node.eval_template('F_C_name') - fmt_func.F_C_name = fmt_func.F_C_name.lower() - - if 'C_this' in options: - fmt_func.C_this = options.C_this - - def name_function_fortran(self, cls, node): - """ Must process C functions to generate their names. - """ - options = node.options - if not options.wrap_fortran: - return - fmt_func = node._fmt - - node.eval_template('F_name_impl') - node.eval_template('F_name_function') - node.eval_template('F_name_generic') - - if 'F_this' in options: - fmt_func.F_this = options.F_this - if 'F_result' in options: - fmt_func.F_result = options.F_result - - class TypeOut(util.WrapperMixin): """A class to write out type information. It subclasses util.WrapperMixin in order to access @@ -971,9 +300,7 @@ def main_with_args(args): # print(all) newlibrary = Schema(all, config).check_schema() - VerifyAttrs(newlibrary, config).verify_attrs() - GenFunctions(newlibrary, config).gen_library() - Namify(newlibrary, config).name_library() + generate.generate_functions(newlibrary, config) if 'splicer' in all: # read splicer files defined in input YAML file From 130819a693de548f70366bf3647249332ffafe21 Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Wed, 10 Jan 2018 15:41:26 -0800 Subject: [PATCH 15/19] Move util.copy_function_node into FunctionNode.clone --- shroud/ast.py | 22 ++++++++++++++++++++++ shroud/generate.py | 13 +++++++------ shroud/util.py | 19 ------------------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/shroud/ast.py b/shroud/ast.py index c26a58cfa..f47f359b1 100644 --- a/shroud/ast.py +++ b/shroud/ast.py @@ -40,6 +40,10 @@ """ Abstract Syntax Tree nodes for Library, Class, and Function nodes. """ +from __future__ import print_function +from __future__ import absolute_import + +import copy from . import util from . import declast @@ -546,6 +550,24 @@ def _to_dict(self): d[key] = value return d + def clone(self): + """Create a copy of a function node to use with C++ template + or changing result to argument. + """ + # Shallow copy everything + new = copy.copy(self) + + # new layer of Options + new._fmt = util.Options(self._fmt) + new.options = util.Options(self.options) + + # deep copy dictionaries + new._ast = copy.deepcopy(self._ast) + new._fmtargs = copy.deepcopy(self._fmtargs) + new._fmtresult = copy.deepcopy(self._fmtresult) + + return new + def clean_dictionary(dd): """YAML converts some blank fields to None, diff --git a/shroud/generate.py b/shroud/generate.py index eb5530be0..97ea822ac 100644 --- a/shroud/generate.py +++ b/shroud/generate.py @@ -40,9 +40,10 @@ """ Generate additional functions required to create wrappers. """ +from __future__ import print_function +from __future__ import absolute_import from . import typemap -from . import util # copy function node class VerifyAttrs(object): @@ -305,7 +306,7 @@ def template_function(self, node, ordered_functions): raise NotImplementedError("Only one cpp_templated type for now") for typename, types in node.cpp_template.items(): for type in types: - new = util.copy_function_node(node) + new = node.clone() ordered_functions.append(new) self.append_function_index(new) @@ -344,7 +345,7 @@ def generic_function(self, node, ordered_functions): raise NotImplemented("Only one generic arg for now") for argname, types in node.fortran_generic.items(): for type in types: - new = util.copy_function_node(node) + new = node.clone() ordered_functions.append(new) self.append_function_index(new) @@ -399,7 +400,7 @@ def has_default_args(self, node, ordered_functions): if arg.init is None: min_args += 1 continue - new = util.copy_function_node(node) + new = node.clone() self.append_function_index(new) new._generated = 'has_default_arg' del new._ast.params[i:] # remove trailing arguments @@ -502,7 +503,7 @@ def arg_to_buffer(self, node, ordered_functions): # Create a new C function and change arguments # to add len_trim attribute - C_new = util.copy_function_node(node) + C_new = node.clone() ordered_functions.append(C_new) self.append_function_index(C_new) @@ -572,7 +573,7 @@ def arg_to_buffer(self, node, ordered_functions): elif result_as_arg: # Create Fortran function without bufferify function_suffix but # with len attributes on string arguments. - F_new = util.copy_function_node(C_new) + F_new = C_new.clone() ordered_functions.append(F_new) self.append_function_index(F_new) diff --git a/shroud/util.py b/shroud/util.py index b05a7721a..eda98f6c2 100644 --- a/shroud/util.py +++ b/shroud/util.py @@ -42,7 +42,6 @@ from __future__ import absolute_import import collections -import copy import string import json import os @@ -417,24 +416,6 @@ def _to_full_dict(self, d=None): return d -def copy_function_node(node): - """Create a copy of a function node to use with C++ template - or changing result to argument. - """ - # Shallow copy everything - new = copy.copy(node) - - new._ast = copy.deepcopy(node._ast) - new._fmt = Options(node._fmt) - new.options = Options(node.options) - - # deep copy dictionaries - new._fmtargs = copy.deepcopy(node._fmtargs) - new._fmtresult = copy.deepcopy(node._fmtresult) - - return new - - class ExpandedEncoder(json.JSONEncoder): """Jason handler to convert objects into a dictionary when they have a _to_dict method. From 95b1fc8efcd968a556a3fe585d6ac2b45d6db8a5 Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Wed, 10 Jan 2018 15:43:20 -0800 Subject: [PATCH 16/19] Fix header in generate types file --- shroud/main.py | 2 +- tests/example/userlibrary_types.yaml | 2 +- tests/include/default_library_types.yaml | 2 +- tests/names/testnames_types.yaml | 2 +- tests/tutorial/tutorial_types.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/shroud/main.py b/shroud/main.py index c8f61acfb..628f7c599 100644 --- a/shroud/main.py +++ b/shroud/main.py @@ -133,7 +133,7 @@ def write_types(self): newlibrary.eval_template('YAML_type_filename') fname = newlibrary._fmt.YAML_type_filename output = [ - '# Types generated by Shroud for class {}'.format( + '# Types generated by Shroud for library {}'.format( self.newlibrary.library), 'types:', ] diff --git a/tests/example/userlibrary_types.yaml b/tests/example/userlibrary_types.yaml index 70ce7d038..a556c0fe1 100644 --- a/tests/example/userlibrary_types.yaml +++ b/tests/example/userlibrary_types.yaml @@ -39,7 +39,7 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # ####################################################################### -# Types generated by Shroud for class UserLibrary +# Types generated by Shroud for library UserLibrary types: ExClass1: diff --git a/tests/include/default_library_types.yaml b/tests/include/default_library_types.yaml index 75bec9906..12fb7269c 100644 --- a/tests/include/default_library_types.yaml +++ b/tests/include/default_library_types.yaml @@ -40,7 +40,7 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # ####################################################################### -# Types generated by Shroud for class default_library +# Types generated by Shroud for library default_library types: Class1: diff --git a/tests/names/testnames_types.yaml b/tests/names/testnames_types.yaml index 4d0ba81f5..4c7047551 100644 --- a/tests/names/testnames_types.yaml +++ b/tests/names/testnames_types.yaml @@ -40,7 +40,7 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # ####################################################################### -# Types generated by Shroud for class testnames +# Types generated by Shroud for library testnames types: Names: diff --git a/tests/tutorial/tutorial_types.yaml b/tests/tutorial/tutorial_types.yaml index 58398d176..dcbce0d5d 100644 --- a/tests/tutorial/tutorial_types.yaml +++ b/tests/tutorial/tutorial_types.yaml @@ -40,7 +40,7 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # ####################################################################### -# Types generated by Shroud for class Tutorial +# Types generated by Shroud for library Tutorial types: Class1: From f9b1cfeaf6d177bcb9b26c55fa7d172b1ceb8301 Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Wed, 10 Jan 2018 15:58:21 -0800 Subject: [PATCH 17/19] Move remaining functionaliy of Schema class into main --- shroud/ast.py | 11 ++++++++++ shroud/main.py | 58 +++++++++++--------------------------------------- 2 files changed, 23 insertions(+), 46 deletions(-) diff --git a/shroud/ast.py b/shroud/ast.py index f47f359b1..77be98643 100644 --- a/shroud/ast.py +++ b/shroud/ast.py @@ -428,6 +428,17 @@ class FunctionNode(AstNode): } } + _decl - generated declaration. + Includes computed attributes + _function_index - sequence number function, + used in lieu of a pointer + _generated - who generated this function + _PTR_F_C_index - Used by fortran wrapper to find index of + C function to call + _PTR_C_CPP_index - Used by C wrapper to find index of C++ function + to call + _subprogram - subroutine or function + """ def __init__(self, parent, decl=None, diff --git a/shroud/main.py b/shroud/main.py index 628f7c599..1f7d12840 100644 --- a/shroud/main.py +++ b/shroud/main.py @@ -42,25 +42,10 @@ generate language bindings """ -# -# Annotate the YAML tree with additional internal fields -# _decl - generated declaration. -# Includes computed attributes -# _function_index - sequence number function, -# used in lieu of a pointer -# _generated - who generated this function -# _PTR_F_C_index - Used by fortran wrapper to find index of -# C function to call -# _PTR_C_CPP_index - Used by C wrapper to find index of C++ function -# to call -# _subprogram - subroutine or function -# -# from __future__ import print_function from __future__ import absolute_import import argparse -import copy import json import os import sys @@ -72,13 +57,11 @@ from . import splicer from . import typemap from . import util +from . import whelpers from . import wrapc from . import wrapf from . import wrapp from . import wrapl -from . import whelpers - -wformat = util.wformat class Config(object): @@ -87,33 +70,6 @@ class Config(object): pass -class Schema(object): - """Create a LibraryNode from a dictionary. - """ - def __init__(self, tree, config): - self.tree = tree # json tree - self.config = config - - def check_schema(self): - """ Check entire schema of input tree. - Create format dictionaries. - """ - node = self.tree - - def_types, def_types_alias = typemap.initialize() - declast.add_typemap() - - # Write out native types as YAML if requested - if self.config.yaml_types: - with open(os.path.join(self.config.yaml_dir, self.config.yaml_types), 'w') as yaml_file: - yaml.dump(def_types, yaml_file, default_flow_style=False) - print("Wrote", self.config.yaml_types) - - newlibrary = ast.create_library_from_dictionary(node) - - return newlibrary - - class TypeOut(util.WrapperMixin): """A class to write out type information. It subclasses util.WrapperMixin in order to access @@ -299,7 +255,17 @@ def main_with_args(args): # print(all) - newlibrary = Schema(all, config).check_schema() + def_types, def_types_alias = typemap.initialize() + declast.add_typemap() + + # Write out native types as YAML if requested + if config.yaml_types: + with open(os.path.join(config.yaml_dir, config.yaml_types), 'w') as yaml_file: + yaml.dump(def_types, yaml_file, default_flow_style=False) + print("Wrote", config.yaml_types) + + newlibrary = ast.create_library_from_dictionary(all) + generate.generate_functions(newlibrary, config) if 'splicer' in all: From 6e336cbbffc5fea428c579e6310563a96c4add49 Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Wed, 10 Jan 2018 16:11:05 -0800 Subject: [PATCH 18/19] Add a test for generate --- tests/test_ast.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_ast.py b/tests/test_ast.py index fa35374bc..5c6cabd8b 100644 --- a/tests/test_ast.py +++ b/tests/test_ast.py @@ -41,6 +41,7 @@ from __future__ import print_function from shroud import ast +from shroud import generate import unittest @@ -194,3 +195,24 @@ def test_c_class2(self): self.assertEqual(library.classes[0].functions[1].options.testa, 'a') self.assertEqual(library.classes[0].functions[1].options.testb, 'bb') self.assertEqual(library.classes[0].functions[1].options.testc, 'c') + + def test_d_generate1(self): + """char bufferify + Geneate an additional function with len and len_trim attributes. + """ + library = ast.LibraryNode() + library.add_function(decl='void func1(char * arg)') + self.assertEqual(len(library.functions), 1) + + generate.generate_functions(library, None) + self.assertEqual(len(library.functions), 2) + self.assertEqual(library.functions[0]._decl, + 'void func1(char * arg +intent(inout))') + self.assertEqual(library.functions[1]._decl, + 'void func1(char * arg +intent(inout)+len(Narg)+len_trim(Larg))') + +# import json +# from shroud import util +# print(json.dumps(library, cls=util.ExpandedEncoder, indent=4, sort_keys=True)) + + From 3d158ca782bcfbe72e3539f159a8636ae83bd7b3 Mon Sep 17 00:00:00 2001 From: Lee Taylor Date: Wed, 10 Jan 2018 17:01:49 -0800 Subject: [PATCH 19/19] Update to 0.6.0 --- CHANGELOG.md | 4 +++- shroud/__init__.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 881a0ba07..9c70ee4b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## v0.6.0 - 2018-01-10 ### Changed - Improved API for creating LibraryNode instances by using keyword arguments. - Factored out routine create_library_from_dictionary for dealing with YAML generated dictionary. +- Moved code into generate.py which generates additional routines to wrap. ## v0.5.0 - 2018-01-09 ### Added @@ -42,4 +45,3 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## v0.2.0 - 2017-10-26 ### Initial Open Source Release - \ No newline at end of file diff --git a/shroud/__init__.py b/shroud/__init__.py index 2e97c1543..36a02c447 100644 --- a/shroud/__init__.py +++ b/shroud/__init__.py @@ -42,6 +42,6 @@ """ -__version__ = "0.5.0" -version_info = (0,5,0,"beta",0) +__version__ = "0.6.0" +version_info = (0,6,0,"beta",0) # 'alpha', 'beta', 'candidate', or 'final'.