#!/usr/bin/python3 # -*- coding: UTF-8 -*- #------------------------------------------------------------------- # Purpose: # Copyright 2020 Huawei Technologies Co., Ltd. All rights reserved. #------------------------------------------------------------------- import os import re import sys import logging logging.basicConfig(stream=sys.stdout, format='[%(asctime)s] [%(lineno)s] %(levelname)s: %(message)s', level=logging.INFO) """ this attr is used for symbol table visible """ GE_ATTR = 'GE_FUNC_VISIBILITY' """ generate stub func body by return type """ RETURN_STATEMENTS = { 'graphStatus': ' std::cout << "[ERROR]: stub library libgraph or libge_compiler cannot be used for execution, please check your "\n ' ' << "environment variables and compilation options to make sure you use the correct library."\n' ' << std::endl;\n' ' return ACL_ERROR_COMPILING_STUB_MODE;', 'Status': ' return SUCCESS;', 'Graph': ' return Graph();', 'Graph&': ' return *this;', 'Format': ' return Format();', 'Format&': ' return *this;', 'Shape': ' return Shape();', 'Shape&': ' return *this;', 'TensorDesc': ' return TensorDesc();', 'TensorDesc&': ' return *this;', 'Tensor': ' return Tensor();', 'Tensor&': ' return *this;', 'Operator': ' return Operator();', 'Operator&': ' return *this;', 'Ptr': ' return nullptr;', 'std::string': ' return "";', 'std::string&': ' return "";', 'string': ' return "";', 'int': ' return 0;', 'DataType': ' return DT_FLOAT;', 'InferenceContextPtr': ' return nullptr;', 'SubgraphBuilder': ' return nullptr;', 'OperatorImplPtr': ' return nullptr;', 'OutHandler': ' return nullptr;', 'std::vector': ' return {};', 'std::vector': ' return {};', 'std::map': ' return {};', 'uint32_t': ' return 0;', 'int64_t': ' return 0;', 'uint64_t': ' return 0;', 'size_t': ' return 0;', 'float': ' return 0.0f;', 'bool': ' return false;', } """ max code len per line in hua_wei software programming specifications """ max_code_len_per_line = 100 """ white_list_for_debug, include_dir_key_words is to determines which header files to generate cc files from when DEBUG on """ white_list_for_debug = ["attr_value.h", "operator.h", "tensor.h", "graph.h", "operator_factory.h", "ge_ir_build.h", "ge_api.h", "tensorflow_parser.h", "caffe_parser.h"] include_dir_key_words = ["ge", "graph", "parser"] DEBUG = True def need_generate_func(func_line): """ :param func_line: :return: """ if func_line.strip().endswith("default") or func_line.strip().endswith("delete") \ or func_line.strip().startswith("typedef") or func_line.strip().startswith("using"): return False return True def file_endswith_white_list_suffix(file): """ :param file: :return: """ if DEBUG: for suffix in white_list_for_debug: if file.endswith(suffix): return True return False else: return True """ belows are patterns used for analyse .h file """ # pattern function pattern_func = re.compile(r"""(^[\s]*) #leading with space,we will find and delete after ([a-zA-Z~_] # void int likely .* [)] #we find ) (?!.*{) # we do not want the case int abc() const .*) (;.*) #we want to find ; and after for we will replace these later \n$ """, re.VERBOSE | re.MULTILINE | re.DOTALL) # pattern comment pattern_comment = re.compile(r'^\s*//') pattern_comment_2_start = re.compile(r'^\s*/[*]') pattern_comment_2_end = re.compile(r'[*]/\s*$') # pattern define pattern_define = re.compile(r'^\s*#define') pattern_define_return = re.compile(r'\\\s*$') # blank line pattern_blank_line = re.compile(r'^\s*$') # virtual,explicit,friend,static pattern_keyword = re.compile(r'(virtual\s+|explicit\s+|friend\s+|static\s+)') # lead space pattern_leading_space = re.compile(r'(^[\s]*)[a-zA-Z~_]') # functions will have patterns such as func ( or func( # but operator is an exception; the class name is preceded by an operator, and the above mode does not exist # format like :"operator = ()" pattern_func_name = re.compile(r'([a-zA-Z0-9~_\-]+\s*|operator?.*)[(]') # template pattern_template = re.compile(r'^\s*template') pattern_template_end = re.compile(r'>\s*$') # namespace pattern_namespace = re.compile(r'namespace.*{') # class : which can handle classA a and {not on the same line, but if found ';' after class,then don't deal with pattern_class = re.compile(r'^[\s]*(class|struct)\s+(%s\s+)?([a-zA-Z0-9_\-]+ 0 and not friend_match: line, func_name = self.handle_class_member_func(line, template_string) # Normal functions else: line, func_name = self.handle_normal_func(line, template_string) need_generate = need_generate_func(line) # func body line += self.implement_function(line) # comment line = self.gen_comment(start_i) + line # write to out file self.write_func_content(line, func_name, need_generate) # next loop self.line_index += 1 logging.info('Added %s functions', len(self.func_list_exist)) logging.info('Successfully converted,please see ' + self.output_file) def handle_func1(self, line): """ :param line: :return: """ find1 = re.search('[(]', line) if not find1: self.line_index += 1 return "continue", line, None find2 = re.search('[)]', line) start_i = self.line_index space_match = pattern_leading_space.search(line) # deal with # int abc(int a, # int b) if find1 and (not find2): self.line_index += 1 line2 = self.input_content[self.line_index] if space_match: line2 = re.sub('^' + space_match.group(1), '', line2) line += line2 while self.line_index < len(self.input_content) and (not re.search('[)]', line2)): self.line_index += 1 line2 = self.input_content[self.line_index] line2 = re.sub('^' + space_match.group(1), '', line2) line += line2 match_start = pattern_start.search(self.input_content[self.line_index]) match_end = pattern_end.search(self.input_content[self.line_index]) if match_start: # like ) { or ) {} int the last line if not match_end: self.stack.append('normal_now') ii = start_i while ii <= self.line_index: ii += 1 self.line_index += 1 return "continue", line, start_i logging.info("line[%s]", line) # ' int abc();'->'int abc()' (line, match) = pattern_func.subn(r'\2\n', line) logging.info("line[%s]", line) # deal with case: # 'int \n abc(int a, int b)' if re.search(r'^\s*(inline)?\s*[a-zA-Z0-9_]+\s*$', self.input_content[start_i - 1]): line = self.input_content[start_i - 1] + line line = line.lstrip() if not match: self.line_index += 1 return "continue", line, start_i return "pass", line, start_i def handle_stack(self, match_start): """ :param match_start: :return: """ line = self.input_content[self.line_index] match_end = pattern_end.search(line) if match_start: self.stack.append('normal_now') if match_end: top_status = self.stack.pop() if top_status == 'namespace_now': self.output_fd.write(line + '\n') elif top_status == 'class_now': self.stack_class.pop() self.stack_template.pop() if match_start or match_end: self.line_index += 1 return "continue" if len(self.stack) > 0 and self.stack[-1] == 'normal_now': self.line_index += 1 return "continue" return "pass" def handle_class(self, template_string, line, match_start, match_class): """ :param template_string: :param line: :param match_start: :param match_class: :return: """ if match_class: # we face a class self.stack_template.append(template_string) self.stack.append('class_now') class_name = match_class.group(3) # class template specializations: class A > if '<' in class_name: k = line.index('<') fit = 1 for ii in range(k + 1, len(line)): if line[ii] == '<': fit += 1 if line[ii] == '>': fit -= 1 if fit == 0: break class_name += line[k + 1:ii + 1] logging.info('class_name[%s]', class_name) self.stack_class.append(class_name) while not match_start: self.line_index += 1 line = self.input_content[self.line_index] match_start = pattern_start.search(line) self.line_index += 1 return "continue" return "pass" def handle_template(self): line = self.input_content[self.line_index] match_template = pattern_template.search(line) template_string = '' if match_template: match_template_end = pattern_template_end.search(line) template_string = line while not match_template_end: self.line_index += 1 line = self.input_content[self.line_index] template_string += line match_template_end = pattern_template_end.search(line) self.line_index += 1 return template_string def handle_namespace(self): line = self.input_content[self.line_index] match_namespace = pattern_namespace.search(line) if match_namespace: # we face namespace self.output_fd.write(line + '\n') self.stack.append('namespace_now') self.line_index += 1 def handle_normal_func(self, line, template_string): template_line = '' self.stack_template.append(template_string) if self.stack_template[-1] != '': template_line = re.sub(r'\s*template', 'template', self.stack_template[-1]) # change '< class T = a, class U = A(3)>' to '' template_line = re.sub(r'\s*=.*>(\s*)$', r'>\1', template_line) template_line = re.sub(r'\s*=.*,', ',', template_line) template_line = re.sub(r'\s*=.*', '', template_line) line = re.sub(r'\s*=.*,', ',', line) line = re.sub(r'\s*=.*\)', ')', line) line = template_line + line self.stack_template.pop() func_name = re.search(r'^.*\)', line, re.MULTILINE | re.DOTALL).group() logging.info("line[%s]", line) logging.info("func_name[%s]", func_name) return line, func_name def handle_class_member_func(self, line, template_string): template_line = '' x = '' if template_string != '': template_string = re.sub(r'\s*template', 'template', template_string) template_string = re.sub(r'\s*=.*>(\s*)$', r'>\1', template_string) template_string = re.sub(r'\s*=.*,', ',', template_string) template_string = re.sub(r'\s*=.*', '', template_string) if self.stack_template[-1] != '': if not (re.search(r'<\s*>', stack_template[-1])): template_line = re.sub(r'^\s*template', 'template', stack_template[-1]) if not (re.search(r'<.*>', self.stack_class[-1])): # for x we get like template -> x = re.sub(r'template\s*<', '<', template_line) # remove template -> x = re.sub(r'\n', '', x) x = re.sub(r'\s*=.*,', ',', x) x = re.sub(r'\s*=.*\>', '>', x) x = x.rstrip() # remove \n x = re.sub(r'(class|typename)\s+|(|\s*class)', '', x) # remove class,typename -> x = re.sub(r'<\s+', '<', x) x = re.sub(r'\s+>', '>', x) x = re.sub(r'\s+,', ',', x) x = re.sub(r',\s+', ', ', x) line = re.sub(r'\s*=\s+0', '', line) line = re.sub(r'\s*=\s+.*,', ',', line) line = re.sub(r'\s*=\s+.*\)', ')', line) logging.info("x[%s]\nline[%s]", x, line) # if the function is long, void ABC::foo() # breaks into two lines void ABC::\n foo() temp_line = pattern_func_name.sub(self.stack_class[-1] + x + '::' + r'\1(', line, count=1) if len(temp_line) > max_code_len_per_line: line = pattern_func_name.sub(self.stack_class[-1] + x + '::\n' + r'\1(', line, count=1) else: line = temp_line logging.info("line[%s]", line) # add template as the above if there is one template_line = re.sub(r'\s*=.*>(\s*)$', r'>\1', template_line) template_line = re.sub(r'\s*=.*,', ',', template_line) template_line = re.sub(r'\s*=.*', '', template_line) line = template_line + template_string + line func_name = re.search(r'^.*\)', line, re.MULTILINE | re.DOTALL).group() logging.info("line[%s]", line) logging.info("func_name[%s]", func_name) return line, func_name def write_func_content(self, content, func_name, need_generate): if not (func_name in self.func_list_exist) and need_generate: self.output_fd.write(content) self.func_list_exist.append(func_name) logging.info('add func:[%s]', func_name) def gen_comment(self, start_i): comment_line = '' # Function comments are on top of function declarations, copy them over k = start_i - 1 # one line before this func start if pattern_template.search(self.input_content[k]): k -= 1 if pattern_comment_2_end.search(self.input_content[k]): comment_line = self.input_content[k].lstrip() while not pattern_comment_2_start.search(self.input_content[k]): k -= 1 comment_line = self.input_content[k].lstrip() + comment_line else: for j in range(k, 0, -1): c_line = self.input_content[j] if pattern_comment.search(c_line): c_line = re.sub(r'\s*//', '//', c_line) comment_line = c_line + comment_line else: break return comment_line @staticmethod def implement_function(func): function_def = '' function_def += '{\n' all_items = func.split() start = 0 return_type = all_items[start] if return_type == "const": start += 1 return_type = all_items[start] if return_type.startswith(('std::map', 'std::set', 'std::vector')): return_type = "std::map" if return_type.endswith('*') or (len(all_items) > start + 1 and all_items[start + 1].startswith('*')): return_type = "Ptr" if len(all_items) > start + 1 and all_items[start + 1].startswith('&'): return_type += "&" if RETURN_STATEMENTS.__contains__(return_type): function_def += RETURN_STATEMENTS[return_type] else: logging.warning("Unhandled return type[%s]", return_type) function_def += '\n' function_def += '}\n' function_def += '\n' return function_def def collect_header_files(path): """ :param path: :return: """ header_files = [] shared_includes_content = [] for root, dirs, files in os.walk(path): files.sort() for file in files: if file.find("git") >= 0: continue if not file.endswith('.h'): continue file_path = os.path.join(root, file) file_path = file_path.replace('\\', '/') header_files.append(file_path) include_str = '#include "{}"\n'.format(file_path[path.rindex('/') + 1:]) shared_includes_content.append(include_str) # for acl error code shared_includes_content.append('#include \n') shared_includes_content.append('const int ACL_ERROR_COMPILING_STUB_MODE = 100039;\n') return header_files, shared_includes_content def generate_stub_file(inc_dir, out_cc_dir): """ :param inc_dir: :param out_cc_dir: :return: """ target_header_files, shared_includes_content = collect_header_files(inc_dir) for header_file in target_header_files: if not file_endswith_white_list_suffix(header_file): continue cc_file = re.sub('.h*$', '.cc', header_file) h_2_cc = H2CC(header_file, out_cc_dir + cc_file[cc_file.rindex('/') + 1:], shared_includes_content) h_2_cc.h2cc() def gen_code(inc_dir, out_cc_dir): """ :param inc_dir: :param out_cc_dir: :return: """ if not inc_dir.endswith('/'): inc_dir += '/' if not out_cc_dir.endswith('/'): out_cc_dir += '/' for include_dir_key_word in include_dir_key_words: generate_stub_file(inc_dir + include_dir_key_word, out_cc_dir) if __name__ == '__main__': inc_dir = sys.argv[1] out_cc_dir = sys.argv[2] gen_code(inc_dir, out_cc_dir)