Про канонический алгоритм парсинга Markdown и подобного
От: Shmj Ниоткуда  
Дата: 13.01.25 10:26
Оценка:
Вроде классически для разбора таких текстов как Markdown применяют типа конечный автомат:

  Скрытый текст
TextSpan markdown2TextSpan(String markdownText) {
  final spans = <TextSpan>[];
  final buffer = StringBuffer();

  int asteriskCount = 0;
  bool isItalic = false;
  bool isBold = false;

  for (int i = 0; i < markdownText.length; i++) {
    final char = markdownText[i];

    if ("*" == char) {
      if (asteriskCount < 2) {
        asteriskCount++;
        continue;
      }

      buffer.write(char);
      continue;
    }

    if (asteriskCount > 0) {
      if (buffer.isNotEmpty) {
        spans.add(_buildTextSpan(buffer.toString(), isItalic, isBold));
      }

      if (1 ==  asteriskCount) isItalic = !isItalic;
      if (2 ==  asteriskCount) isBold = !isBold;

      buffer.clear();
      asteriskCount = 0;
    }

    buffer.write(char);
  }

  if (buffer.isNotEmpty) {
    spans.add(_buildTextSpan(buffer.toString(), isItalic, isBold));
  }

  return TextSpan(children: spans);
}


Но это не точно, голова сейчас не работает — могут быть ошибки тут.

Дал задание GPT — он сначала решил с помощью хитровумных регулярных выражений. Минус такого подхода — регулярки сложно поддерживать, если будут добавляться новые теги. В таком алгоритме как у меня все намного проще.

Попросил без регулярных выражений — он начал как бы заглядывать вперед, делать i+1 и т.д. Т.е. не понял как сделать проще. Когда попросил без i+1 — то он такого наворотил, раз в 5 больше кода и ошибку там найти стало не реально.

Вопрос у меня такой — где-то описан этот алгоритм парсинга? Узнаете ли вы его?
=сначала спроси у GPT=
Отредактировано 13.01.2025 10:31 Shmj . Предыдущая версия . Еще …
Отредактировано 13.01.2025 10:27 Shmj . Предыдущая версия .
Отредактировано 13.01.2025 10:26 Shmj . Предыдущая версия .
Re: Про канонический алгоритм парсинга Markdown и подобного
От: пффф  
Дата: 13.01.25 10:41
Оценка: +1
Здравствуйте, Shmj, Вы писали:

S>Вроде классически для разбора таких текстов как Markdown применяют типа конечный автомат:


S>
  Скрытый текст


Маркдаун — это не только и не столько звёздочки


S>Вопрос у меня такой — где-то описан этот алгоритм парсинга? Узнаете ли вы его?


Диалектов маркдауна и парсеров его — кучи
Re[2]: Про канонический алгоритм парсинга Markdown и подобного
От: Shmj Ниоткуда  
Дата: 13.01.25 10:55
Оценка:
Здравствуйте, пффф, Вы писали:

П>Маркдаун — это не только и не столько звёздочки


Что мешает линейно добавлять другие управляющие символы?

S>>Вопрос у меня такой — где-то описан этот алгоритм парсинга? Узнаете ли вы его?

П>Диалектов маркдауна и парсеров его — кучи

Да вопрос не в этом же...
=сначала спроси у GPT=
Re: Про канонический алгоритм парсинга Markdown и подобного
От: Pzz Россия https://github.com/alexpevzner
Дата: 13.01.25 11:40
Оценка: +2
Здравствуйте, Shmj, Вы писали:

S>Вроде классически для разбора таких текстов как Markdown применяют типа конечный автомат:


Это ты у Marty спроси. Он большой специалист по маркдауну.

S>Дал задание GPT — он сначала решил с помощью хитровумных регулярных выражений. Минус такого подхода — регулярки сложно поддерживать, если будут добавляться новые теги. В таком алгоритме как у меня все намного проще.


GPT твой — он делает, "как все". Потому, что его учили на всехних текстах. А все теперь зачем-то любят регулярки, толком их не понимая.

Хорошая иллюстрация к вопросу, заменит ли GPT живых человеческих разработчиков.
Re: Про канонический алгоритм парсинга Markdown и подобного
От: Кодт Россия  
Дата: 13.01.25 17:59
Оценка: 7 (2)
Здравствуйте, Shmj, Вы писали:

S>Вроде классически для разбора таких текстов как Markdown применяют типа конечный автомат:

