Процесс токенизации в Marked

Токенизация — это первый этап обработки Markdown в библиотеке Marked, на котором исходный текст преобразуется в набор токенов. Каждый токен представляет собой логическую единицу синтаксиса Markdown: заголовок, абзац, список, ссылку, изображение, кодовый блок и так далее. Токенизация позволяет отделить структуру документа от его содержания, что значительно упрощает последующую генерацию HTML.

Лексический анализ текста

При разборе Markdown Marked использует лексический анализ. Исходный текст последовательно сканируется строка за строкой. Лексический анализ основан на регулярных выражениях, каждое из которых соответствует определённому синтаксическому элементу Markdown. Например:

  • Заголовки (#, ##, ### и т.д.) распознаются шаблоном, который ищет одну или несколько решёток в начале строки, за которыми следует текст.
  • Списки (*, -, + для маркированных списков и числа для нумерованных) распознаются шаблонами, которые учитывают отступы и символы списка.
  • Блоки кода определяются тройными обратными кавычками ``` или отступом в четыре пробела.

Каждое совпадение с регулярным выражением создаёт токен. Токен содержит тип элемента, содержимое, а иногда и дополнительные метаданные, например уровень заголовка или язык программирования для блоков кода.

Виды токенов

Marked использует следующие основные виды токенов:

  • heading — заголовки всех уровней. Токен содержит depth (уровень заголовка) и text.
  • paragraph — абзацы текста.
  • list — списки, включающие массив items. Каждый элемент списка также является токеном.
  • list_item — отдельный пункт списка, может содержать вложенные токены (например, параграфы или списки).
  • code — блоки кода с опциональной информацией о языке.
  • blockquote — блоки цитат, содержащие массив токенов.
  • hr — горизонтальная линия.
  • link — ссылка, содержащая href и title.
  • image — изображение с href и alt.
  • strong, em, codespan, br, del — inline-токены для форматирования текста.

Процесс построения токенов

  1. Инициализация лексера Создаётся экземпляр класса Lexer, которому передаётся исходный текст Markdown. Лексер хранит текущую позицию в тексте и последовательность токенов.

  2. Построчная проверка Текст разбивается на строки. Каждая строка проверяется по очереди на совпадение с регулярными выражениями блоков (block-level tokens). Если совпадение найдено, создаётся соответствующий токен, а текст строки маркируется как обработанный.

  3. Рекурсивный разбор вложенных структур Некоторые блоки, например списки или блоки цитат, могут содержать другие блоки. Marked создаёт токены для вложенных элементов рекурсивно. Это позволяет точно сохранить иерархию документа.

  4. Обработка inline-токенов После построения блоковых токенов текст внутри каждого блока дополнительно проходит разбор на inline-токены. Например, в абзаце может встречаться жирный текст, ссылки или встроенный код. Для этого используется InlineLexer, который сканирует строку и заменяет найденные элементы на соответствующие токены.

  5. Финальная сборка массива токенов В результате получается массив токенов, описывающий всю структуру документа. Этот массив затем передаётся в рендерер, который превращает токены в HTML.

Особенности реализации

  • Marked использует жадные регулярные выражения, что ускоряет токенизацию и позволяет минимизировать количество проходов по тексту.
  • Поддержка различных типов Markdown осуществляется через конфигурируемые правила. Пользователь может передать собственные регулярные выражения для блоков и inline-элементов.
  • Лексер автоматически корректирует неправильное вложение элементов, например, неправильные списки или смешанные блоки цитат.
  • Процесс токенизации отделён от рендеринга, что позволяет легко внедрять собственные методы преобразования токенов в другие форматы, кроме HTML.

Пример токенизации

Markdown:

# Заголовок 1

Текст с **жирным** и _курсивом_.

- Пункт 1
- Пункт 2

Результирующие токены:

[
  { "type": "heading", "depth": 1, "text": "Заголовок 1" },
  { "type": "paragraph", "text": "Текст с **жирным** и _курсивом_" },
  {
    "type": "list",
    "ordered": false,
    "items": [
      { "type": "list_item", "text": "Пункт 1", "tokens": [] },
      { "type": "list_item", "text": "Пункт 2", "tokens": [] }
    ]
  }
]

Этот пример показывает, как Marked преобразует текст Markdown в структурированный набор токенов, сохраняя типы элементов и их вложенность.

Оптимизация токенизации

Marked применяет следующие приёмы для повышения производительности:

  • Предварительная компиляция регулярных выражений.
  • Использование массивов вместо связных списков для хранения токенов.
  • Минимизация рекурсивных вызовов через оптимизированный разбор вложенных блоков.
  • Обработка inline-токенов отдельно от блоков, чтобы избежать лишних проходов по тексту.

Тщательная токенизация позволяет Marked обеспечивать быструю и точную генерацию HTML из Markdown даже для больших документов с глубокой вложенностью элементов.