2013-12-10 1 views
0

Я пишу генератор документации а и получение включают пути прямо в ад, так я просто пропустить полностью каждый включает в себя, когда я анализирую файл. Я также настраиваю руками все проблематичные определения или блоки #ifdef, которые будут пропускаться из-за отсутствующих включений (и другой командной строки в сравнении с производственной сборкой).разбор с libclang: получение CXX_BASE_SPECIFIER курсоров, когда базовые типы неизвестных

проблема, которую я заметил, что:

struct ComplexBuffer : IAnimatable 
{ 
}; 

С IAnimatable не объявлена ​​(или поступательно декларируется).
Я использую питон связывание clang.cindex, поэтому я использую get_children для итерации: этого результат выходит:

Found grammar element "IAnimatable" {CursorKind.CLASS_DECL} [line=37, col=8] 
Found grammar element "ComplexBuffer" {CursorKind.STRUCT_DECL} [line=39, col=9] 

, если я закончу базовый тип:

class IAnimatable {}; 

struct ComplexBuffer : IAnimatable 

Я получаю правильный выход:

Found grammar element "IAnimatable" {CursorKind.CLASS_DECL} [line=37, col=8] 
Found grammar element "ComplexBuffer" {CursorKind.STRUCT_DECL} [line=39, col=9] 
Found grammar element "class IAnimatable" {CursorKind.CXX_BASE_SPECIFIER} [line=39, col=25] 
Found grammar element "class IAnimatable" {CursorKind.TYPE_REF} [line=39, col=25] 

Именно то, что я хочу, потому что я могу обнаружить список наследования, чтобы положить в документацию.

Эта проблема возникает только потому, что я пропускаю все входящие.

Возможно, я смогу обходным путем, перерисовывая линию декларации вручную?

EDIT PS: мой разборе питон скрипт для завершения:

import clang.cindex 

index = clang.cindex.Index.create() 
tu = index.parse(sys.argv[1], args=["-std=c++98"], options=clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES) 

def printall_visitor(node): 
    print 'Found grammar element "%s" {%s} [line=%s, col=%s]' % (node.displayname, node.kind, node.location.line, node.location.column) 

def visit(node, func): 
    func(node) 
    for c in node.get_children(): 
     visit(c, func) 

visit(tu.cursor, printall_visitor) 
+0

Почему бы вам не попросить компилятор предоставить вам предварительно обработанный вывод и работать с ним? Пусть компилятор обрабатывает пути '# include',' # ifdef' и т. Д. И может ли он дать вам то, что увидит синтаксический анализатор C++? С 'gcc' вы используете' gcc -E', и я уверен, что есть аналогичный флаг для clang –

+0

Я не могу позволить этому дескриптору include. он просто плюнет «файл не найден» и остановит разбор прямо там. Для правильного управления им нужна идеальная командная строка '-I'. Здесь все ады разрываются. –

+0

Вы не можете получить оригинальные флаги сборки? Как вы надеетесь обрабатывать '# ifdef' или случайный макрос? (Да, макросы не часто бывают, но они бывают.) –

ответ

4

Я собираюсь ответить на этот вопрос сам, потому что код, который я придумал может быть полезным для будущих Googlers.

В конце концов, я закодировал оба метода, которые должны работать, чтобы восстановить список базовых классов в списке наследования в строке объявления класса.

один с использованием курсора AST и один полностью ручной, справляющийся с C++ сложностью.

здесь весь результат:

#!/usr/bin/env python 
# -*- coding: utf-8 -*- 
''' 
Created on 2013/12/09 

@author: voddou 
''' 

import sys 
import re 
import clang.cindex 
import os 
import string 

class bcolors: 
    HEADER = '\033[95m' 
    OKBLUE = '\033[94m' 
    CYAN = '\033[96m' 
    OKGREEN = '\033[92m' 
    WARNING = '\033[93m' 
    FAIL = '\033[91m' 
    ENDC = '\033[0m' 
    MAGENTA = '\033[95m' 
    GREY = '\033[90m' 

    def disable(self): 
     self.HEADER = '' 
     self.OKBLUE = '' 
     self.OKGREEN = '' 
     self.WARNING = '' 
     self.FAIL = '' 
     self.ENDC = '' 
     self.CYAN = '' 
     self.MAGENTA = '' 
     self.GREY = '' 

from contextlib import contextmanager 

@contextmanager 
def scopedColorizer(color): 
    sys.stdout.write(color) 
    yield 
    sys.stdout.write(bcolors.ENDC) 

#clang.cindex.Config.set_library_file("C:/python27/DLLs/libclang.dll") 

src_filepath = sys.argv[1] 
src_basename = os.path.basename(src_filepath) 

parseeLines = file(src_filepath).readlines() 

def trim_all(astring): 
    return "".join(astring.split()) 

def has_token(line, token): 
    trimed = trim_all(line) 
    pos = string.find(trimed, token) 
    return pos != -1 

def has_any_token(line, token_list): 
    results = [has_token(line, t) for t in token_list] 
    return any(results) 

def is_any(astring, some_strings): 
    return any([x == astring for x in some_strings]) 

def comment_out(line): 
    return "//" + line 

