Создание пользовательского printf с проверкой и подсветкой синтаксиса - PullRequest
0 голосов
/ 18 января 2020

Я хотел бы иметь возможность создать механизм ведения журнала, который очень просто передает аргументы в printf, но мне нужна подсветка синтаксиса и проверка ввода. Это схема для пространства имен Log, которое у меня есть.

#pragma once
#include "pch.h"

namespace Log
{   
    // Determines whether to create the console window or not.
    // Turn off for before release.
    extern bool CreateConsoleWindow;

    // Initialisation.
    extern bool Init();

    // Writes a line to the console, appended with \r\n.
    extern void WriteLine(const char* _Format, ...);

    // Writes a line to the console, with a [Debug] prepend.
    extern void DebugInfo(const char* _Format, ...);

    // Writes a line to the console, with a [Server] prepend.
    extern void ServerInfo(const char* _Format, ...);

    // Destruction.
    extern bool Dispose();
}

Это моя реализация для Log::WriteLine(_Format, ...):

    // Writes a line to the console, appended with \r\n.
    void WriteLine(const char* _Format, ...)
    {
        // Print the message.
        char buffer[4096];
        va_list args;
        va_start(args, _Format);
            auto rc = vsnprintf(buffer, sizeof(buffer), _Format, args);
        va_end(args);

        // Append the new line.
        printf("\r\n");
    }

Но когда я это делаю, я теряю вся проверка и подсветка синтаксиса, которые необходимы, будучи новичком, из входной строки. Например:

auto hash = Crypto::GetHash(syntax[1].c_str()); // unsigned long, by way of multiple nested macros.
printf("Hash: %d", hash);

Это будет показывать %d светло-зеленым, и в printf будет предупреждение о hash, говорящее, что оно не будет отображаться, потому что оно ожидает %lu. Я полагаюсь на VS, чтобы научить меня, что я делаю неправильно, чтобы я мог учиться на своих ошибках.

Если я сделаю то же самое с моей функцией:

auto hash = Crypto::GetHash(syntax[1].c_str()); // unsigned long, by way of multiple nested macros.
Log::WriteLine("Hash: %d", hash);

Тогда %d такой же коричневый, как и остальная часть строки, и ошибки проверки нет.

Эти две вещи важны. Есть ли способ сделать эту работу правильно? Я новичок в C ++ после десятилетнего опыта. NET, и кривая обучения огромна. Я думал, что начну с основ, просто с простой системы регистрации, чтобы я мог видеть, что выводится в любое время, чистым и элегантным способом. В C# это заняло бы всего пару минут, но в C ++ теперь, спустя четыре часа, у меня открыто около 40 вкладок в трех windows из Firefox, и я все еще разбиваю моя голова против каждой кирпичной стены, с которой можно столкнуться.

Я пытался определить макрос внутри пространства имен:

    #define WriteLine(_Format, ...) printf(_Format, __VA_ARGS__)

Но он говорит, что не может найти printf. Я пытался украсить _Printf_format_string_:

    extern int _cdecl DebugInfo(_Printf_format_string_ const char* _Format, ...);

Но это не имеет значения. Я пытался __attribute__((format(printf, 1, 2)))

    extern void WriteLine(const char* _Format, ...) __attribute__((format(printf, 1, 2)));

Но это вызывает массу ошибок в ожидании {, unexpected identifier и expected a declaration, но не говорит, где и почему, или как.

Я слишком много спрашиваю в C ++, для такого базового c запроса?

1 Ответ

1 голос
/ 19 января 2020

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

<iostreams> Решение

По умолчанию std::cout синхронизируется с printf, поэтому Возможно, вам не нужно иметь дело с printf напрямую. Существует множество решений о том, как добиться различных результатов с помощью библиотеки библиотек iostreams, хотя это немного хлопотно для использования. Он безопасен для типов, поэтому не требует дополнительной поддержки IDE для непосредственного отображения ошибок.

Обратите внимание, что iostreams имеет довольно плохую репутацию из-за его сложности и простоты использования.

Typesafe Variadi c Template

Вы можете использовать шаблон variadi c по строкам :


void add_format_specifier(std::ostream& out, int const&) {
    out << "%d";
}

void add_format_specifier(std::ostream& out, char const*) {
    out << "%s";
}

void add_format_specifier(std::ostream& out, hex const&) {
    out << "%x";
}

template<typename... Args>
void WriteLine(Args&&... args) {
    std::ostringstream format;
    ((void)0, ..., add_format_specifier(format, args));
    printf(format.str().c_str(), args...);
}

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

Проверка времени компиляции строка формата

возможно добавить пользовательскую проверку спецификаторов формата во время компиляции, которую затем можно static_assert. Базовые строительные блоки basi c, которые вам нужны, - это шаблон variadi c, который также принимает ваш спецификатор формата (в основном вам нужно сохранить типы внутри вашей функции variadi c), и который вызывает спецификатор формата вдоль строк :

template<std::size_t N>
consteval bool check_format(char const (&format)[N], std::size_t i) {
    for(; i < N; ++i) {
        if(format[i] == '%') {
            if(i + 1 >= N) {
                return false;
            }
            if(format[i + 1] == '%') {
                ++i; // skip literal '%'
            } else {
                return false; // no more specifiers expected
            }
        }
    }
    return true;
}

template<std::size_t N, typename T, typename... Args>
consteval bool check_format(char const (&format)[N], std::size_t i) {
    for(; i < N; ++i) {
        if(format[i] == '%') {
            if(i + 1 >= N) {
                return false; // unterminated format specifier
            }
            if(format[i + 1] == '%') {
                ++i; // skip literal '%'
            } else {
                if constexpr(std::is_same_v<T, int>) {
                    // quickly check if it is an acceptable integer format specifier
                    if(format[i + 1] != 'd' && format[i + 1] != 'x') {
                        return false;
                    } else {
                        return check_format<N, Args...>(format, i + 2);
                    }
                } else {
                    return false; // unknown format specifier
                }
            }
        }
    }
    return false;
}

См. здесь , чтобы узнать больше контекста. (Примечание. В этом примере используется спецификатор C ++ 20 consteval, который может не поддерживаться вашим компилятором. Подобные эффекты могут быть достигнуты с помощью constexpr.)

Это решение даст вам возможность ошибки времени, но нет подсветки синтаксиса - C ++ не может повлиять на то, как ваша IDE dr aws строки (пока;)).

Решение для макроса

Хотя ваш макрос #define WriteLine(_Format, ...) printf(_Format, __VA_ARGS__) на самом деле не позволяет вам делать все это в отношении реализации чего-либо, кроме переименования printf, будет работать при условии, что код пользователя также включает <cstdio>. Если вы предоставите заголовок с этим макросом, вы можете просто добавить в него sh для простоты использования.

Необходимо сделать лишь небольшое улучшение, так что макрос также работает для звонков вида WriteLine("abc"):

#include <cstdio>
#define WriteLine(_Format, ...) printf(_Format __VA_OPT__(,) __VA_ARGS__)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...