2012-06-21 12 views
15

È possibile analizzare C++ con dichiarazioni incomplete con clang con la sua API libclang esistente? Cioè parsing .cpp file senza includere tutte le intestazioni, deducendo le dichiarazioni al volo. quindi, ad es. Il testo che segue:Clang per parsing fuzzy C++

A B::Foo(){return stuff();} 

rileverà simbolo sconosciuto A, chiamare il mio callback che detrae A è una classe usando la mia magia euristica, quindi chiamare questo callback allo stesso modo con B e Foo e roba. Alla fine voglio essere in grado di dedurre che ho visto un membro Foo di classe B che restituisce A, e roba è una funzione .. O qualcosa del genere. Contesto : Voglio vedere se riesco a fare un'evidenziazione della sintassi ragionevole e l'analisi del codice senza analizzare tutte le intestazioni molto rapidamente.

[EDIT] Per chiarire, sto cercando un parsing C++ molto limitato, possibilmente con qualche euristico per rimuovere alcune delle restrizioni.

La grammatica C++ è piena di dipendenze del contesto. Foo() è una chiamata di funzione o una costruzione di un temporaneo di classe Foo? È Foo <Bar> roba; un modello Foo <Bar> istanza e dichiarazione di roba variabile, oppure è strano 2 chiamate all'operatore sovraccarico < e operatore>? È possibile solo raccontare nel contesto e il contesto spesso deriva dall'analisi delle intestazioni.

Quello che sto cercando è un modo per collegare le mie regole di convenzione personalizzate. Per esempio. So che non sovraccarico i simboli Win32, quindi posso tranquillamente supporre che CreateFile sia sempre una funzione,, e conosco persino la sua firma. So anche che tutte le mie classi iniziano con una maiuscola e sono sostantivi, e le funzioni sono di solito verbi, quindi posso ragionevolmente intuire che Foo e Bar sono nomi di classe. In uno scenario più complesso, so che non scrivo espressioni senza effetti collaterali come un < b> c; quindi posso supporre che a sia sempre un modello di istanziazione. E così via.

Quindi, la domanda è se sia possibile utilizzare l'API Clang per richiamare ogni volta che incontra un simbolo sconosciuto e dargli una risposta usando la mia euristica non-C++. Se il mio euristico fallisce, allora l'analisi fallisce, ovviamente. E non sto parlando di analizzare la libreria Boost :) Sto parlando di C++ molto semplice, probabilmente senza modelli, limitato a un minimo che in questo caso può essere gestito da clang.

+0

È sempre possibile modificare direttamente CLang. Non sono sicuro di quanto sarebbe facile, dato che ci sono molte volte in cui la ricerca può determinare legalmente che non venga trovato nulla (ad es. Contesti dipendenti, ADL). –

+0

Hai davvero bisogno di clang? Se no, forse ha senso provare altre soluzioni? Può succedere che funzionino meglio. –

+0

Sì, ho guardato antlr, ed è fattibile, anche se sospetto che sarebbe più difficile e meno performante .. In effetti sto usando antlr per analizzare un C++ limitato, quindi mi sarebbe familiare. Ci sono altre alternative reali? –

risposta

3

Un'altra soluzione che credo si adatti di più il PO di parsing sfocata.

Durante l'analisi, clang mantiene le informazioni semantiche attraverso la parte Sema dell'analizzatore. Quando si incontra un simbolo sconosciuto, Sema torna a ExternalSemaSource per ottenere alcune informazioni su questo simbolo. Attraverso questo, potresti attuare ciò che vuoi.

Ecco un breve esempio su come impostarlo. Non è del tutto funzionale (non sto facendo nulla nel metodo LookupUnifiedqualificato), potrebbe essere necessario fare ulteriori indagini e penso che sia un buon inizio.

// Declares clang::SyntaxOnlyAction. 
#include <clang/Frontend/FrontendActions.h> 
#include <clang/Tooling/CommonOptionsParser.h> 
#include <clang/Tooling/Tooling.h> 
#include <llvm/Support/CommandLine.h> 
#include <clang/AST/AST.h> 
#include <clang/AST/ASTConsumer.h> 
#include <clang/AST/RecursiveASTVisitor.h> 
#include <clang/Frontend/ASTConsumers.h> 
#include <clang/Frontend/FrontendActions.h> 
#include <clang/Frontend/CompilerInstance.h> 
#include <clang/Tooling/CommonOptionsParser.h> 
#include <clang/Tooling/Tooling.h> 
#include <clang/Rewrite/Core/Rewriter.h> 
#include <llvm/Support/raw_ostream.h> 
#include <clang/Sema/ExternalSemaSource.h> 
#include <clang/Sema/Sema.h> 
#include "clang/Basic/DiagnosticOptions.h" 
#include "clang/Frontend/TextDiagnosticPrinter.h" 
#include "clang/Frontend/CompilerInstance.h" 
#include "clang/Basic/TargetOptions.h" 
#include "clang/Basic/TargetInfo.h" 
#include "clang/Basic/FileManager.h" 
#include "clang/Basic/SourceManager.h" 
#include "clang/Lex/Preprocessor.h" 
#include "clang/Basic/Diagnostic.h" 
#include "clang/AST/ASTContext.h" 
#include "clang/AST/ASTConsumer.h" 
#include "clang/Parse/Parser.h" 
#include "clang/Parse/ParseAST.h" 
#include <clang/Sema/Lookup.h> 

