Для разработчика компиляторов каретная диагностика — это отличный способ убить двух зайцев одним камнем. Она избавляет от многих проблем при написании компилятора и становится хорошим заделом при создании среды разработки.
Для реализации каретной диагностики компилятору следует хранить содержимое файла с исходным кодом целиком, вместе с 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 у меня под рукой нет.



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