Re[5]: Про канонический алгоритм парсинга Markdown и подобно
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 14.01.25 22:22
Оценка:
Здравствуйте, Shmj, Вы писали:

M>>Что "это"? Мой набросок? Нет, не будет. Он вообще работать не будет, он про иллюстрацию, как бы я делал автомат. А как ты сделаешь, я не знаю


S>Я имею в виду саму идею — у вас состояние одномерное — enum, который может иметь только одно значение. Если я правильно понял, то такое уже не получится:


S>
S>_**test**_

S>_**te_ st**

S>*test* - курсив
S>**test** - жирный
S>***test*** - жирный курсив
S>



Получится. Внимательнее посмотри. У меня есть отдельные состояния для одной звездочки, двух звёздочек, и тп. Само собой, что надо вовремя выплёвывать накопленное в лексере дальше в парсер.


S>а в моем варианте — работает. Причем 3 строчки кода без сложной логики и регулярок.


У тебя состояние размазано, отдельно текущий символ, отдельно — количество его повторений. У меня это отражено в enum. Но код у тебя в любом случае говно. Если уж не хочется плодить много состояний, то можно так:

class MdLexer
{

    char                 tokenChar = 0;
    std::size_t          charCnt   = 0;

    static bool isSpecialChar(char ch)
    {
        return ch=='\'' || ch=='*' || ch=='_' || ch=='`' || ch=='\"' || ch=='#'; // Можно отдельным параметром шаблона сделать, как Traits
    }

public:

    template<typename TokenHandler>
    void operator()(char ch, const TokenHandler &tokenHandler)
    {
        // по нулевому символу - финализируем, сбрасываем кешированное, если было
        // Финализировать можно без проблем много раз, финализация просто сбрасывает текущее состояние в парсер
        // Если входной поток символов какой-то из говна, и там могут быть нулевые символы, и это не было проверено на входе
        // то ничего тут не сломается
        if (ch==0) 
        {
            if (tokenChar!=0)
                tokenHandler(tokenChar, charCnt, true);

            // Нулевой символ не прокидываем, зачем парсеру геммор перекидывать?
            return;
        }

        if (tokenChar==ch)
        {
            // повторение символа
            ++charCnt;
            return;
        }

        // Какой-то другой символ пришел, не тот, что мы коллекционируем.

        // Сбросили в парсер то, что было накоплено, если было
        if (tokenChar!=0)
            tokenHandler(tokenChar, charCnt, true);

        // Обнуляем накопления
        tokenChar = 0;
        charCnt   = 0;

        if (!isSpecialChar(ch))
        {
            tokenHandler(ch, 1, false); // прокинули просто символ
            return;
        }

        // Стартуем накопление спец символов
        tokenChar = ch;
        charCnt   =  1; // Один символ таки накопили сразу при его поступлении

    }

}; // class MdLexer


template<typename TokenHandler, typename MarkdownCharsContainer>
void parseMarkdown(const MarkdownCharsContainer &mdc, const TokenHandler &tokenHandler)
{
    MdLexer lexer;
    for(auto ch: mdc)
        lexer(ch, tokenHandler);
    lexer(0, tokenHandler); // Финализируем

}


В принципе, то же самое, что и у тебя, только гораздо более универсальное.

Только не забывай, что у тебя есть зависимость от контекста. Так, три бэктика, если они с новой строки (и впереди только опционально пробелы) — то это режим листинга, и там отключается разбор маркдауна, но включается режим подсветки синтаксиса (язык указывается сразу после бэктиков).

Это можно добавить в мой лексер выше, а можно учитывать в парсере. Я бы пошел по второму варианту. Потому что токенизация во входном потоке ничего не меняет, просто иногда группирует одинаковые символы, и их при желании в зависимости от режима так же легко разгруппировать. В более сложных случаях, как, например, разбор плюсового кода и парсинг шаблонов с угловыми скобками, и с угловыми повторяющимися скобками — это лучше делать на уровне лексера, с обратной связью от парсера к лексеру — являются ли угловые скобки отдельными спец операторными символами, или это просто "больше", "меньше", или оператор сдвига.

Ну, и в спец символы, для простоты, можно также добавить пробелы, табы, и переводы строки, проще будет в парсере это обрабатывать.

ЗЫ Вообще, глядя на твой код, я понимаю, что все твои десятки тем и вопросов по программированию, а также данные тебе там ответы — это всё не пошло тебе на пользу
Маньяк Робокряк колесит по городу
Отредактировано 14.01.2025 22:30 Marty . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.