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

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

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

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

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

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

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

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

Ответы

Ответ 1

Другое решение, которое, я думаю, подойдет больше 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 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 относятся к проекту cling, где неизвестные символы являются переменными (отсюда комментарии и код).

Ответ 2

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

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

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

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

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