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