среда, 3 марта 2010 г.

Символьные вычисления 2

Ранее я уже писал о символьных вычислениях, но в тот раз ограничился только общими впечатлениями и обещал написать отдельную большую статью. К таковой я, к сожалению, ещё не готов, но зато могу рассказать о том, как я использую символьные вычисления для реализации cl-closure-template, тем более, что последний троллинг на ЛОР-е буквально вынуждает меня к этому.

cl-closure-template - это система шаблонов для веб и казалось бы точно никакого отношения к символьным вычислениям иметь не может. И это, вероятно, действительно так если речь идёт о php. Но использование Common Lisp для решения этой задачи даёт богатую почву для использования символьных вычислений. Мало того, они чрезвычайно эффективны для решения данной задачи. Итак, cl-closure-template имеет следующие компоненты:

1. src/expression.lisp - парсер выражений, используемых в языке шаблонов. Это расширенная и переработанная версия файла infix.lisp из AIMA. Пусть дано выражение:
$a + $b[1] 
Первым делом оно считывается в символьную форму:
((:VARIABLE :A) + (:VARIABLE :B) [ 1 ])
Теперь полученную инфиксную форму необходимо привести к принятой в лисп префиксной нотации:
(+ (:VARIABLE :A) (ELT (:VARIABLE :B) 1))
Сам алгоритм преобразования представляется мне довольно сложным, но он был сложен ещё в AIMA и я поначалу не верил, что смогу его модифицировать, а cl-closure-template должна обрабатывать куда более сложные выражение, чем оригинальная версия (если кто забыл, то в AIMA этот код используется для разбора выражений логики первого порядка).

Т.е. здесь очевидно имеют две фазы вычислений, которые можно с полным правом отнести к символьным: преобразование строки в символьную форму и последующее приведение её к префиксной нотации.


2. src/template.lisp - собственно парсер шаблонов. Для описания грамматики языка шаблонов используются макросы define-mode, вот как выглядит описание тэга "for":
(define-mode for-tag (70 :all)
(:allowed :all)
(:entry "{for\\s+[^}]*}(?=.*{/for})")
(:entry-attribute-parser parse-for-attributes)
(:exit "{/for}"))
Информация, указанная в данной форме сохраняется в свойствах символа for-tag, который в последующем используется для сборки парсера, а сам символ for-tag будет использоваться в s-выражении, являющимся результатом разбора шаблона. Например, разбор следующего шаблона:
{template test}
{for $x in range(10)} ! {/for}
{/template}
вернёт такую символьную форму:
(closure-template.parser:template ("test")
(closure-template.parser:for-tag ((:variable :x)
(:range 10))
" ! "))
Таким образом, в данном компоненте можно отметить, что для описания грамматики шаблонов используется символьная форма, а результатом разбора шаблона является символьное выражение - весьма характерный пример использования символьных вычислений. Кроме того, в коде используется предоставляемая cl-ppcre возможность задания регулярных выражений в символьной форме, которая весьма существенно упрощает реализацию.

3. src/common-lisp-backend.lisp - предоставляет возможность использовать шаблоны в программах на Common Lisp. Шаблоны компилируются в машинный код (на поддерживающих это реализациях), для этого символьное представление шаблона преобразуется в код на Common Lisp, который, естественно, тоже имеет символьную форму. Опять получилось две фазы символьных вычислений: преобразование одной символьной формы в другую и выполнение её с помощью eval.

4. src/javascript-backend.lisp - аналогичен предыдущему компоненту, но только предназначен для генерации кода на JavaScript, для чего шаблон в символьной форме сначала преобразуется в символьный формат parenscript и затем с помощью parenscript компилируется в JavaScript. Опять совершенно явный пример символьных вычислений.

5. t/cl-backend-test.lisp - тесты для CL-бэкэнда, примечательным является то, что используемая библиотека lift сохраняет полную информацию о тестах в символьной форме в свойствах символов. Обычно это не имеет никакого значения, но оказалось очень важным для данного проекта.

6. t/js-backend-test.lisp - обеспечивает возможность тестирования JS-бэкэнда. Тесты для CL и JS-бэкэндов должны быть, вообще говоря, совершенно одинаковыми и я очень не хотел писать их руками, что бы потом ещё и мучатся с сопровождением. На помощь пришло описанное выше свойство lift. Код в данном файле извлекает полную информацию о тестах для CL-бэкэнда, в том числе код самих тестов, преобразует эти тесты в формат parenscript и компилирует с его помощью их в тесты для jsunittest.js. Кроме того, запускается веб-сервер и теперь можно смотреть результаты выполнения тестов в браузере. Я был чрезвычайно впечатлен этим ярким примером использования символьных вычислений.

Вывод: реализация cl-closure-template является примеров использования символьных вычислений, собственно, кроме символьных вычислений там больше и нет ничего.

У меня имеется ещё богатый материал по использованию символьных вычислений в cl-routes и RESTAS, да и вообще, чем больше я пишу на Common Lisp, тем больше использую возможности символьных вычислений, но об этом пожалуй в следующий раз.

Комментариев нет:

Отправить комментарий