summaryrefslogtreecommitdiff
path: root/gr-utils/blocktool/core/parseheader_generic.py
diff options
context:
space:
mode:
Diffstat (limited to 'gr-utils/blocktool/core/parseheader_generic.py')
-rw-r--r--gr-utils/blocktool/core/parseheader_generic.py350
1 files changed, 350 insertions, 0 deletions
diff --git a/gr-utils/blocktool/core/parseheader_generic.py b/gr-utils/blocktool/core/parseheader_generic.py
new file mode 100644
index 0000000000..ef6a1398ca
--- /dev/null
+++ b/gr-utils/blocktool/core/parseheader_generic.py
@@ -0,0 +1,350 @@
+#
+# Copyright 2020 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+#
+""" Module to generate AST for the headers and parse it """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import os
+import re
+import codecs
+import logging
+
+from ..core.base import BlockToolException, BlockTool
+from ..core.iosignature import io_signature, message_port
+from ..core.comments import read_comments, add_comments, exist_comments
+from ..core import Constants
+
+LOGGER = logging.getLogger(__name__)
+PYGCCXML_AVAILABLE = False
+try:
+ from pygccxml import parser, declarations, utils
+ PYGCCXML_AVAILABLE = True
+except:
+ from ...modtool.tools import ParserCCBlock
+ from ...modtool.cli import ModToolException
+
+class GenericHeaderParser(BlockTool):
+ """
+ : Single argument required: file_path
+ file_path: enter path for the header block in any of GNU Radio module
+ : returns the parsed header data in python dict
+ : return dict keys: namespace, class, io_signature, make,
+ properties, methods
+ : Can be used as an CLI command or an extenal API
+ """
+ name = 'Block Parse Header'
+ description = 'Create a parsed output from a block header file'
+
+ def __init__(self, file_path=None, blocktool_comments=False, include_paths=None, **kwargs):
+ """ __init__ """
+ BlockTool.__init__(self, **kwargs)
+ self.parsed_data = {}
+ self.addcomments = blocktool_comments
+ self.include_paths = None
+ if (include_paths):
+ self.include_paths = [p.strip() for p in include_paths.split(',')]
+ if not os.path.isfile(file_path):
+ raise BlockToolException('file does not exist')
+ file_path = os.path.abspath(file_path)
+ self.target_file = file_path
+
+
+
+ self.initialize()
+ self.validate()
+
+ def initialize(self):
+ """
+ initialize all the required API variables
+ """
+
+ if type(self.target_file) == list:
+ self.module = self.target_file
+ for dirs in self.target_file:
+ if not os.path.basename(self.module).startswith(Constants.GR):
+ self.module = os.path.abspath(
+ os.path.join(self.module, os.pardir))
+ else:
+ self.module = self.target_file
+ if not os.path.basename(self.module).startswith(Constants.GR):
+ self.module = os.path.abspath(
+ os.path.join(self.module, os.pardir))
+
+ self.modname = os.path.basename(self.module)
+ self.filename = os.path.basename(self.target_file)
+ self.targetdir = os.path.dirname(self.target_file)
+
+ def validate(self):
+ """ Override the Blocktool validate function """
+ BlockTool._validate(self)
+ if not self.filename.endswith('.h'):
+ raise BlockToolException(
+ 'Cannot parse a non-header file')
+
+ def _parse_cc_h(self, fname_h):
+ """ Go through a .cc and .h-file defining a block and return info """
+ def _get_blockdata(fname_h):
+ """ Return the block name and the header file name from the .cc file name """
+ blockname = os.path.splitext(os.path.basename(
+ fname_h))[0]
+ fname_cc = blockname + '_impl' + '.cc'
+ contains_modulename = blockname.startswith(
+ self.modname+'_')
+ blockname = blockname.replace(self.modname+'_', '', 1)
+ fname_cc = os.path.join(fname_h.split('include')[0],'lib',fname_cc)
+ return (blockname, fname_cc, contains_modulename)
+ # Go, go, go
+ LOGGER.info("Making GRC bindings for {}...".format(fname_h))
+ (blockname, fname_cc, contains_modulename) = _get_blockdata(fname_h)
+ try:
+ parser = ParserCCBlock(fname_cc,
+ os.path.join(
+ self.targetdir, fname_h),
+ blockname,
+ '39'
+ )
+ except IOError:
+ raise ModToolException(
+ "Can't open some of the files necessary to parse {}.".format(fname_cc))
+
+ if contains_modulename:
+ return (parser.read_params(), parser.read_io_signature(), self.modname+'_'+blockname)
+ else:
+ return (parser.read_params(), parser.read_io_signature(), blockname)
+
+ def parse_function(self, func_decl):
+ fcn_dict = {
+ "name": str(func_decl.name),
+ "return_type": str(func_decl.return_type),
+ "has_static": func_decl.has_static if hasattr(func_decl, 'has_static') else '0',
+ "arguments": []
+ }
+ for argument in func_decl.arguments:
+ args = {
+ "name": str(argument.name),
+ "dtype": str(argument.decl_type),
+ "default": argument.default_value
+ }
+ fcn_dict['arguments'].append(args)
+
+ return fcn_dict
+
+ def parse_class(self, class_decl):
+ class_dict = {'name': class_decl.name, 'member_functions': []}
+ if class_decl.bases:
+ class_dict['bases'] = class_decl.bases[0].declaration_path
+
+ constructors = []
+ # constructors
+ constructors = []
+ query_methods = declarations.access_type_matcher_t('public')
+ if hasattr(class_decl, 'constructors'):
+ cotrs = class_decl.constructors(function=query_methods,
+ allow_empty=True, recursive=False,
+ header_file=self.target_file,
+ name=class_decl.name)
+ for cotr in cotrs:
+ constructors.append(self.parse_function(cotr))
+
+ class_dict['constructors'] = constructors
+
+ # class member functions
+ member_functions = []
+ query_methods = declarations.access_type_matcher_t('public')
+ if hasattr(class_decl, 'member_functions'):
+ functions = class_decl.member_functions(function=query_methods,
+ allow_empty=True, recursive=False,
+ header_file=self.target_file)
+ for fcn in functions:
+ if str(fcn.name) not in [class_decl.name, '~'+class_decl.name]:
+ member_functions.append(self.parse_function(fcn))
+
+ class_dict['member_functions'] = member_functions
+
+ # enums
+ class_enums = []
+ if hasattr(class_decl, 'variables'):
+ enums = class_decl.enumerations(
+ allow_empty=True, recursive=False, header_file=self.target_file)
+ if enums:
+ for _enum in enums:
+ current_enum = {'name': _enum.name, 'values': _enum.values}
+ class_enums.append(current_enum)
+
+ class_dict['enums'] = class_enums
+
+ # variables
+ class_vars = []
+ query_methods = declarations.access_type_matcher_t('public')
+ if hasattr(class_decl, 'variables'):
+ variables = class_decl.variables(allow_empty=True, recursive=False, function=query_methods,
+ header_file=self.target_file)
+ if variables:
+ for _var in variables:
+ current_var = {
+ 'name': _var.name, 'value': _var.value, "has_static": _var.has_static if hasattr(_var, 'has_static') else '0'}
+ class_vars.append(current_var)
+ class_dict['vars'] = class_vars
+
+ return class_dict
+
+ def parse_namespace(self, namespace_decl):
+ namespace_dict = {}
+ # enums
+ namespace_dict['name'] = namespace_decl.decl_string
+ namespace_dict['enums'] = []
+ if hasattr(namespace_decl, 'enumerations'):
+ enums = namespace_decl.enumerations(
+ allow_empty=True, recursive=False, header_file=self.target_file)
+ if enums:
+ for _enum in enums:
+ current_enum = {'name': _enum.name, 'values': _enum.values}
+ namespace_dict['enums'].append(current_enum)
+
+ # variables
+ namespace_dict['variables'] = []
+ if hasattr(namespace_decl, 'variables'):
+ variables = namespace_decl.variables(
+ allow_empty=True, recursive=False, header_file=self.target_file)
+ if variables:
+ for _var in variables:
+ current_var = {
+ 'name': _var.name, 'values': _var.value, 'has_static': _var.has_static if hasattr(_var, 'has_static') else '0'}
+ namespace_dict['variables'].append(current_var)
+
+ # classes
+ namespace_dict['classes'] = []
+ if hasattr(namespace_decl, 'classes'):
+ classes = namespace_decl.classes(
+ allow_empty=True, recursive=False, header_file=self.target_file)
+ if classes:
+ for _class in classes:
+ namespace_dict['classes'].append(self.parse_class(_class))
+
+ # free functions
+ namespace_dict['free_functions'] = []
+ free_functions = []
+ if hasattr(namespace_decl, 'free_functions'):
+ functions = namespace_decl.free_functions(allow_empty=True, recursive=False,
+ header_file=self.target_file)
+ for fcn in functions:
+ if str(fcn.name) not in ['make']:
+ free_functions.append(self.parse_function(fcn))
+
+ namespace_dict['free_functions'] = free_functions
+
+ # sub namespaces
+ namespace_dict['namespaces'] = []
+
+ if hasattr(namespace_decl, 'namespaces'):
+ sub_namespaces = []
+ sub_namespaces_decl = namespace_decl.namespaces(allow_empty=True)
+ for ns in sub_namespaces_decl:
+ sub_namespaces.append(self.parse_namespace(ns))
+
+ namespace_dict['namespaces'] = sub_namespaces
+
+ return namespace_dict
+
+ def get_header_info(self, namespace_to_parse):
+ """
+ PyGCCXML header code parser
+ magic happens here!
+ : returns the parsed header data in python dict
+ : return dict keys: namespace, class, io_signature, make,
+ properties, methods
+ : Can be used as an CLI command or an extenal API
+ """
+ module = self.modname.split('-')[-1]
+ self.parsed_data['module_name'] = module
+ self.parsed_data['filename'] = self.filename
+
+ import hashlib
+ hasher = hashlib.md5()
+ with open(self.target_file, 'rb') as file_in:
+ buf = file_in.read()
+ hasher.update(buf)
+ self.parsed_data['md5hash'] = hasher.hexdigest()
+
+ # Right now if pygccxml is not installed, it will only handle the make function
+ # TODO: extend this to other publicly declared functions in the h file
+ if not PYGCCXML_AVAILABLE:
+ self.parsed_data['parser'] = 'simple'
+ (params, iosig, blockname) = self._parse_cc_h(self.target_file)
+ self.parsed_data['target_namespace'] = namespace_to_parse
+
+ namespace_dict = {}
+ namespace_dict['name'] = "::".join(namespace_to_parse)
+ class_dict = {}
+ class_dict['name'] = blockname
+
+ mf_dict = {
+ "name": "make",
+ "return_type": "::".join(namespace_to_parse + [blockname,"sptr"]),
+ "has_static": "1"
+ }
+
+ args = []
+
+ for p in params:
+ arg_dict = {
+ "name":p['key'],
+ "dtype":p['type'],
+ "default":p['default']
+ }
+ args.append(arg_dict)
+
+ mf_dict["arguments"] = args
+
+ class_dict["member_functions"] = [mf_dict]
+ namespace_dict["classes"] = [class_dict]
+ self.parsed_data["namespace"] = namespace_dict
+
+ return self.parsed_data
+ else:
+ self.parsed_data['parser'] = 'pygccxml'
+ generator_path, generator_name = utils.find_xml_generator()
+ xml_generator_config = parser.xml_generator_configuration_t(
+ xml_generator_path=generator_path,
+ xml_generator=generator_name,
+ include_paths=self.include_paths,
+ compiler='gcc',
+ undefine_symbols=['__PIE__'],
+ #define_symbols=['BOOST_ATOMIC_DETAIL_EXTRA_BACKEND_GENERIC', '__PIC__'],
+ define_symbols=['BOOST_ATOMIC_DETAIL_EXTRA_BACKEND_GENERIC'],
+ cflags='-std=c++11 -fPIC')
+ decls = parser.parse(
+ [self.target_file], xml_generator_config)
+ global_namespace = declarations.get_global_namespace(decls)
+
+ # namespace
+ # try:
+ main_namespace = global_namespace
+ for ns in namespace_to_parse:
+ main_namespace = main_namespace.namespace(ns)
+ if main_namespace is None:
+ raise BlockToolException('namespace cannot be none')
+ self.parsed_data['target_namespace'] = namespace_to_parse
+
+ self.parsed_data['namespace'] = self.parse_namespace(main_namespace)
+
+ # except RuntimeError:
+ # raise BlockToolException(
+ # 'Invalid namespace format in the block header file')
+
+ # namespace
+
+ return self.parsed_data
+
+ def run_blocktool(self):
+ """ Run, run, run. """
+ pass
+ # self.get_header_info()