среда, 2 января 2013 г.

Каретная диагностика (caret diagnostic)

Для разработчика компиляторов каретная диагностика — это отличный способ убить двух зайцев одним камнем. Она избавляет от многих проблем при написании компилятора и становится хорошим заделом при создании среды разработки.

Для реализации каретной диагностики компилятору следует хранить содержимое файла с исходным кодом целиком, вместе с AST и остальными производными данными. Принцип крайне прост: мы сопровождаем диагностическое сообщение не обратной распечаткой ошибочного выражения, а его диапазоном в текстовом файле с исходным кодом. В условном парсере языка Python это может выглядеть так:

m_diagnosticClient.reportError(token().range(), "`%s' is not printable expression");
consumeToken();
break;

Объект, которому передаётся вся диагностика, получает только диапазон(-ы) ошибки и описание в виде обычной строки либо строки и прикреплённых к ней параметров. Затем он вправе провести локализацию описания, подставить в него параметры и скопировать из исходного кода строки, составляющие контекст ошибки — всё это делается единообразно независимо от рода ошибки. При этом интерфейс клиента может выглядеть, например, так
class IDiagnosticClient
{
public:
    virtual ~IDiagnosticClient() = default;

    /**
     * \brief Pushes semantic, syntax or lexical error to client
     * \param cursor - first position of error
     * \param message - nullptr not expected
     */
    virtual void reportError(const SourceLocation& cursor, const char* message) = 0;
    virtual void reportError(const SourceRange& range, const char* message) = 0;

    virtual void reportWarning(const SourceLocation& cursor, const char* message) = 0;
    virtual void reportWarning(const SourceRange& range, const char* message) = 0;
};  

Традиционная диагностика

В далёкую эпоху мейнфреймов и слабых персональных компьютеров компиляторы не осмеливались хранить весь компилируемый исходный код в памяти, и диагностика выдавалась путём обратного преобразования, то есть распечатки, дефектного узла абстрактного синтаксического дерева (AST). Легко могла возникнуть ситуация, когда какое-то сложное выражение печаталось неверно или неточно. Посмотрим на простой код на C/C++ с двумя ошибками:
void foo(char **p, char **q)
{
  (p - q)();
  p();
}
Компилятор GCC версии 4.2, выпущенный в 2008 году, применяет обратное преобразование узла AST, и делает это неудачно
t.c:3:10: error: called object  is not a function
t.c:4:4: error: called object ‘p’ is not a function
Пример неточной распечатки — это потеря форматирования кода? Всё-таки программисты по-разному ставят пробелы, и пихают const в самые разные места.
void foo1(const SourceRange &range);
void foo2(SourceRange const& range);

Мощь каретной диагностики

Clang изначально, а GCC с версии 4.8 используют каретную диагностику. При этом clang вычисляет место ошибки (строка и колонка), несколько (обычно 0) диапазонов, обличающих виновные в ошибке объекты, а также несколько (обычно 0) замен текста, необходимых для исправления ошибки. Рассмотрим код, порождающий простое предупреждение о неиспользуемой переменной

#include <stdio.h>

bool importantFunction(int sufficientArgument)
{
    return false;
}

int main()
{
    if (importantFunction(1))
        puts("Hello, world!");
    return 0;
}

При компиляции clang 3.2 выдаст предупреждение о неиспользованном аргументе:

Видно, что компилятор предпочёл не делать обратную печать выражения и просто показал на место проблемы в строке. Это не баг, а фишка — ведь человек видит сразу весь контекст проблемы. Изменим пример, чтобы увидеть ошибку с двумя диапазонами

#include <stdio.h>
#include <string>

int main()
{
    std::string num1 = "1";
    int num2 = 2;
    if (num1 + num2 == 3)
        puts("Hello, world!");
    return 0;
} 

При компиляции clang 3.2 выдаст ошибку:
 

Плюсы для IDE

Позиция ошибки, диапазоны ошибки, абстрактное сообщение, которое можно перевести на родной язык пользователя — все возможности каретной диагностики будут прекрасным подспорьем для IDE. Да что там говорить, посмотрите картинки



Кроме того, к диапазону и описанию ошибки можно прикрепить несколько замен текста, исправляющих ошибку: каждая замена описывается диапазоном (SourceRange) и новым текстом, заменяющим старый. Именно так действует libclang, в его терминологии это называется FixIt. К сожалению, экспериментальный плагин для QtCreator пока ещё не умеет обрабатывать FixIt, а XCode у меня под рукой нет.

Комментариев нет:

Отправить комментарий