S>Вопрос у меня такой — где-то описан этот алгоритм парсинга? Узнаете ли вы его?

Автомат лексического разбора. Без заглядывания вперёд там будут откладывания решений назад.
Типа, встретилась звёздочка — переходим в состояние "то ли это первый символ лексемы две звёздочки, то ли три звёздочки, то ли одинокая звёздочка в тексте". Ну и так далее.

А уже потом будешь синтаксическим разборщиком смотреть, что две звёздочки в начале соответствуют двум звёздочкам в конце жирного блока, или же это непарная и потому относящаяся просто к тексту.
Или что там можно мешать скобки как угодно: plain ** bold * bold italic ** italic * plain...

На регекспах сделать читаемый и универсальный код лексера, в принципе, не так уж и сложно.
Выписать все специальные строчки разметки,
— сделать группу с вариантами (\*\*|\*|etc) — далее назову этот паттерн MARKUP, — естественно, надо более длинные разметки поместить впереди более коротких, чтобы жадный алгоритм выгребал их полностью
— сделать группу простого текста с негативным забеганием, (. (?! MARKUP))+ — далее назову этот паттерн PLAINTEXT
— и объединить их, и дать группам имена ((?<markup> MARKUP) | (?<plaintext> PLAINTEXT))*

Всё, вы великолепны! И пусть у движка регекспа голова болит, как из этого недетерминированного автомата сделать детерминированный.
Негативное забегание всё ещё остаётся в регулярной грамматике. Вот заглядывание назад — это уже контекстно-зависимая грамматика.

Кажется, писать такое руками для чего-то более сложного, чем одна и две звёздочки, на голом C/C++/C#/что там больше нравится — это порнография.
Я не знаток, на каких библиотеках сейчас парсеры пишут. В конце концов, старая добрая кодогенерация lex+bison...
Перекуём баги на фичи!
Re[2]: Про канонический алгоритм парсинга Markdown и подобного
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 13.01.25 21:31
Оценка:
Здравствуйте, Pzz, Вы писали:

S>>Вроде классически для разбора таких текстов как Markdown применяют типа конечный автомат:


Pzz>Это ты у Marty спроси. Он большой специалист по маркдауну.


Да ну, какой я специалист... Так, решил запилить небольшую нашлёпку поверх маркдауна, а сам маркдаун я не разбираю пока, хотя это в планах тоже есть
Маньяк Робокряк колесит по городу
Re: Про канонический алгоритм парсинга Markdown и подобного
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 13.01.25 22:32
Оценка: 8 (1)
Здравствуйте, Shmj, Вы писали:

S>Вроде классически для разбора таких текстов как Markdown применяют типа конечный автомат:


Кодт выше всё неплохо расписал, в общем.

Если писать автомат (что Кодт назвал порнографией, но лично я бы так и сделал), то писать его именно так, как написал Кодт, а не так, как у тебя. У тебя уже какая-то копрофилия.

Например:

enum State
{
  stInitial
  stWaitSecondAsterisc,
  stWaitSecondUndersore,
  stWaitSecondbacktick,
  stWaitThirdbacktick,
  //...

};

void handleChar(char ch, State &st)
{
  switch(st)
  {
    case stInitial:
        // Или тут тоже switch
        if (ch=='*')
            st = stWaitSecondAsterisc;
        else if (ch=='_')
            st = stWaitSecondUndersore;
        else if (ch=='`')
            st = stWaitSecondbacktick;
        else
            {} // Процессим тут остальное
        break;

    case stWaitSecondAsterisc:
        if (ch=='*')
        {
            // тут выплевываем двойной астерикс
            st = stInitial;
        }
        else
            {} // Процессим тут остальное
        break;

    case stWaitSecondUndersore:
        if (ch=='_')
        {
            // тут выплевываем двойной андерскор
            st = stInitial;
        }
        else
            {} // Процессим тут остальное
        break;

    case stWaitSecondbacktick:
        if (ch=='`')
            st = stWaitThirdbacktick;
        else
            {} // Процессим тут остальное
        break;

    case stWaitThirdbacktick:
        if (ch=='`')
        {
            // тут выплевываем тройной бэктик
            st = stInitial;
        }
        else
            {} // Процессим тут остальное
        break;

    // ...
  }
}


Я бы разбил на две фазы — сначала "токенизируем" в токены, потом делаем второй автомат синтаксического анализатора.

Это классические фазы парсинга — сначала лексер (токенизер), потом семантический анализатор на выхлопе лексера.