# alter the original file to remove #inlude directives and protective ifdef blocks 
for i, l in enumerate(parseeLines): 
    if has_token(l, "#include"): 
     parseeLines[i] = comment_out(l) 
    elif has_any_token(l, ["#ifdef", "#ifdefined", "#ifndef", "#if!defined", "#endif", "#elif", "#else"]): 
     parseeLines[i] = comment_out(l) 

index = clang.cindex.Index.create() 
tu = index.parse(src_basename, 
       args=["-std=c++98"], 
       unsaved_files=[(src_basename, "".join(parseeLines))], 
       options=clang.cindex.TranslationUnit.PARSE_SKIP_FUNCTION_BODIES) 

print 'Translation unit:', tu.spelling, "\n" 

def gather_until(strlist, ifrom, endtokens): 
    """make one string out of a list of strings, starting from a given index, until one token in endtokens is found. 
    ex: gather_until(["foo", "toto", "bar", "kaz"], 1, ["r", "z"]) 
     will yield "totoba" 
    """ 
    result = strlist[ifrom] 
    nextline = ifrom + 1 
    while not any([string.find(result, token) != -1 for token in endtokens]): 
     result = result + strlist[nextline] 
     nextline = nextline + 1 
    nearest = result 
    for t in endtokens: 
     nearest = nearest.partition(t)[0] 
    return nearest 

def strip_templates_parameters(declline): 
    """remove any content between < > 
    """ 
    res = "" 
    nested = 0 
    for c in declline: 
     if c == '>': 
      nested = nested - 1 
     if nested == 0: 
      res = res + c 
     if c == '<': 
      nested = nested + 1 
    return res 

# thanks Markus Jarderot from Stackoverflow.com 
def comment_remover(text): 
    def replacer(match): 
     s = match.group(0) 
     if s.startswith('/'): 
      return "" 
     else: 
      return s 
    pattern = re.compile(
     r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', 
     re.DOTALL | re.MULTILINE 
    ) 
    return re.sub(pattern, replacer, text) 

def replace_any_of(haystack, list_of_candidates, by_what): 
    for cand in list_of_candidates: 
     haystack = string.replace(haystack, cand, by_what) 
    return haystack 

cxx_keywords = ["class", "struct", "public", "private", "protected"] 

def clean_name(displayname): 
    """remove namespace and type tags 
    """ 
    r = displayname.rpartition("::")[2] 
    r = replace_any_of(r, cxx_keywords, "") 
    return r 

def find_parents_using_clang(node): 
    l = [] 
    for c in node.get_children(): 
     if c.kind == clang.cindex.CursorKind.CXX_BASE_SPECIFIER: 
      l.append(clean_name(c.displayname)) 
    return None if len(l) == 0 else l 

# syntax based custom parsing 
def find_parents_list(node): 
    ideclline = node.location.line - 1 
    declline = parseeLines[ideclline] 
    with scopedColorizer(bcolors.WARNING): 
     print "class decl line:", declline.strip() 
    fulldecl = gather_until(parseeLines, ideclline, ["{", ";"]) 
    fulldecl = clean_name(fulldecl) 
    fulldecl = trim_all(fulldecl) 
    if string.find(fulldecl, ":") != -1: # if inheritance exists on the declaration line 
     baselist = fulldecl.partition(":")[2] 
     res = strip_templates_parameters(baselist) # because they are separated by commas, they would break the split(",") 
     res = comment_remover(res) 
     res = res.split(",") 
     return res 
    return None 

# documentation generator 
def make_htll_visitor(node): 
    if (node.kind == clang.cindex.CursorKind.CLASS_DECL 
     or node.kind == clang.cindex.CursorKind.STRUCT_DECL 
     or node.kind == clang.cindex.CursorKind.CLASS_TEMPLATE): 

     bases2 = find_parents_list(node) 
     bases = find_parents_using_clang(node) 
     if bases is not None: 
      with scopedColorizer(bcolors.CYAN): 
       print "class clang list of bases:", str(bases) 

     if bases2 is not None: 
      with scopedColorizer(bcolors.MAGENTA): 
       print "class manual list of bases:", str(bases2) 


def visit(node, func): 
    func(node) 
    for c in node.get_children(): 
     visit(c, func) 

visit(tu.cursor, make_htll_visitor) 

with scopedColorizer(bcolors.OKGREEN): 
    print "all over" 

этот код позволил мне принять неполную C++ единицы перевода, правильно разборе заявления, такие как это:

struct ComplexBuffer 
    : IAnimatable 
    , Bugger, 

     Mozafoka 
{ 
}; 

справляется также с этим:

struct AnimHandler : NonCopyable, IHandlerPrivateGetter< AnimHandler, AafHandler > // CRTP 
{ 
... 
}; 

дает мне этот выход:

class manual list of bases: ['NonCopyable', 'IHandlerPrivateGetter<>'] 

, который хорош, функция clang не вернула ни одного класса в базовом списке. Теперь необходимо объединить результат обеих этих функций, используя set, чтобы быть на безопасной стороне, если ручной синтаксический анализатор что-то пропустит.Однако я думаю, что это может вызвать тонкие дублирования из-за разницы между displayname и моим собственным парсером.

Но вот вы, googlers, хороший шаблон генератора документации pangon clang, который не нуждается в полной корректности вариантов сборки и довольно быстро, потому что он полностью игнорирует операторы include.

хороший день для всех.

Смежные вопросы