#include <iostream> 
using namespace clang; 
using namespace clang::tooling; 
using namespace llvm; 

class ExampleVisitor : public RecursiveASTVisitor<ExampleVisitor> { 
private: 
    ASTContext *astContext; 

public: 
    explicit ExampleVisitor(CompilerInstance *CI, StringRef file) 
     : astContext(&(CI->getASTContext())) {} 

    virtual bool VisitVarDecl(VarDecl *d) { 
    std::cout << d->getNameAsString() << "@\n"; 
    return true; 
    } 
}; 

class ExampleASTConsumer : public ASTConsumer { 
private: 
    ExampleVisitor visitor; 

public: 
    explicit ExampleASTConsumer(CompilerInstance *CI, StringRef file) 
     : visitor(CI, file) {} 
    virtual void HandleTranslationUnit(ASTContext &Context) { 
    // de cette façon, on applique le visiteur sur l'ensemble de la translation 
    // unit 
    visitor.TraverseDecl(Context.getTranslationUnitDecl()); 
    } 
}; 

class DynamicIDHandler : public clang::ExternalSemaSource { 
public: 
    DynamicIDHandler(clang::Sema *Sema) 
     : m_Sema(Sema), m_Context(Sema->getASTContext()) {} 
    ~DynamicIDHandler() = default; 

    /// \brief Provides last resort lookup for failed unqualified lookups 
    /// 
    /// If there is failed lookup, tell sema to create an artificial declaration 
    /// which is of dependent type. So the lookup result is marked as dependent 
    /// and the diagnostics are suppressed. After that is's an interpreter's 
    /// responsibility to fix all these fake declarations and lookups. 
    /// It is done by the DynamicExprTransformer. 
    /// 
    /// @param[out] R The recovered symbol. 
    /// @param[in] S The scope in which the lookup failed. 
    virtual bool LookupUnqualified(clang::LookupResult &R, clang::Scope *S) { 
    DeclarationName Name = R.getLookupName(); 
    std::cout << Name.getAsString() << "\n"; 
    // IdentifierInfo *II = Name.getAsIdentifierInfo(); 
    // SourceLocation Loc = R.getNameLoc(); 
    // VarDecl *Result = 
    //  // VarDecl::Create(m_Context, R.getSema().getFunctionLevelDeclContext(), 
    //  //     Loc, Loc, II, m_Context.DependentTy, 
    //  //     /*TypeSourceInfo*/ 0, SC_None, SC_None); 
    // if (Result) { 
    // R.addDecl(Result); 
    // // Say that we can handle the situation. Clang should try to recover 
    // return true; 
    // } else{ 
    // return false; 
    // } 
    return false; 
    } 

private: 
    clang::Sema *m_Sema; 
    clang::ASTContext &m_Context; 
}; 

// *****************************************************************************/ 

LangOptions getFormattingLangOpts(bool Cpp03 = false) { 
    LangOptions LangOpts; 
    LangOpts.CPlusPlus = 1; 
    LangOpts.CPlusPlus11 = Cpp03 ? 0 : 1; 
    LangOpts.CPlusPlus14 = Cpp03 ? 0 : 1; 
    LangOpts.LineComment = 1; 
    LangOpts.Bool = 1; 
    LangOpts.ObjC1 = 1; 
    LangOpts.ObjC2 = 1; 
    return LangOpts; 
} 

int main() { 
    using clang::CompilerInstance; 
    using clang::TargetOptions; 
    using clang::TargetInfo; 
    using clang::FileEntry; 
    using clang::Token; 
    using clang::ASTContext; 
    using clang::ASTConsumer; 
    using clang::Parser; 
    using clang::DiagnosticOptions; 
    using clang::TextDiagnosticPrinter; 

    CompilerInstance ci; 
    ci.getLangOpts() = getFormattingLangOpts(false); 
    DiagnosticOptions diagnosticOptions; 
    ci.createDiagnostics(); 

    std::shared_ptr<clang::TargetOptions> pto = std::make_shared<clang::TargetOptions>(); 
    pto->Triple = llvm::sys::getDefaultTargetTriple(); 

    TargetInfo *pti = TargetInfo::CreateTargetInfo(ci.getDiagnostics(), pto); 

    ci.setTarget(pti); 
    ci.createFileManager(); 
    ci.createSourceManager(ci.getFileManager()); 
    ci.createPreprocessor(clang::TU_Complete); 
    ci.getPreprocessorOpts().UsePredefines = false; 
    ci.createASTContext(); 

    ci.setASTConsumer(
     llvm::make_unique<ExampleASTConsumer>(&ci, "../src/test.cpp")); 

    ci.createSema(TU_Complete, nullptr); 
    auto &sema = ci.getSema(); 
    sema.Initialize(); 
    DynamicIDHandler handler(&sema); 
    sema.addExternalSource(&handler); 

    const FileEntry *pFile = ci.getFileManager().getFile("../src/test.cpp"); 
    ci.getSourceManager().setMainFileID(ci.getSourceManager().createFileID(
     pFile, clang::SourceLocation(), clang::SrcMgr::C_User)); 
    ci.getDiagnosticClient().BeginSourceFile(ci.getLangOpts(), 
              &ci.getPreprocessor()); 
    clang::ParseAST(sema,true,false); 
    ci.getDiagnosticClient().EndSourceFile(); 

    return 0; 
} 