Это можно сделать и разными инструментами. Я в своё время пробовал flex/bison для своего первого парсера плюсиков, который 0x03ие плюсики раскрашивал в HTML, но оно очень неудобно. Потом ещё рагель пытался юзать, но он тоже не удобен. Для рагеля я в своё время нашел тулзу в сорцах ABNFGEN, и подпиливал её под себя, это было поудобнее, но тоже хрень неудобная получилась, когда попытался что-то посложнее сделать. Сейчас, говорят, ANTLR массово используется. Я пробовал осилить теорию формальных грамматик, или как оно там, книгу дракона прочитал, но там об этом не особо было, потом ещё пару каких-то учебников вузовских, но все эти LL(N)/LR(N) как-то не раскурил. Кстати, если кто что посоветует на эту тему для совсем тупых, буду признателен.

Ещё можно врукопашку рекурсивным спуском налабать. Я такое делал по мотивам книжки Герберта Шилдта по сишечке (она у меня была ещё в распечатке на матричном принтере) — тут в принципе, всё просто, но писать долго и скучно. И если грамматика меняется (или, даже, просто приоритет операторов), дофига переписывать. Тут Sinclair про PEG расказывает, как я понял, оно само на основе правил генерит разбор рекурсивным спуском, и вроде даже удобно, но хз, есть ли для плюсов.

Мне больше нравится разбить поток символов на токены/лексемы, и потом творить с ними любую дичь — так, я как-то делал одну штуку, и уж не помню зачем, но мне захотелось поменять приоритеты операторов, сделать не так, как в плюсах. Я вроде что-то для асма специфического делал, и там вроде было с приоритетами по-другому. Я тогда написал парсер потока токенов, который строит дерево выражения с учетом передаваемой ему таблицы приоритетов операторов, и поменять приоритеты можно было хоть на лету — грубо говоря, в языке заводим директиву $asm_pri — выражения считаются по асмовским правилам, или $cpp_pri — выражения считаются по правилам плюсов. Мне это понравилось. Прикольно и гибко, хотя, конечно, хз зачем нужно, но такого с инструментариями автоматической генерации достичь очень сложно, если вообще возможно


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

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


S>Дал задание GPT — он сначала решил с помощью хитровумных регулярных выражений. Минус такого подхода — регулярки сложно поддерживать, если будут добавляться новые теги. В таком алгоритме как у меня все намного проще.


Минус такого подхода — GPT тебе правдоподобной херни нальёт полную панамкутележку, сиди потом, разбирайся, чего он там накосячил.


S>Вопрос у меня такой — где-то описан этот алгоритм парсинга? Узнаете ли вы его?


Что значит — узнаете ли вы его?

Универсального алгоритма парсинга маркдауна нет.

Базовая спека по маркдауну — это CommonMark specification of Markdown.
Там же где-то ищи и Markdown.pl. Там как раз будут регулярки — на перле по другому никак ничего не сделать. ТОлько там перловый диалект регулярок, надо будет переводить в твой.


Ещё есть у нас Github Flavored Markdown — вроде вполне совместим с CommonMark, но добавляет некоторые расширения. GFM вполне совместим с тем, что юзается в GitLab, и также вполне совместим (кроме нескольких нюансов) с тем маркдауном, который поддерживает доксиген.

Как я понял, Github изначально был написал на Руби (на рельсах, наверное), и оттого там для рендеринга маркдауна используется написанный на Руби движок Jekyll, к нему только нужно доустановить пакет для GFM. Там, скорее всего, тоже регулярки, можно выдрать оттуда, только там, скорее всего, тоже свой диалект регулярок.

А какого-то каноничного парсера маркдауна нет и не будет. Каждая реализация в мелочах несовместима с другими.


Да, ещё есть VSCode и к нему разные плагины


Можно поковырять их сорцы
Маньяк Робокряк колесит по городу
Отредактировано 13.01.2025 22:42 Marty . Предыдущая версия . Еще …
Отредактировано 13.01.2025 22:33 Marty . Предыдущая версия .
Re[2]: Про канонический алгоритм парсинга Markdown и подобно
От: Shmj Ниоткуда  
Дата: 14.01.25 08:07
Оценка:
Здравствуйте, Marty, Вы писали:

M>
M>enum State
M>{
M>  stInitial
M>  stWaitSecondAsterisc,
M>  stWaitSecondUndersore,
M>  stWaitSecondbacktick,
M>  stWaitThirdbacktick,
M>  //...

M>};
M>


