Сообщение Re[5]: Про канонический алгоритм парсинга Markdown и подобно от 14.01.2025 22:22
Изменено 14.01.2025 22:30 Marty
Re[5]: Про канонический алгоритм парсинга Markdown и подобно
Здравствуйте, Shmj, Вы писали:
M>>Что "это"? Мой набросок? Нет, не будет. Он вообще работать не будет, он про иллюстрацию, как бы я делал автомат. А как ты сделаешь, я не знаю
S>Я имею в виду саму идею — у вас состояние одномерное — enum, который может иметь только одно значение. Если я правильно понял, то такое уже не получится:
S>
Получится. Внимательнее посмотри. У меня есть отдельные состояния для одной звездочки, двух звёздочек, и тп. Само собой, что надо вовремя выплёвывать накопленное в лексере дальше в парсер.
S>а в моем варианте — работает. Причем 3 строчки кода без сложной логики и регулярок.
У тебя состояние размазано, отдельно текущий символ, отдельно — количество его повторений. У меня это отражено в enum. Но код у тебя в любом случае говно. Если уж не хочется плодить много состояний, то можно так:
В принципе, то же самое, что и у тебя, только гораздо более универсальное.
Только не забывай, что у тебя есть зависимость от контекста. Так, три бэктика, если они с новой строки (и впереди только опционально пробелы) — то это режим листинга, и там отключается разбор маркдауна, но включается режим подсветки синтаксиса (язык указывается сразу после бэктиков).
Это можно добавить в мой лексер выше, а можно учитывать в парсере. Я бы пошел по второму варианту. Потому что токенизация во входном потоке ничего не меняет, просто иногда группирует одинаковые символы, и их при желании в зависимости от режима так же легко разгруппировать. В более сложных случаях, как, например, разбор плюсового кода и парсинг шаблонов с угловыми скобками, и с угловыми повторяющимися скобками — это лучше делать на уровне лексера, с обратной связью от парсера к лексеру — являются ли угловые скобки отдельными спец операторными символами, или это просто "больше", "меньше", или оператор сдвига.
Ну, и в спец символы, для простоты, можно также добавить пробелы, табы, и переводы строки, проще будет в парсере это обрабатывать.
ЗЫ Вообще, глядя на твой код, я понимаю, что все твои десятки тем и вопросов по программированию, а также данные тебе там ответы — это всё не пошло тебе на пользу
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); // Финализируем
}
В принципе, то же самое, что и у тебя, только гораздо более универсальное.
Только не забывай, что у тебя есть зависимость от контекста. Так, три бэктика, если они с новой строки (и впереди только опционально пробелы) — то это режим листинга, и там отключается разбор маркдауна, но включается режим подсветки синтаксиса (язык указывается сразу после бэктиков).
Это можно добавить в мой лексер выше, а можно учитывать в парсере. Я бы пошел по второму варианту. Потому что токенизация во входном потоке ничего не меняет, просто иногда группирует одинаковые символы, и их при желании в зависимости от режима так же легко разгруппировать. В более сложных случаях, как, например, разбор плюсового кода и парсинг шаблонов с угловыми скобками, и с угловыми повторяющимися скобками — это лучше делать на уровне лексера, с обратной связью от парсера к лексеру — являются ли угловые скобки отдельными спец операторными символами, или это просто "больше", "меньше", или оператор сдвига.
Ну, и в спец символы, для простоты, можно также добавить пробелы, табы, и переводы строки, проще будет в парсере это обрабатывать.
ЗЫ Вообще, глядя на твой код, я понимаю, что все твои десятки тем и вопросов по программированию, а также данные тебе там ответы — это всё не пошло тебе на пользу
Re[5]: Про канонический алгоритм парсинга Markdown и подобно
Здравствуйте, Shmj, Вы писали:
M>>Что "это"? Мой набросок? Нет, не будет. Он вообще работать не будет, он про иллюстрацию, как бы я делал автомат. А как ты сделаешь, я не знаю
S>Я имею в виду саму идею — у вас состояние одномерное — enum, который может иметь только одно значение. Если я правильно понял, то такое уже не получится:
S>
Получится. Внимательнее посмотри. У меня есть отдельные состояния для одной звездочки, двух звёздочек, и тп. Само собой, что надо вовремя выплёвывать накопленное в лексере дальше в парсер.
S>а в моем варианте — работает. Причем 3 строчки кода без сложной логики и регулярок.
У тебя состояние размазано, отдельно текущий символ, отдельно — количество его повторений. У меня это отражено в enum. Но код у тебя в любом случае говно. Если уж не хочется плодить много состояний, то можно так:
В принципе, то же самое, что и у тебя, только гораздо более универсальное.
Только не забывай, что у тебя есть зависимость от контекста. Так, три бэктика, если они с новой строки (и впереди только опционально пробелы) — то это режим листинга, и там отключается разбор маркдауна, но включается режим подсветки синтаксиса (язык указывается сразу после бэктиков).
Это можно добавить в мой лексер выше, а можно учитывать в парсере. Я бы пошел по второму варианту. Потому что токенизация во входном потоке ничего не меняет, просто иногда группирует одинаковые символы, и их при желании в зависимости от режима так же легко разгруппировать. В более сложных случаях, как, например, разбор плюсового кода и парсинг шаблонов с угловыми скобками, и с угловыми повторяющимися скобками — это лучше делать на уровне лексера, с обратной связью от парсера к лексеру — являются ли угловые скобки отдельными спец операторными символами, или это просто "больше", "меньше", или оператор сдвига.
Ну, и в спец символы, для простоты, можно также добавить пробелы, табы, и переводы строки, проще будет в парсере это обрабатывать.
ЗЫ Вообще, глядя на твой код, я понимаю, что все твои десятки тем и вопросов по программированию, а также данные тебе там ответы — это всё не пошло тебе на пользу
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); // Финализируем
}
В принципе, то же самое, что и у тебя, только гораздо более универсальное.
Только не забывай, что у тебя есть зависимость от контекста. Так, три бэктика, если они с новой строки (и впереди только опционально пробелы) — то это режим листинга, и там отключается разбор маркдауна, но включается режим подсветки синтаксиса (язык указывается сразу после бэктиков).
Это можно добавить в мой лексер выше, а можно учитывать в парсере. Я бы пошел по второму варианту. Потому что токенизация во входном потоке ничего не меняет, просто иногда группирует одинаковые символы, и их при желании в зависимости от режима так же легко разгруппировать. В более сложных случаях, как, например, разбор плюсового кода и парсинг шаблонов с угловыми скобками, и с угловыми повторяющимися скобками — это лучше делать на уровне лексера, с обратной связью от парсера к лексеру — являются ли угловые скобки отдельными спец операторными символами, или это просто "больше", "меньше", или оператор сдвига.
Ну, и в спец символы, для простоты, можно также добавить пробелы, табы, и переводы строки, проще будет в парсере это обрабатывать.
ЗЫ Вообще, глядя на твой код, я понимаю, что все твои десятки тем и вопросов по программированию, а также данные тебе там ответы — это всё не пошло тебе на пользу