L'idea e la classe DynamicIDHandler sono da cling progetto in cui i simboli sconosciuti sono variabili (da qui i commenti e il codice).

5

A meno che non si limiti fortemente il codice che le persone sono autorizzate a scrivere, è fondamentalmente impossibile fare un buon lavoro di analisi del C++ (e quindi evidenziazione della sintassi oltre parole chiave/espressioni regolari) senza analizzare tutte le intestazioni. Il pre-processore è particolarmente bravo a rovinare tutto per te.

Ci sono alcune riflessioni sulle difficoltà di analisi Fuzzy (nel contesto di Visual Studio) qui, che potrebbero essere di interesse: http://blogs.msdn.com/b/vcblog/archive/2011/03/03/10136696.aspx

+0

Sì, sto cercando un parsing C++ molto limitato, possibilmente con un certo euristico per sollevare alcune delle restrizioni. La grammatica ++ è piena di dipendenze del contesto. Quello che mi chiedo se sia possibile –

+0

... ho aggiunto un chiarimento alla mia domanda. Grazie per la risposta! –

5

So che la domanda è piuttosto vecchio, ma hanno uno sguardo here:

LibFuzzy è una libreria per l'analisi euristica di C++ basata sul Lexer di Clang . Il parser fuzzy è a tolleranza d'errore, funziona senza conoscenza di il sistema di compilazione e su file di origine incompleti. Come il parser fa necessariamente ipotesi, l'albero della sintassi risultante potrebbe essere parzialmente errato .

È un sottoprogetto di clang-highlight, uno strumento (sperimentale?) Che sembra non essere più sviluppato.

Sono interessato solo alla parte di parsing fuzzy e lo ho modificato su my github page dove ho risolto diversi problemi minori e reso lo strumento autonomo (può essere compilato all'esterno dell'albero dei sorgenti di clang). Non provare a compilarlo con C++ 14 (quale modalità predefinita di G ++ 6), perché ci saranno conflitti con make_unique.

Secondo this page, il formato clang ha il proprio parser fuzzy (ed è attivamente sviluppato), ma il parser era (è?) Più strettamente accoppiato allo strumento.

0

OP non desidera "parsing fuzzy". Quello che vuole è il contesto completo - gratuito analisi del codice sorgente C++, senza alcun obbligo di risoluzione di nome e tipo. Ha intenzione di fare ipotesi plausibili sui tipi in base al risultato del parse.

Clang analisi di grovigli e risoluzione nome/tipo, il che significa che deve avere tutte le informazioni sul tipo di sfondo disponibili quando analizza. Altre risposte suggeriscono una LibFuzzy che produce alberi di analisi errati e un parser fuzzy per il formato clang su cui non so nulla. Se uno insiste nel produrre un AST classico, nessuna di queste soluzioni produrrà l'albero "giusto" di fronte a pareri ambigui.

nostro DMS Software Reengineering Toolkit con il suo C++ front end può parse C++ fonte without the type information, e produce "AST" accurate; questi sono in effetti sintassi astratti dags in cui le forche negli alberi rappresentano diverse possibili interpretazioni del codice sorgente in base a una grammatica precisa della lingua (analoghi ambigui (sub)).

Ciò che Clang prova a fare è evitare di produrre questi sub-pars multipli utilizzando le informazioni sul tipo durante l'analisi. Ciò che DMS fa è produrre le analisi ambigue, e nel passaggio (facoltativo) post-parsing (attributo-grammar-evaluation), raccogliere informazioni sulle tabelle dei simboli ed eliminare le sotto-analisi che sono incoerenti con i tipi; per programmi ben formati, questo produce un AST semplice senza ambiguità.

Se OP vuole fare ipotesi euristiche circa le informazioni sul tipo, dovrà conoscere queste possibili interpretazioni. Se vengono eliminati in anticipo, non può indovinare semplicemente quali tipi potrebbero essere necessari. Una possibilità interessante è l'idea di modificare la grammatica degli attributi (fornita in forma sorgente come parte del front-end C++ del DMS), che già conosce tutte le regole di tipo C++, per fare ciò con informazioni parziali. Sarebbe un enorme vantaggio iniziare a costruire l'analizzatore euristico da zero, dato che deve conoscere circa 600 pagine di nomi arcani e regole di risoluzione del tipo dello standard.

You can see examples of the (dag) produced by DMS's parser.