А будет ли это работать, если текст одновременно и жирный и курсив?

В Wiki _вроде **можно** сделать так_.
=сначала спроси у GPT=
Отредактировано 14.01.2025 8:08 Shmj . Предыдущая версия . Еще …
Отредактировано 14.01.2025 8:08 Shmj . Предыдущая версия .
Re[3]: Про канонический алгоритм парсинга Markdown и подобно
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 14.01.25 08:12
Оценка:
Здравствуйте, Shmj, Вы писали:

S>А будет ли это работать, если текст одновременно и жирный и курсив?


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


S>В Wiki _вроде **можно** сделать так_.


Я в курсе
Маньяк Робокряк колесит по городу
Re[4]: Про канонический алгоритм парсинга Markdown и подобно
От: Shmj Ниоткуда  
Дата: 14.01.25 08:23
Оценка:
Здравствуйте, Marty, Вы писали:

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


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

_**test**_

_**te_ st**

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


а в моем варианте — работает. Причем 3 строчки кода без сложной логики и регулярок.
=сначала спроси у GPT=
Отредактировано 14.01.2025 8:24 Shmj . Предыдущая версия .
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 . Предыдущая версия .
Re: Про канонический алгоритм парсинга Markdown и подобного
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 14.01.25 22:28
Оценка:
Здравствуйте, Shmj, Вы писали:

S>
  Скрытый текст
S>
S>TextSpan markdown2TextSpan(String markdownText) {
S>  final spans = <TextSpan>[];
S>  final buffer = StringBuffer();

S>  int asteriskCount = 0;
S>  bool isItalic = false;
S>  bool isBold = false;

S>  for (int i = 0; i < markdownText.length; i++) {
S>    final char = markdownText[i];

S>    if ("*" == char) {
S>      if (asteriskCount < 2) {
S>        asteriskCount++;
S>        continue;
S>      }

S>      buffer.write(char);
S>      continue;
S>    }

S>    if (asteriskCount > 0) {
S>      if (buffer.isNotEmpty) {
S>        spans.add(_buildTextSpan(buffer.toString(), isItalic, isBold));
S>      }

S>      if (1 ==  asteriskCount) isItalic = !isItalic;
S>      if (2 ==  asteriskCount) isBold = !isBold;

S>      buffer.clear();
S>      asteriskCount = 0;
S>    }

S>    buffer.write(char);
S>  }

S>  if (buffer.isNotEmpty) {
S>    spans.add(_buildTextSpan(buffer.toString(), isItalic, isBold));
S>  }

S>  return TextSpan(children: spans);
S>}
S>


Да — код — говно. Так пишут первокуры или чат гопота
Маньяк Робокряк колесит по городу
Re[6]: Про канонический алгоритм парсинга Markdown и подобно
От: Shmj Ниоткуда  
Дата: 14.01.25 23:18
Оценка:
Здравствуйте, Marty, Вы писали:

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


А что если и звездочка и подчеркивание открыты — это какое состояние? Будете перемножать?
=сначала спроси у GPT=
Re[7]: Про канонический алгоритм парсинга Markdown и подобно
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 14.01.25 23:31
Оценка: -1
Здравствуйте, Shmj, Вы писали:

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


S>А что если и звездочка и подчеркивание открыты — это какое состояние? Будете перемножать?


Ты идиот? Обработаю согласно правилам MD (которые, кстати, у разных диалектов MD работают по-разному)
Маньяк Робокряк колесит по городу
Re: Про канонический алгоритм парсинга Markdown и подобного
От: Dimonka Верблюд  
Дата: 15.01.25 17:18
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Вроде классически для разбора таких текстов как Markdown применяют типа конечный автомат:


S>Но это не точно, голова сейчас не работает — могут быть ошибки тут.


S>Дал задание GPT — он сначала решил с помощью хитровумных регулярных выражений. Минус такого подхода — регулярки сложно поддерживать, если будут добавляться новые теги. В таком алгоритме как у меня все намного проще.


Первые попытки парсеров маркдауна у меня были примерно такие же, но потом постоянно всплывают "нюансы". Причём GPT мне сразу говорил — не парься, а возьми готовую библиотеку, благо их есть. В конечном итоге прислушался к его советам.
Re: Про канонический алгоритм парсинга Markdown и подобного
От: Sinclair Россия https://github.com/evilguest/
Дата: 18.05.25 06:53
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Вроде классически для разбора таких текстов как Markdown применяют типа конечный автомат:


