пятница, 13 ноября 2009 г.

Реализовать Closure Template за 5 дней

Я считаю, что мне отчаянно повезло. В тот момент, когда, с одной стороны, проблема распределения логики между серверной и клиентской частью окончательно загнала меня в угол, а с другой очень серьёзно не хватало приемлемой системы шаблонов для Common Lisp, и я со всех сил искал выход, в этот самый момент Google публикует Closure Template. И пусть там, в оригинальном коде, чуть менее 15 000 строк кода (т.е. фактически примерно год разработки, если писать с начала и самостоятельно продумывать дизайн). Главное, что это как раз то решение, которое мне было нужно, а уж реализовать уже готовую спецификацию, да на Common Lisp, - не такая большая проблема. Это удача номер один. Удача номер два - спецификация довольно проста и я сразу понял, что смогу реализовать её малой кровью. Что может быть приятней хорошей и простой в реализации идеи? Правда, при первом прочтении документации меня смутили несколько моментов, их реализация была возможна, но грозила вылиться в нудный и раздутый код, чего я очень не люблю. Но, и в этом удача номер три, абсолютно все эти моменты, меня смутившие, смутили программистов Google не меньше меня, и они аккуратно, не нанося особо большого вреда система (но на мой вкус, несколько подпортив дизайн с точки зрения "высокого программирования" :)), расставили в документации примечания, срезающие все углы и значительно упрощающие реализацию (и это то при их ресурсах? халявщики...). И каждый раз, когда я думал, что здесь может быть сложно, я шёл уточнять в доке и обнаруживал заветное примечание, делающее всё простым, ну не здорово ли? Удача номер четыре? Не думаю, что здесь стоит говорить об удаче - я, конечно, считаю, что Common Lisp безусловно лучший язык для большинства задач, но насколько хорошо именно эта задача легла на CL это просто невероятно, я очень серьёзно впечатлён и эти 5 дней, вполне вероятно, являются лучшими в моей программистской практике.

Итого, спустя пять (!) дней после начала разработки я имею почти законченный (за исключением некоторых мелких деталей, о них ниже) парсер и Common Lisp backend, компилирующий шаблоны в машинный код (на платформах, умеющих это, например на SBCL). Поддерживаются все управляющие конструкции, все указанные в спецификации операторы и функции. Система уже достаточно неплохо отлажена и готова к использованию. Размер исходного код - менее 800 строк кода (без учёта тестов) на Common Lisp (сравниваем с оригинальными 15 000 и делаем какие-нибудь выводы). Какие-либо "навороченные" библиотеки не используются, только регулярные выражения, да штатные средства символьных вычислений, плюс есть зависимость от моей же библиотеки wiki-parser, но в ней то менее 200 строк значимого для данной задачи кода (она также содержит парсер для dokuwiki-разметки).

Есть некоторые отличия, и самое главное это различная реакция на "плохую" разметку. Оригинальная реализация, встречая некорректную конструкцию или выражение ругается матом и указывает на номер строки и позицию в ней, и ещё чего-то там сообщает, ну натуральный компилятор. Подобный уровень парсинга находится вне области моих возможностей, ибо (я уже об этом как-то рассказывал), я совершенно туп в парсинге (возможно, по причине отсутствия математического образования, хотя не могу этого точно утверждать, просто не знаю чему там учат математиков). Термины типа "контекстно-свободная грамматика" или "форма Бэкуса - Наура" вызывают у меня священный ужас и желание перевести разговор на другую тему. Поэтому, для разбора шаблонов я использовал технику, применяемую при разборе wiki-разметок. В итоге, моя реализация правильно обрабатывает все корректные конструкции, а некорректные просто игнорирует и оставляет их в виде простого текста, так что, если накосячить, то результат сразу будет заметен в браузере, чего, на мой взгляд, вполне достаточно.

В данный момент не реализовано два тэга: msg и css - я ещё не вполне разобрался что это такое и какое там должно быть поведение. Но проблема локализации меня в данный момент не очень сильно заботит, поэтому команда msg может некоторое время подождать. Также, не поддерживаются дополнительные атрибуты тэга tempalte. Правда, атрибут autoescape вообще вызывает у меня подозрения: на мой взгляд, подобная обработка должна производиться задолго до попадания данных в шаблон. А вот атрибут private мне очень понравился, он кажется придуман специально для демонстрации подавляющего превосходства Common Lisp над всем остальными языками :), будет обязательно. Также пока не поддерживаются дополнительные директивы команды print. Плюс, литералы float пока можно записывать только самым простым способом, например 3.14, варианты в стиле 6.02e23 пока не поддерживаются. Ну и для строковых литералов также пока не поддерживаются escape-последовательности. Вот, по нереализованному кажется всё. Ну, да, JavaScipt backend-а пока тоже нет, но он будет реализован в самое ближайшее время и ориентировочно будет содержать 200-300 строк, так что общий объём системы будет как раз около 1000 строк кода.

