Clang для нечеткого разбора C++

Можно ли вообще анализировать C++ с неполными объявлениями с помощью clang с его существующим API libclang? Т.е. разбирать файл.cpp без включения всех заголовков, выводя объявления на лету. так, например, следующий текст:

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

Обнаружит неизвестный символ A, вызовет мой обратный вызов, который вычитает A, является классом, использующим мою магическую эвристику, а затем вызовет этот обратный вызов таким же образом с B, Foo и прочим. В конце я хочу сделать вывод, что я видел член Foo класса B, возвращающий A, а вещи - это функция. Или что-то в этом роде. контекст: я хочу посмотреть, смогу ли я сделать разумную подсветку синтаксиса и анализ кода на лету, не разбирая все заголовки очень быстро.

[EDIT] Чтобы уточнить, я ищу очень сильно ограниченный синтаксический анализ C++, возможно, с некоторой эвристикой, чтобы снять некоторые из ограничений.

C++ грамматика полна контекстных зависимостей. Является ли Foo() вызовом функции или конструкцией временного класса Foo? Является ли Foo материалом; создание шаблона Foo и объявление переменных вещей, или это странно выглядящие 2 вызова перегруженного оператора <и оператора>? Это можно сказать только в контексте, а контекст часто возникает при разборе заголовков.

То, что я ищу, - это способ подключения моих пользовательских правил соглашения. Например, я знаю, что не перегружаю символы Win32, поэтому могу смело предположить, что CreateFile - это всегда функция, и я даже знаю ее сигнатуру. Я также знаю, что все мои классы начинаются с заглавной буквы и являются существительными, а функции, как правило, являются глаголами, поэтому я могу предположить, что Foo и Bar являются именами классов. В более сложном сценарии я знаю, что не пишу выражений без побочных эффектов, таких как c; поэтому я могу предположить, что a всегда является экземпляром шаблона. И так далее.

Итак, вопрос в том, возможно ли использовать Clang API для обратного вызова каждый раз, когда он встречает неизвестный символ, и дать ему ответ, используя мою собственную не-C++ эвристику. Если моя эвристика не сработает, то разбор, очевидно, не удастся. И я не говорю о разборе библиотеки Boost:) Я говорю об очень простом C++, возможно, без шаблонов, ограниченном некоторым минимумом, который может обработать clang в этом случае.

4 ответа

Решение

Другое решение, которое, я думаю, больше подойдет для OP, чем для нечеткого анализа.

При разборе clang поддерживает семантическую информацию через часть Sema анализатора. При обнаружении неизвестного символа Sema возвращается к ExternalSemaSource, чтобы получить некоторую информацию об этом символе. Благодаря этому вы можете реализовать то, что вы хотите.

Вот быстрый пример того, как его настроить. Это не совсем функционально (я ничего не делаю в методе LookupUnqualified), вам может потребоваться провести дальнейшие исследования, и я думаю, что это хорошее начало.

// 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;
}

Идея и класс DynamicIDHandler происходят из цепляющего проекта, в котором неизвестные символы являются переменными (отсюда и комментарии, и код).

Я знаю, что вопрос довольно старый, но посмотрите здесь:

LibFuzzy - это библиотека для эвристического анализа C++, основанная на лексере Clang. Нечеткий синтаксический анализатор является отказоустойчивым, работает без знания системы сборки и неполных исходных файлов. Поскольку синтаксический анализатор обязательно делает догадки, результирующее синтаксическое дерево может быть частично неверным.

Это подпроект от clang-highlight, (экспериментального?) Инструмента, который, похоже, больше не разрабатывается.

Я заинтересован только в части нечеткого анализа и разветвил ее на своей странице github, где я исправил несколько мелких проблем и сделал инструмент автономным (его можно скомпилировать вне дерева исходных текстов clang). Не пытайтесь скомпилировать его с C++14 (режим по умолчанию G++ 6), потому что будут конфликты с make_unique,

Согласно этой странице, clang-format имеет свой собственный нечеткий синтаксический анализатор (и активно развивается), но анализатор был (есть?) Более тесно связан с инструментом.

Если вы строго не ограничите код, который разрешено писать людям, в принципе невозможно выполнить хорошую работу по синтаксическому анализу C++ (и, следовательно, выделению синтаксиса за пределами ключевых слов / регулярных выражений) без анализа всех заголовков. Препроцессор особенно хорош для того, чтобы все испортить.

Здесь есть некоторые мысли о трудностях нечеткого анализа (в контексте визуальной студии), которые могут быть интересны: http://blogs.msdn.com/b/vcblog/archive/2011/03/03/10136696.aspx

ОП не хочет "нечеткого разбора". То, что он хочет, - это полный неконтекстный синтаксический анализ исходного кода C++ без каких-либо требований к разрешению имен и типов. Он планирует сделать обоснованные предположения о типах, основанных на результате разбора.

Clang правильный анализ путаницы и разрешение имени / типа, что означает, что он должен иметь всю эту информацию о типе фона, доступную, когда это анализирует. Другие ответы предполагают LibFuzzy, который производит неправильные деревья разбора, и некоторый нечеткий парсер для clang-формата, о котором я ничего не знаю. Если кто-то настаивает на создании классического AST, ни одно из этих решений не даст "правильного" дерева перед лицом неоднозначных разборов.

Наш инструментарий реинжиниринга программного обеспечения DMS с его внешним интерфейсом C++ может анализировать источник C++ без информации о типе и производить точные "ASTs"; на самом деле это абстрактные синтаксические даги, где разветвления в деревьях представляют различные возможные интерпретации исходного кода в соответствии с точной языковой грамматикой (неоднозначные (под) разборы).

То, что пытается сделать Clang, - это избегать создания этих множественных подпроцессов, используя информацию о типе при разборе. DMS производит неоднозначные разборы и при (необязательном) прохождении после анализа (атрибут-грамматика-оценка) собирает информацию таблицы символов и устраняет подразборы, несовместимые с типами; для правильно сформированных программ это приводит к простому AST без неясностей.

Если ОП хочет сделать эвристические предположения о типовой информации, ему нужно знать эти возможные интерпретации. Если они устранены заранее, он не может прямо догадаться, какие типы могут понадобиться. Интересной возможностью является идея изменения грамматики атрибута (предоставляемой в исходной форме как часть внешнего интерфейса C++ DMS), которая уже знает все правила типа C++, чтобы сделать это с частичной информацией. Это было бы огромным шагом вперед по сравнению с созданием эвристического анализатора с нуля, учитывая, что он должен знать около 600 страниц тайных правил разрешения имен и типов из стандарта.

Вы можете увидеть примеры (dag), созданного парсером DMS.

Другие вопросы по тегам