Классически все формальные языки делят на три группы: контекстно-зависимые, контекстно-свободные, и регулярные.
Сложность разбора каждого из классов радикально различается.
Поскольку примерно все реальные формальные языки относятся к КЗ-классу, прямой разбор которого слишком тяжёл, классический разбор делается в три этапа:
1. Токенизация (лексический анализ). Строим промежуточный "язык", который является регулярным. Разбирается конечным автоматом. На входе — поток символов, на выходе — поток лексем (токенов).
2. Парсинг (синтаксический анализ). Строим промежуточный "язык", который является контекстно-свободным. Разбирается конечным автоматом с магазинной памятью. На входе — поток лексем, на выходе — дерево разбора.
3. Семантический анализ. Проверяем, что то, что получилось на предыдущем этапе, соответствует некоторой программе на реальном КЗ-языке. Делается при помощи рукопашных правил. Классически — как цепочка трансформаций дерева и построений таблиц символов. Общего решения нет, пилят кто во что горазд. Кто-то подпиливает язык для того, чтобы семантический анализ был однопроходным (и тогда его вообще можно объединить с предыдущей фазой). Кто-то делает N-проходный анализ с фиксированным N, кто-то делает динамические алгоритмы, которые иногда даже могут свалиться в экспоненциально-сложные вычисления, но в среднем работают полиномиально.

Из недавних достижений в этой области — популяризация безлексерных парсеров. Во времена оны вторая фаза была дорогой в реализации, поэтому никому не хотелось выпиливать какой-нибудь LL(k) парсер со встроенным разбором идентификаторов/комментариев/строковых констант. Возможность применить DFA для токенизации и быстро обнаружить лексические ошибки была настолько искушающей, что и по сю пору часть народа не представляет себе других вариантов.
Оборотной стороной этого подхода являются дурацкие ограничения — вроде отсутствия поддержки вложенных комментов в более-менее всех C-подобных языках. Аналогичные сложности возникают примерно везде, где "токен" начинает иметь мало-мальски сложную структуру, типа интерполированных строк.
В общем, современное состояние желаний и возможностей устроено так, что удобнее применять генераторы безлексерных парсеров, способных строить эффективные магазинные автоматы для любых КС-языков, без необходимости вручную пилить последние на "регулярную" и "магазинную" части.
Вторая популярная проблема — ident-based parsing. Эта штука плохо работает с традиционными КС-парсерами, потому что является частным случаем КЗ. Известные мне решения — либо полностью ручной разбор, либо переход к МП-лексеру, который умеет вставлять в стрим токены ident/dedent.

С точки зрения этого подхода то, что вы пытаетесь сделать — это совместить "генерированный" токенизатор на основе регулярок с "рукопашным" КС-парсером.
Теоретически, вы можете даже достичь успеха на этом поприще. Особенно, если будете понимать, что именно вы делаете. Вы не один такой — в частности, VS Code (и многие другие языки) применяет для раскраски синтаксиса TextMate2. Это как раз язык, в котором токенизация делается регекспами, а разбор — рекурсивными правилами в КС-стиле.

На практике — я бы взял какой-нибудь взрослый генератор парсеров и построил решение сразу на нём. Если, конечно, есть нетерпимость к коробочным реализациям парсеров маркдауна, коих, АФАИК, завезено с избытком.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: По канонически
От: Stanislaw K СССР  
Дата: 18.05.25 07:29
Оценка:
Здравствуйте, Pzz, Вы писали:


S>>Дал задание GPT — он сначала решил с помощью хитровумных регулярных выражений. Минус такого подхода — регулярки сложно поддерживать, если будут добавляться новые теги. В таком алгоритме как у меня все намного проще.


Pzz>GPT твой — он делает, "как все". Потому, что его учили на всехних текстах. А все теперь зачем-то любят регулярки, толком их не понимая.


Pzz>Хорошая иллюстрация к вопросу, заменит ли GPT живых человеческих разработчиков.


А, кстати, интересный вопрос — сейчас ИИ натаскан на примерах, созданных человеками. Само оно ничего не придумывает нового, только комбинирует старое, чем чаще в старом встречается какой-то вариант, тем чаще он предлагается как надежное проверенное решение.

Следующие поколения ИИ будут натаскиваться на текстах созданных текущими ИИ. И значит на еще более частом повторе всё техже вариантов.

Значит ли это что прогресс остановился?
Все проблемы от жадности и глупости
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.