Внутренний мир Razor. Часть 1–Рекурсивный пинг-понг

пятница, 9 июля 2010, Евгений Жарков

MRK37GЭто первая статья о новом ASP.NET парсере – Razor. Над которым мы работали достаточно долго, и я хотел бы рассказать читателям, как же он работает.

Razor-парсер сильно отличается от существующего ASPX-парсера. Фактически ASPX-парсер, почти полностью, построен на регулярных выражениях, потому что синтаксис достаточно простой для разбора. Razor-парсер же разделен на три компонента:

  1. Парсер разметки, который имеет базовое представление о HTML-синтаксисе.
  2. Парсер кода, который имеет базовое представление C# или VB.
  3. И главный “дирижер”, которые знает, как соединить два парсера вместе.

Когда я говорю “базовое представление” я подразумеваю именно основы, мы не говори о полностью самостоятельном C# и HTML парсере. У себя в команде мы шутим, называя их “Опознователь разметки” и “Осмыслитель кода” :)

 

Итого на сцене Razor играет три “актера”: Парсер ядра, Парсер разметки и Парсер кода. Все трое работают вместе, чтобы разобрать документ Razor. Теперь давайте возьмем файл Razor и проведем полный обзор процедуры парсинга с использованием данных актеров. Использовать будем следующий пример:

<ul>@foreach(var p in Model.Products) {  
    <li>@p.Name ($@p.Price)</li>  <br>    }  
</ul> 

Итак начнем сверху. Фактически парсер Razor находится в одном из состояний в любой момент парсинга: разбор разметки документа, разбор разметки блока или разбор блока кода. Первые два обрабатываются парсером разметки, а последний парсером кода. Когда парсер ядра запускается первый раз, он вызывает парсер разметки и просит его разобрать разметку документа и вернуть результат. Сейчас парсер находится в состоянии разбора разметки документа. При таком состоянии, он просто ищет символ “@”, ему не важно какие теги ему попадаются и все, что касается HTML, главная цель – “@”. Когда же он нашел @, то решает – это переключение на код или email-адрес? Данное решение основывается на символах до и после @, проверяя на валидность email-адрес. Это всего лишь стандартная процедура, присутствует последовательность проверок, чтобы произвести переключение на режим кода.

В данном случае, когда мы видим первый символ “@”, ему предшествует пробел, что не валидно для email-адреса. Так что мы точно знаем,что нужно переключится на режим кода. Парсер разметки вызывает внутри парсер кода и просит разобрать блок кода. Блок, в определении парсера Razor, в основном является единым куском кода или разметки с четким началом и завершением. Так что “foreach” в нашем случае является примером блока кода. Он начинается с символа “f” и заканчивается “}”. Парсер кода знает достаточно о С#, что бы понять это, соответственно он начинает разбор кода. Парсер кода проделывает некоторое простое отслеживание C# операторов, так что когда он доберется до “

  • ”, то понимает, что тег находится в начале C#-выражения. “
  • ” невозможно разместить в начале C#-выражения, так что парсер кода знает, что с этого места начинается блок разметки. Поэтому он возвращается в вызов парсера разметки, для того, чтобы разобрать блок HTML. Это создает своего рода рекурсивный пинг-понг между парсерами кода и разметки. Мы начали с разметки, далее вызвали внутри код, далее снова разметка и так далее, до тех пор, пока не получили результат всей цепочки вызовов:

    • HtmlMarkupParser.ParseDocument()
      • CSharpCodeParser.ParseBlock()
        • HtmlMarkupParser.ParseBlock()
          • CSharpCodeParser.ParseBlock()

    (Понятное дело, я исключил из списка множество вспомогательных методов :).

    Это проливает свет на фундаментальное отличие между ASPX и Razor. В ASPX файлах,  вы можете думать о коде и разметке, как о двух параллельных потоках. Вы пишете разметку, потом перепрыгиваете и пишете код, потом обратно возвращаетесь и пишете разметку и т.д. Razor же файлы, как дерево. Вы пишете разметку, далее вкладываете в нее код, далее помещаете разметку в код и т.д.

    Итак мы только что вызвали парсер разметки для разбора блока разметки, блок начинается с “

  • ” и заканчивается на “
  • ”. До тех пор, пока не найдем “”, мы не решим, что блок разметки окончен. Так что, если у вас будет “}” где-то внутри “
  • ” он не завершит “foreach”, так как мы не продвинулись достаточно далеко вверх по стеку.

    Во время разбора “

  • ”, парсер разметки видит множество символов “@”, из чего следует множество вызовов парсера кода. Таким образом стек вызова увеличивается:

    • HtmlMarkupParser.ParseDocument()
      • CSharpCodeParser.ParseBlock()
        • HtmlMarkupParser.ParseBlock()
          • CSharpCodeParser.ParseBlock()

    Я углублюсь в детали обработки блоков позже, потому что процесс немного сложен, в итоге мы закончили с данными блоками кодами и вернулись в блок “

  • ”. Далее, мы видим “
  • ”, так что мы завершаем и этот блок и возвращаемся в блок “foreach”. “}” закрывает блок, так что теперь мы снова на вершине стека – документа разметки. После этого мы читаем, пока не достигнем конца файла, не найдя больше символов “@”. И вуаля! Мы разобрали данный файл!"

    Я надеюсь общая структура алгоритма разбора ясна. Главное – это перестать думать, что парсер кода и разметки работают в отдельных потоках, а вместо этого конструкции располагаются одна в другой. Намекну, вдохновние мы черпали из PowerShell ;).

     

    Источник – VibrantCode


    Ищите нас в интернетах!

    Комментарии

    Свежие вакансии