Поскольку разработка подобной системы немыслима без тестов (по крайней мере, я себе это плохо представляю) (странно, но я не нашёл тестов в оригинальном коде Google, плохо искал? это вполне возможно, не силён в Java), так вот, сейчас у меня есть 75 тестов общим размером в 500 строк кода, всё успешно проходятся. Для тестирования использовались следующие реализации: SBCL, Clozure CL, CLISP. Тесты всегда несколько надуманны, поэтому, для пущей надёжности, я перевёл одно из своих приложений (над которым работал в последнее время) на эту систему (ранее там использовалась HTML-TEMPLATE) - каких-либо аномалий выявлено не было, система работает нормально.

В следующий раз я наверное расскажу что же это всё таки за система - Closure Template (мне показалась, судя по обсуждениям на некоторых новостных ресурсах, что она не получила в рунете должной оценки) и почему её стоит сравнивать даже не сколько с традиционными шаблонами, сколько с технологией XSLT (несмотря на то, что как технологии они совершенно различны).


P.S. Ну да, исходный код cl-closure-template распространяется под лицензией Lisp-LGPL и доступен по адресу: http://github.com/archimag/cl-closure-template

10 комментариев:

  1. А много ли там CL-specific вещей? Иными словами, сложно ли будет перевести это на другой диалект лиспа, в частности на Clojure?

    ОтветитьУдалить
  2. как интересно - ты бы закинул анонс в planet lisp? или попроси 13-49 дать ссылку на твой пост, прогнанный через google translate?

    ОтветитьУдалить
  3. > А много ли там CL-specific вещей?
    Я не настолько силён в других диалектах лисп, что бы хоть как-то судить об этом.

    > в частности на Clojure
    Ну, в Clojure, насколько я понимаю, должна быть доступна оригинальная реализация :) Потом, для генерации JS я собираюсь использовать Parenscript, не думаю, что это можно будет портироваться на другие лиспы.

    ОтветитьУдалить
  4. 2zwork: на первый взгляд, особых привязок к CL нет, можно попробовать реализовать для clojure.
    другой вопрос - есть ли смысл? для clojure доступна velocity, которая тоже вполне вменяемая шаблонная система

    ОтветитьУдалить
  5. > ты бы закинул анонс в planet lisp

    У меня была мысль, попросить об этом Vladimir Sedach (разработчика Parenscript), но это после завершения JavaScript backend-а (я к нему уже приступил, думаю будет на следующей неделе).

    ОтветитьУдалить
  6. Буду рад помочь с PR, но жду следующий пост который объяснит что это такое и с чем его кушают. Я не очень врубаюсь в смысл Closure Template и чем оно лучше других шаблонных систем, может потому что сам недолюбливаю их (URI-Template исключение, но все равно иногда достает).

    ОтветитьУдалить
  7. При передаче в шаблон double-float'ов они выводятся в виде 1.2d3, который тот же javascript не понимает. Пока я делаю на них coerce в single-float, чтобы всё работало.

    А вообще наверное нужен механизм настройки печати различных примитивных типов, ведь шаблонизатор можно не только для генерации html'я со внедренным javascript'ом использовать.

    ОтветитьУдалить
  8. > При передаче в шаблон double-float'ов они
    > выводятся в виде 1.2d3

    Хм... Для вывода используется (format *out* "~A" ...) и я пока не могу предложить какой-либо разумной политики в этой области. В оригинальной Java-реализации есть просто допустимые типы данных, даёшь не те данные - получи исключение.

    > А вообще наверное нужен механизм настройки
    > печати различных примитивных типов

    Это будет вести к усложнению. Я рассуждаю так, что вот есть спецификация, которую ребята из Google разработали и обкатали на многих проектах. Если они считаю, что её достаточно, то скорей всего так и есть. И выходить за рамки этой спецификации как не очень хотелось бы. Вдруг потом придётся портировать проекты с Java на CL ? гы ;)

    ОтветитьУдалить
  9. Ну, если держать в уме совместимость с Явой, может для всех подтипов float явно использовать что-то вроде "~,,,,,,'EE" ?

    ОтветитьУдалить
  10. @binarin
    Дело в том, что там сейчас очень простой код для вывода, один единственный format на все типы. Можно попытаться сделать обработку более интеллектуальной, возможно, в этом и есть смысл, но в данный момент у меня не достаточный опыт использования и я не готов судить, какое решение было бы оптимальным.

    Я уже рассуждал на эту тему, ибо в паре мест мне надо выводить время, которое представленно объектами local-time:timestamp. И я пока не пришёл к лучшему решению, чем проводить дополнительные обработки в момент подготовки данных, а не на уровне движка шаблонов.

    Но, Вы ведь можете форкнуть, внести необходимые изменения и после этого будет проще оценить, насколько они являются приемлемыми ;)

    ОтветитьУдалить