3
0
mirror of https://github.com/triqs/dft_tools synced 2024-11-01 19:53:45 +01:00
dft_tools/pytriqs/wrap_generator/clang_parser.py
Olivier Parcollet 522477eb2d desc generator : minor cleaning
- while porting apps. examples to 1.2
- add build_ipytriqs, etc...
- various details...
2014-07-27 15:38:22 +02:00

199 lines
7.5 KiB
Python

# This module defines the function parse that
# call libclang to parse a C++ file, and retrieve
# from the clang AST the classes, functions, methods, members (including
# template).
# This module is use e..g by the wrapper desc generator.
import sys,re,os
import clang.cindex
import itertools
from mako.template import Template
import textwrap
def get_annotations(node):
return [c.displayname for c in node.get_children()
if c.kind == clang.cindex.CursorKind.ANNOTATE_ATTR]
def process_doc (doc) :
if not doc : return ""
for p in ["/\*","\*/","^\s*\*", "///", "//", r"\\brief"] : doc = re.sub(p,"",doc,flags = re.MULTILINE)
return doc.strip()
file_locations = set(())
class member_(object):
def __init__(self, cursor,ns=()):
loc = cursor.location.file.name
if loc : file_locations.add(loc)
self.doc = process_doc(cursor.raw_comment)
self.ns = ns
self.name = cursor.spelling
self.access = cursor.access_specifier
self.ctype = cursor.type.spelling
def namespace(self) :
return "::".join(self.ns)
class type_(object):
def __init__(self, cursor):
self.name, self.canonical_name = cursor.spelling, cursor.get_canonical().spelling
class Function(object):
def __init__(self, cursor, is_constructor = False, ns=() ): #, template_list =()):
loc = cursor.location.file.name
if loc : file_locations.add(loc)
self.doc = process_doc(cursor.raw_comment)
self.brief_doc = self.doc.split('\n')[0].strip() # improve ...
self.ns = ns
self.name = cursor.spelling
self.annotations = get_annotations(cursor)
self.access = cursor.access_specifier
self.params = [] # a list of tuple (type, name, default_value or None).
self.template_list = [] #template_list
self.is_constructor = is_constructor
self.is_static = cursor.is_static_method()
for c in cursor.get_children():
if c.kind == clang.cindex.CursorKind.TEMPLATE_TYPE_PARAMETER :
self.template_list.append(c.spelling)
elif (c.kind == clang.cindex.CursorKind.PARM_DECL) :
default_value = None
for ch in c.get_children() :
# TODO : string literal do not work.. needs to use location ? useful ?
if ch.kind in [clang.cindex.CursorKind.INTEGER_LITERAL, clang.cindex.CursorKind.FLOATING_LITERAL,
clang.cindex.CursorKind.CHARACTER_LITERAL, clang.cindex.CursorKind.STRING_LITERAL] :
default_value = ch.get_tokens().next().spelling
t = type_(c.type)
self.params.append ( (t, c.spelling, default_value ))
#else :
# print " node in fun ", c.kind
self.rtype = type_(cursor.result_type) if not is_constructor else None
def namespace(self) :
return "::".join(self.ns)
def signature_cpp(self) :
s = "{name} ({args})" if not self.is_constructor else "{rtype} {name} ({args})"
s = s.format(args = ', '.join( ["%s %s"%(t.name,n) + "="%d if d else "" for t,n,d in self.params]), **self.__dict__)
if self.template_list :
s = "template<" + ', '.join(['typename ' + x for x in self.template_list]) + "> " + s
if self.is_static : s = "static " + s
return s.strip()
@property
def is_template(self) : return len(self.template_list)>0
def __str__(self) :
return "%s\n%s\n"%(self.signature_cpp(),self.doc)
class Class(object):
def __init__(self, cursor,ns):
loc = cursor.location.file.name
if loc : file_locations.add(loc)
self.doc = process_doc(cursor.raw_comment)
self.brief_doc = self.doc.split('\n')[0].strip() # improve ...
self.ns = ns
self.name = cursor.spelling
self.functions = []
self.constructors = []
self.methods = []
self.members = []
self.proplist = []
self.annotations = get_annotations(cursor)
self.file = cursor.location.file.name
# MISSING : constructors template not recognized
for c in cursor.get_children():
# Only public nodes
if c.access_specifier != clang.cindex.AccessSpecifier.PUBLIC : continue
if (c.kind == clang.cindex.CursorKind.FIELD_DECL):
m = member_(c)
self.members.append(m)
elif (c.kind == clang.cindex.CursorKind.CXX_METHOD):
f = Function(c)
self.methods.append(f)
elif (c.kind == clang.cindex.CursorKind.CONSTRUCTOR):
f = Function(c, is_constructor = True)
self.constructors.append(f)
elif (c.kind == clang.cindex.CursorKind.FUNCTION_DECL):
f = Function(c)
self.functions.append(f)
elif (c.kind == clang.cindex.CursorKind.FUNCTION_TEMPLATE):
f = Function(c)
self.methods.append(f)
def namespace(self) :
return "::".join(self.ns)
def __str__(self) :
s,s2 = "class {name}:\n {doc}\n\n".format(**self.__dict__),[]
for m in self.members :
s2 += ["%s %s"%(m.ctype,m.name)]
for m in self.methods :
s2 += str(m).split('\n')
for m in self.functions :
s2 += ("friend " + str(m)).split('\n')
s2 = '\n'.join( [ " " + l.strip() + '\n' for l in s2 if l.strip()])
return s + s2
def build_functions_and_classes(cursor, namespaces=[]):
classes,functions = [],[]
for c in cursor.get_children():
if (c.kind == clang.cindex.CursorKind.FUNCTION_DECL
and c.location.file.name == sys.argv[1]):
functions.append( Function(c,namespaces))
elif (c.kind in [clang.cindex.CursorKind.CLASS_DECL, clang.cindex.CursorKind.STRUCT_DECL]
and c.location.file.name == sys.argv[1]):
classes.append( Class(c,namespaces))
elif c.kind == clang.cindex.CursorKind.NAMESPACE:
child_fnt, child_classes = build_functions_and_classes(c, namespaces +[c.spelling])
functions.extend(child_fnt)
classes.extend(child_classes)
return functions,classes
def parse(filename, debug, compiler_options, where_is_libclang):
compiler_options = [ '-std=c++11', '-stdlib=libc++'] + compiler_options
clang.cindex.Config.set_library_file(where_is_libclang)
index = clang.cindex.Index.create()
print "Parsing the C++ file (may take a few seconds) ..."
translation_unit = index.parse(filename, ['-x', 'c++'] + compiler_options)
print "... done. \nExtracting ..."
# If clang encounters errors, we report and stop
errors = [d for d in translation_unit.diagnostics if d.severity >= 3]
if errors :
s = "Clang reports the following errors in parsing\n"
for err in errors :
loc = err.location
s += '\n'.join(["file %s line %s col %s"%(loc.file, loc.line, loc.column), err.spelling])
raise RuntimeError, s + "\n... Your code must compile before making the wrapper !"
# Analyze the AST to extract classes and functions
functions, classes = build_functions_and_classes(translation_unit.cursor)
print "... done"
global file_locations
if len(file_locations) != 1 :
print file_locations
raise RuntimeError, "Multiple file location not implemented"
file_locations = list(file_locations)
if debug :
print "functions"
for f in functions :
print f
print "classes"
for c in classes :
print c
return functions, classes