Skip to content

Conversation

@nbdevncrs
Copy link

@SquirrelLeonid
Copy link

Давай тут отвечу для истории. Сначала непосредственно по твоим вопросам.

Я создал TokenPosition, который определяет позицию токена в изначальном тексте, но это нужно исключительно для тестирования, чтобы какое-то конкретное место выкидывать для наглядности, то есть функциональности не несет. в таком случае его лучше оставить, убрать, или без разницы?

Отрезать лишнее всегда легче, чем пришить необходимое. Если этот участок кода не мешает препятствует выполнению задачи, то ничего такого в том, чтобы он пока был. Но сдается мне, что этот класс в том или ином виде тебе может пригодиться.

У меня получилось реально много разных классов для нодов, большая часть наследуется даже от абстрактных классов BlockNode и InlineNode, которые относятся к INode, то есть разделяются на блочные и строчные. для меня это выглядит неприятно, что есть так много классов, но при этом сама логика выглядит вполне нормальной, и я не знаю как это улучшить/поправить, или может в целом попусту парюсь. в общем, был бы рад, если бы ты смог на ревью с этим моментом помочь или расписать чуть подробнее.

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

@SquirrelLeonid
Copy link

Tokenizer

CharStream

Использование Stream в имени как бы наталкивает на мысль о том, что объект должен освобождать какие-то ресурсы (как минимум реализовывать IDisposable). В действительности нам здесь не нужно ничего освобождать. Я бы предложил поменять имя на CharCursor или TextCursor.
В целом, наверное такой вопрос по этому классу - он действительно необходим? С текущим скелетом кажется, что можно обойтись простым обходом for по строке. Я не предлагаю прям сейчас его удалять - возможно он обрастет какими-то деталями при реализации.

TokenType

Какие значения будут представлены в перечислении TokenType?

TokenPosition

Текст можно рассматривать с разных сторон в зависимости от текущего контекста и задачи. К примеру:

  • Если мы работаем с логами работы приложения, то нам удобно рассматривать текст как набор строк, где каждая строка представляет собой отдельную запись в логе (дата, уровень сообщения, кто отправил, текст ошибки и т.п.).
  • Или мы можем рассматривать текст как последовательность символов. Ведь новая строка не что иное как пара символов \r\n (если говорить о Windows)

В рамках этой задачи нам может быть удобнее рассматривать текст именно во втором варианте, ведь каждый отдельный символ для нас может иметь значение

Parsing

Декомпозиция выглядит неплохо. Вижу, что учтены не только отдельные теги, но и область их действия (тег может / не может входить в другой тег). Один момент:

  1. Я бы предложил подумать над вариантами имен для BlockNode и InlineNode. Вроде бы понятно, что они под собой подразумевают, но, кажется, смысл немного ускользает.

В целом по декомпозиции

Из текущего скелета видно, как оно может прийти к итоговому решению. Над декомпозицией хорошо поработал.
Для меня пока остаются открытыми два момента:

  1. Вопрос о перечислении TokenType (смотри выше)
  2. Вопрос о том, как обрабатывать пересечения между тегами (когда один может исключать другой, например). Хотя в целом могу представить, что логика может быть размещена где-то на уровне Parser

Предпроверку засчитываю. В итоговом решении обязательно покрой код тестами (тут действительно отличная практика для TDD)

Copy link

@SquirrelLeonid SquirrelLeonid left a comment

Choose a reason for hiding this comment

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

Оставил множество замечаний по стилистике кода, именованию переменных и т.д.
Ключевых момента три:

  1. Постараться упростить UnderscoreHandler
  2. Аналогично для LinkHandler
  3. Сделать рефакторинг в классе HtmlRenderer. Я бы сказал, что у этого наивысший приоритет.

В остальном решение весьма неплохое на мой взгляд, но шлифануть код надо.

using Parsing;
using Rendering;

public class Md

Choose a reason for hiding this comment

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

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


public static class EscapeHandler
{
public static void HandleEscape(List<InlineTypeNode> children, ParserCursor cursor, IList<Token> tokens)

Choose a reason for hiding this comment

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

Есть неиспользуемый параметр

public static void HandleLink(List<InlineTypeNode> children, ParserCursor cursor, IList<Token> tokens)
{
var link = TryParseLink(cursor, tokens);
if (link != null) children.Add(link);

Choose a reason for hiding this comment

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

Не ошибка, но я предпочитаю размещать if и строку кода в отдельных строках. Тут на твое усмотрение.
В других таких ситуациях аналогично.

}
}

private static LinkNode? TryParseLink(ParserCursor cursor, IList<Token> tokens)

Choose a reason for hiding this comment

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

Если не ошибаюсь, то знак вопроса в LinkNode? можно вообще опустить. Это и так ссылочный тип.

Попробуй в .csproj убрать строку с добавлением nullable.


private static LinkNode? TryParseLink(ParserCursor cursor, IList<Token> tokens)
{
var rightBracket = FindTokenBeforeEol(tokens, cursor.Index + 1, TokenType.RightBracket);

Choose a reason for hiding this comment

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

Если переменная хранит индекс, то в имени это тоже стоит отразить. Т.е. будет лучше rightBracketIndex, например. Другие переменные тоже посмотри на такое проявление

{
ArgumentNullException.ThrowIfNull(text);

var cursor = new CharCursor(text);

Choose a reason for hiding this comment

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

тоже можно уточнить имя до charCursor. Упрощаем чтение кода, когда уйдем далеко от этой строчки


while (!cursor.End)
{
var c = cursor.Current;

Choose a reason for hiding this comment

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

лучше избегать таких простых названий. Хоть и очевидно, что это текущий символ, но будет хорошо так его и назвать

return tokens;
}

private static void ReadText(List<Token> tokens, CharCursor cursor)

Choose a reason for hiding this comment

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

Лучше избегать Side-эффектов, в частности наполнения листа. Будет лучше сменить сигнатуру метода так, чтобы он возвращал полученный токен. Ну и соответственно название поменять согласованно с новой сигнатурой.

tokens.Add(new Token(TokenType.Text, sb.ToString()));
}

private static bool IsTextChar(char c)

Choose a reason for hiding this comment

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

Могу предложить написать альтернативный подход, записав значения один раз в словарь или HashSet.
Тогда в месте вызова этого метода можно будет перейти на вызов Contains

@@ -0,0 +1,17 @@
namespace Markdown.Tokenizing;

public enum TokenType

Choose a reason for hiding this comment

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

Тут подсвечу два момента.

  1. Полезной практикой является явная нумерация значений в перечислении.
    Это полезно несколькими вещами:
    а) Явный контроль над числовым представлением того или иного значения (enum по сути - набор целочисленных констант)
    б) Существенно сокращает вероятность ошибки в случаях, когда ты пишешь маппинг из сущностей одного уровня (транспортный), в сущности другого уровня (бизнес-логика). Напиши мне отдельно, если нужен пример
  2. Всегда полезно в качестве первого значения резервировать Unknown, со значением 0. Это способствует обратной совместимости

…nder method to INode, so nodes now implementing it, depending on render way they need
…uge logic between new functions, it also works with a cursor now, so no -1, 0, and 1 semantic in UnderscoreHandler.cs needed now. some fields now renamed
@SquirrelLeonid
Copy link

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

UnderscoreHandler тоже стало намного приятнее читать. На методы хорошо разбил. Семантика с -1 0 1 в каком то виде осталась, но теперь это вполне конкретная операция по поиску индекса, что гуд.

Задачу на максимальный балл засчитываю, но посмотри и другие комментарии.

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