Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Минеев Максим #243

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

mineevmaxim
Copy link

@mineevmaxim
Copy link
Author

Комментарий к архитектуре:
У нас есть три главные сущности:

  • Lexer
  • Parser
  • Converter
    По сути всё работает так:
    Строка MD -> (Lexer) > Токены -> (Parser) -> Абстрактное синтаксическое дерево AST -> (Converter) -> строка HTML

Схема архитектуры и зависимостей:
image


public class MarkdownToHtmlConverter(ILexer lexer, IParser parser)
{
public ILexer Lexer { get; } = lexer;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

можно сделать private

{
Console.WriteLine("Hello, World!");
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

кажется нет необходимости и можно убрать


namespace Markdown.Tokens;

public abstract class Token(int position) : IToken

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А зачем интерфейс для абстрактного класса? Интерфейс нужен для того чтобы описать поведение, но ведь абстрактный класс тоже это делает

public abstract string Value { get; }
public int Position => position;
public int Length => Value.Length;
public int GetIndexToNextToken() => Position + Length;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

нет использований

public List<IToken> Tokenize(string input)
{
position = 0;
var nestingStack = new Stack<string>();
Copy link

@masssha1308 masssha1308 Dec 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше position сделать локальной переменной чтобы была одинаковая логика работы с position, nestingStack, input

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если position сделать локальной переменной, то возникнет несколько неудобств:

  1. Придется прокидывать текущую позицию практически во все функции, увеличится список параметров
  2. Сейчас логика сдвига позиции находится максимально близко к самим "действиям", т.е. как только мы совершили какое-то действие (например, спарсили текст), мы сразу сдвигаем позицию в том же методе парсинга. Если будем передавать position в метод параметром, то нам придется либо передавать его ссылкой (ref), либо возвращать из метода необходимый сдвиг или еще как-то ухищряться, чтобы правильно сдвинуть position

Поэтому мне кажется, что более красиво и аккуратно будет оставить position полем класса, а чтобы сделать логику работы более одинаковой, можно вынести стек тоже полем класса и не передавать его каждый раз параметром (с инпутом так поступить не можем, потому что получем его параметром в методе, вынести инициализацию в конструктор тоже не можем, потому что нарушим контракт метода Tokenize и не сможем получать в конвертере не конкретную реализацию лексера, а интерфейс)

position += 2;
}

private bool NextIsDoubleGround(string input) =>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Здесь напрашивается сделать инпут valueObject'ом и все эти методы перетащить туда

{
private int position;
private readonly List<IToken> tokens = [];
private const string DoubleGround = "__";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

заметила что некоторые символы инициализированы дважды (например, DoubleGround в MarkdownLexer и в MarkdownParser). Исходя из этого предложение вынести эти константы в отдельный класс и использовать везде его

stack.Push(Ground);
}

private void ParseItalicOrBoldAndAdvanceWhenStackHasOne(bool isSingleGround, bool isDoubleGround,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

сейчас реализована такая логика, что мы кладем токен в стек и в зависимости от последующих токенов мы токен из стека можем убрать, что привело к усложнению кода (ParseItalicOrBoldAndAdvanceWhenStackHasOne, ParseItalicOrBoldAndAdvanceWhenStackHasTwo и т.д.).
можем ли мы анализировать последующие символы чтобы сразу добавлять корректные токены?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

На самом деле в стек всегда сразу добавляются корректные токены (открывающие токены в соответствии с разрешенной вложенностью), но трудность заключается в следующем: в процессе лексического анализа нам надо правильно (хотя бы частично) обрабатывать вложенность, чтобы на этапе парсинга получать уже "правильный" набор токенов (___text___ должен парситься лексером в ['__', '_', 'text', '_', '__'], а не, например, в ['_', '__', 'text', '_', '__']). Чтобы решать проблемы с вложенностью, я, по аналогии с популярной задачей про валидность скобочной последовательности, собирался просто закидывать в стек открывающие теги, а когда встречаем закрывающий, то достаем открывающий из стека, но из-за того, что тег курсива явялется частью тега полужирного, добавлять и доставать открывающие теги оказалось не так уж "просто" и приходится проверять очень много условий.

Изначально вся логика обработки этой вложенности была написана в ParseItalicOrBoldAndAdvance, но метод вырос до +-150 строк кода и очень глубокой вложенностью if-ов, и, чтобы хоть немного его уменьшить и сделать более декларативным я разнес эту логику по разным функциям, но получились такие страшные названия (называл прямо "в лоб", ровно то, что делает функция, возможно можно было назвать более удачно). В итоге я так и не придумал что-то лучше, чтобы код был более понятным и не нагруженным, мне показалось, что в текущем состоянии функции получились в достаточной мере декларативными, но с очень длинными названиями (от этого избавиться мне не удалось)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется большинство проблем бы решилось и код упростился, если бы в стек клали не строки (двойное или одинарное подчеркивание), а char (только одинарные подчеркивания)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется большинство проблем бы решилось и код упростился, если бы в стек клали не строки (двойное или одинарное подчеркивание), а char (только одинарные подчеркивания)

Возможно, подумаю над этим

private void ParseHeadingAndAdvance(string input)
{
if (NextIsSpace(input) && IsStartOfParagraph(input)) tokens.Add(new HeadingToken(position++));
else tokens.Add(new TextToken(position, "#"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

лучше константу заиспользовать

}

private void ParseItalicOrBoldAndAdvanceWhenStackHasOne(bool isSingleGround, bool isDoubleGround,
bool isTripleGround,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

вместо булевых флагов можно передавать int

var isDoubleGround = input.NextIsGround(position);
var isTripleGround = input.NextIsDoubleGround(position);
var isSingleGround = !isTripleGround && !isDoubleGround;
if (stack.Count == 0) ParseItalicOrBoldAndAdvanceWhenStackEmpty(isSingleGround, isTripleGround, stack);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

можно заменить на switch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants