понедельник, 20 декабря 2010 г.

Колличество процессоров

Если вам вдруг потребуется узнать количество процессоров в коде на Common Lisp, то сделать это с помощью iolib можно так:
(iolib.syscalls:sysconf iolib.syscalls:sc-nprocessors-onln)

суббота, 4 декабря 2010 г.

10 тысяч запросов в секунду

Накидал очень простой (100 строк кода) прототип асинхронного веб-сервера на базе iolib. Он умеет принимать GET-запрос и не обращая на него внимание отдавать одну и ту же страницу. Практической пользы от него никакой, но для исследования вопроса вполне сгодится. Так вот, планка в 10 000 запросов в секунду (тестировал через ab) на моей машине была уверенно взята без каких-либо оптимизаций - временами скорость доходила до 11 000 запросов в секунду. Код не полностью корректен, но от него это и не требуется. Всё обработка ведётся в одном потоке. Собственно, код:
(asdf:operate 'asdf:load-op '#:iolib)
(asdf:operate 'asdf:load-op '#:iterate)

(defpackage #:http.test
(:use #:cl #:iter)
(:export #:start #:stop))

(in-package #:http.test)

(defparameter *event-base* nil)

(defparameter *reply*
(let ((endl #.(babel:octets-to-string (coerce #(13 10) '(vector (unsigned-byte 8)))))
(content "<html>
<head>
<title>Hello world</title>
</head>
<body>
<h1>Hello world</h1>
</body>
</html>"))
(babel:string-to-octets
(with-output-to-string (out)
(write-string "HTTP/1.0 200 OK" out)
(write-string endl out)
(format out "Content-Length: ~A" (length content))
(write-string endl out)
(write-string "Content-Type: text/html" out)
(write-string endl out)
(write-string endl out)
(write-string content out))
:encoding :latin1)))

(defvar *bucket-pool* nil)

(defun get-bucket ()
(or (pop *bucket-pool*)
(make-array 4096 :element-type '(unsigned-byte 8))))

(defun free-bucket (bucket)
(push bucket *bucket-pool*))

(defun read-http-headers (socket callback)
(let ((headers (get-bucket))
(size 0))
(flet ((read-handler (fd event errorp)
(declare (ignore event errorp fd))
(multiple-value-bind (buffer count) (iolib.sockets:receive-from socket :buffer headers)
(declare (ignore buffer))
(incf size count))

(when (and (> size 4)
(equal '(13 10 13 10)
(coerce (subseq headers (- size 4) size) 'list)))
(iolib.multiplex:remove-fd-handlers *event-base*
(iolib.sockets:socket-os-fd socket)
:read t)
(free-bucket headers)
(funcall callback))))
(iolib.multiplex:set-io-handler *event-base*
(iolib.sockets:socket-os-fd socket)
:read #'read-handler))))

(defun send-http-reply (socket data callback)
(let ((curpos 0)
(total-length (length data)))
(flet ((write-handler (fd event errorp)
(declare (ignore fd event errorp))
(cond
((= curpos total-length)
(iolib.multiplex:remove-fd-handlers *event-base*
(iolib.sockets:socket-os-fd socket)
:write t)
(funcall callback))
(t (incf curpos
(iolib.sockets:send-to socket data :start curpos))))))
(iolib.multiplex:set-io-handler *event-base*
(iolib.sockets:socket-os-fd socket)
:write #'write-handler))))

(defun accept-connection (passive-socket)
(let ((active-socket (iolib.sockets:accept-connection passive-socket)))
(read-http-headers active-socket
(lambda ()
(send-http-reply active-socket
*reply*
(lambda ()
(close active-socket)))))))

(defun start (&optional (port 8080))
(setf *event-base* (make-instance 'iolib.multiplex:event-base))
(flet ((impl ()
(iolib.sockets:with-open-socket (acceptor :connect :passive
:address-family :internet
:type :stream
:external-format '(:utf-8 :eol-style :crlf)
:ipv6 nil)
(iolib.sockets:bind-address acceptor
iolib.sockets:+ipv4-unspecified+
:port port
:reuse-addr t)
(iolib.sockets:listen-on acceptor :backlog 5)

(flet ((accept (fd event errorp)
(declare (ignore fd event errorp))
(accept-connection acceptor)))
(iolib.multiplex:set-io-handler *event-base*
(iolib.sockets:socket-os-fd acceptor)
:read #'accept))

(iolib.multiplex:event-dispatch *event-base*)
(close *event-base*))))
(bordeaux-threads:make-thread #'impl
:name "*http-server*")))

(defun stop ()
(iolib.multiplex:exit-event-loop *event-base*))

четверг, 25 ноября 2010 г.

Обработка PNG-изображений на Common Lisp

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

А вот с этим проблема, поскольку украденный мной набор png-файлов сделан только в чёрном исполнении. Сам я в дизайне полный ноль, всякими Gimp-ами владею очень слабо и вообще, как создаются подобные изображения понятия не имею: я пробовал создать такое просто кодом с помощью градиентов, закруглений и т.п., но так хорошо никак не получается.

И я решил просто по-пиксельно заменить все цвета оригинальных изображений на новые, которые будут вычисляться на основе базового цвета. В оригинальных файлах основным цветом является rgb(30, 30, 30), но для создания эффекта тени используется переход данного цвета в чёрный. Функция translate-color вычисляет новый цвет на основе базового и опирается на rgb(30, 30, 30) как на основу старого изображения:
(defun translate-color (orig base-color)
  (iter (for i in orig)
        (for j in base-color)
        (collect (min (max (+ i j -30) 0)
                      255
)
)
)
)
Для создания png-файлов есть известное и хорошее решение - ZPNG, а вот библиотеки для разбора png-файлов я не знал и кажется такая библиотека не освещалось широко где-либо, по крайней мере, я не видел. Однако, быстрый поиск в гугл сразу показал мне библиотеку png-read. Я опробовал её на нескольких примерах и кажется она "просто работает". Таким образом, я смог записать такой код по изменению цвета нужных мне изображений:
(defun make-other-png (orig dest base-color)
  (let* ((orig-png (png-read:read-png-file orig))
         (orig-image (png-read:image-data orig-png))
         (png (make-instance 'zpng:png
                             :color-type :truecolor-alpha
                             :width (png-read:width orig-png)
                             :height (png-read:height orig-png)
)
)

         (image (zpng:data-array png))
)

    (iter (for w from 0 below (png-read:width orig-png))
          (iter (for h from 0 below (png-read:height orig-png))
                (iter (for c in (translate-color (list (aref orig-image w h 0)
                                                       (aref orig-image w h 1)
                                                       (aref orig-image w h 2)
)

                                                 base-color
)
)

                      (for i from 0)
                      (setf (aref image h w i) c)
)

                (setf (aref image h w 3)
                      (aref orig-image w h 3)
)
)
)

    (zpng:write-png png dest)
)
)
Функция make-other-png принимает путь к оригинальному файлу, путь для сохранения нового изображения и цвет, который должен являться базовым для нового изображения.

Опробовал данный код и остался очень доволен результатом. Вот что получается в результате вызова
(make-other-png "win_LB.png" "out.png" '(0 192 0))

Слева оригинальное изображение, а с права получившееся в результате преобразования.

P.S. Ebuild для png-read я добавил в свой форк gentoo-lisp-overlay.

среда, 24 ноября 2010 г.

Переделал свой форк cl-pdf

Форк cl-pdf я сделал довольно давно и тогда я ещё плохо ориентировался как в CL, так и в git, в итоге форк был оформлен очень топорно, без истории изменений. Сейчас дошли руки полностью его переделать используя git svn, так что в него попала полная история изменения. Все свои изменения также внёс одно за другим. Так что стало намного лучше и можно теперь нормально синхронизироваться с основным репозиторием, если там вдруг будут изменения, а они там бывают, хоть и реже чем раз в год.

От оригинальной версии мой форк отличается следующим:
  • Почищен разный мусор, типа каких-то левых патчей для поддержки CMUCL, различных вариаций на тему zlib и т.п., которые предлагалось как-то загружать руками
  • Для сжатия используется salza2 и только она.
  • Поддерживается загрузка и использования ttf шрифтов с помощью zpb-ttf
  • У функций draw-centered-text, draw-left-text и draw-right-text имеется дополнительный опциональный параметр max-height (параметр max-width уже был в оригинальной версии)
  • Добавлена функция append-child-ouline, а также экспортируется функция outline-root
Вообще надо немного привести в порядок код для генерации PDF, который я использую на работе, а также код для генерации PDF-версии PCL, который используется на lisper.ru и в соответствии с этим также внести ряд небольших изменений.

Плюс, есть желание выкинуть из cl-pdf код для парсинга PNG-файлов и использовать для этого библиотеку png-read (которую я обнаружил на днях) и сделать возможным использование PNG-изображений с прозрачностью (сейчас мне приходиться насильственно добавлять к таким изображениям фон).

четверг, 18 ноября 2010 г.

Необычное использование restas-directory-publisher

Модуль restas-directory-publisher по начальной задумке предназначался для простой публикации директорий, содержащих статические файлы. Но в последнее время я использовал его сразу несколькими способами, которые я никак не ожидал в момент разработки и которые показались мне довольно любопытными. Так что решил немного об этом рассказать.

Сейчас у меня возникла необходимость показывать на странице пользователю диалог, в котором о мог бы выбрать файл, находящийся на файловой системе сервера. Немного погуглив нашёл несколько решений и примеров для jquery, которые показались мне просто ужасными и я решил, что сделать собственное решение будет значительно проще и быстрее. Как оказалось, делается оно почти тривиально.

Полученное мною решение состоит из трёх частей: шаблон cl-closure-template для генерации контента на стороне клиента, несколько строк кода на JavaScript для управления и серверная часть, которая возвращает информацию о файловой системе.

Модуль restas-directory-publisher умеет собирать информацию о файловой системе, но по умолчанию возвращаёт её в формате html, а мне для данной задачи нужно в формате JSON. Исправить этот недостаток можно так:
(defun encode-json (obj)
(flet ((encode-json-list (list stream)
(if (keywordp (car list))
(json:encode-json-plist list stream)
(json::encode-json-list-guessing-encoder list stream))))
(let ((json::*json-list-encoder-fn* #'encode-json-list))
(json:encode-json-to-string obj))))

(restas:mount-submodule -file-system- (#:restas.directory-publisher)
(restas.directory-publisher:*baseurl* '("api"))
(restas.directory-publisher:*directory* #P"/")
(restas.directory-publisher:*autoindex* t)
(restas.directory-publisher:*autoindex-template* #'encode-json))
Здесь производится настройка подключения субмодуля и с переменной restas.directory-publisher:*autoindex-template*, используемой для генерации контента, связывается функция #'encode-json (реализацию данной функции я уже приводил ранее).

Шаблон для генерации разметки:
{template directoryBrowse}
<table summary="Directory Listing" cellpadding="0" cellspacing="0">
<thead>
<tr>
<th class="n">Name</th>
<th class="m">Last Modified</th>
<th class="s">Size</th>
<th class="t">Type</th></tr>
</thead>

<tbody>
{if $parent}
<tr>
<td class="n">
<span class="directory" href="{$parent}">Parent Directory</span>
</td>
<td class="m"> </td>
<td class="s">-  </td>
<td class="t">Directory</td>
</tr>
{/if}

{foreach $path in $paths}
<tr>
<td class="n">
<span class="{$path.type == 'Directory' ? 'directory' : 'file'}" href="{$path.href}">
{$path.name}
</span>
{nil}
{if $path.type == 'Directory'}/{/if}
</td>
<td class="m">{$path.lastModified}</td>
<td class="s">{$path.size ? $path.size : '-  ' |noAutoescape}</td>
<td class="t">{$path.type}</td>
</tr>
{/foreach}
</tbody>
</table>
{/template}
Я лишь немного модифицировал шаблон, используемый в restas-directory-publisher

Управлящий код на JavaScript совсем прост:
$(document).ready( function () { browse("/api/"); } );

function browse (url) {
function directoryClick (evt) {
browse($(evt.currentTarget).attr("href"));
}

function fileClick (evt) {
$("h1").html(decodeURI($(evt.currentTarget).attr("href")));
}

function handler (data) {
$("#content").html(restas.jsBrowser.view.directoryBrowse(data));
$("#content .directory").click(directoryClick);
$("#content .file").click(fileClick);
}

$.getJSON(url, handler);
}
Я организовал этот код в виде отдельного законченного примера jsBrowser, который включил в состав restas-directory-publisher, посмотреть исходный код можно здесь

вторник, 16 ноября 2010 г.

cl-mssql и FreeTDS-0.82

Обновил у себя FreeTDS до версии 0.82 и обнаружил проблемы с кодировками. Я использую cl-mssql для взаимодействия с 1С, данные там лежат в кодировке cp1251, а у меня в системе используется utf-8. Версия FreeTDS-0.62 кажется вообще никак не учитывала кодировки, поэтому в cl-mssql есть параметр соединения :external-format, который использовался для настройки переменной cffi:*default-foreign-encoding* - я устанавливал его в :cp1251 и спокойно работал. Версия FreeTDS-0.82 уже относится к этому не так просто и, вероятно, самостоятельно занимается перекодированием строк (а может как-то по другому взаимодействует с сервером, я не спец в этом вопросе). Теперь приходиться настраивать кодировку в /etc/freetds.conf:
[global]
client charset = utf8
Кодировка, указанная в /etc/freetds.conf, должна совпадать с кодировкой, которая указывается в mssql:connect (по-умолчанию - :utf-8).

понедельник, 15 ноября 2010 г.

cl-popen

Переименовал свою либу iolib.process в cl-popen и несколько изменил интерфейс. Использовать для создания и взаимодействия с дочерними потоками через стандартные потоки ввода/вывода можно, например, так:
(popen:with-popen2 ("cat | grep good" conveyer pin pout)
(write-line "Java is bad" pin)
(write-line "Python is bad" pin)
(write-line "Common Lisp is good" pin)
(write-line "imho" pin)
(close pin)
(read-line pout))
Поскольку стал использовать эту либу по работе, то добавил ebuild в свой оверлей.

воскресенье, 14 ноября 2010 г.

Предупреждение

Сегодня ночью ожидается недоступность lisper.ru в связи с техническими работа в датацентре.

Регистрация на lisper.ru и спам

При регистрации на lisper.ru отсылается письмо со ссылкой на продолжение регистрации. Это письмо стабильно в gmail попадает в спам. Прошу помощи у компетентных людей в чём причины и как это победить.

среда, 10 ноября 2010 г.

cl-uglify-js

Совершенно случайно (автор форкнул мой форк cl-pdf, а я пошёл посмотреть кто такой) обнаружил cl-uglify-js - библиотеку (Common Lisp) для "сжатия" кода на JavaScript. Я вообще давно уже мечтал о подобной либе для CL, ибо тянуть в проект на CL что-нибудь типа Google Closure Compiler совсем не хочется. Теперь же всё получается просто. Я протестировал эту библиотеку на своём основном проекте, в частности, на коде который генерирует cl-closure-template и обнаружил, что проект всё ещё работает (никаких изменений не выявленно), а код превратился в жуткое нечитабельное сжатое месиво - именно то, что нужно.

Вообще, ситуация с разработкой высоко-интерактивных веб-приложений на Common Lisp начинает казаться мне всё более и более симпатичной.

Кстати, cl-uglify-js является портом UglifyJS - аналогичной библиотеки для Node.js того же автора, которая на github имеет более 300 подписчиков, что внушает серьёзный оптимизм. Забавно, что в UglifyJS для разбора JavaScript используется порт parse-js - вот такая вот тесная интеграция JavaScript и Common Lisp.

вторник, 9 ноября 2010 г.

Ценителям поэзии

Есть в рунете замечательный сайт - www.stihi.ru, на котором помимо графоманов довольно много и интересных, хороших авторов. Если какой-то автор вам особенно понравился, то все его стихи выкачать и аккуратно сложить по папочкам (в соответствии с классификацией автора) можно с помощью такого кода на Common Lisp:
(defun load-poem (url dir &key verbose)
(html:with-parse-html (page url :encoding "cp1251")
(let* ((index (xpath:find-single-node page "/html/body/index"))
(title (xpath:find-string index "h1")))
(when verbose
(format t "Load ~A (~A)~%" url title))
(with-open-file (out (make-pathname :directory (pathname-directory dir)
:name title
:type "txt")
:direction :output :if-exists :supersede)
(iter (for br in (xpath:find-list index "div[@class='text']/br"))
(write-line (string-trim #(#\Newline)
(xtree:text-content (xtree:prev-sibling br)))
out))))))

(defun load-all-poems (url target &key verbose (recursive t))
(ensure-directories-exist target)
(html:with-parse-html (page url :encoding "cp1251")
(iter (for node in (xpath:find-list page "//ul/li/a"))
(load-poem (puri:merge-uris (puri:parse-uri (xtree:attribute-value node "href"))
url)
target
:verbose verbose))
(when recursive
(iter (for node in (xpath:find-list page "//div[@id='bookheader']/a"))
(let ((title (xtree:text-content node)))
(when verbose
(format t "~%Load book ~A~%" title))
(load-from-stihiru (puri:merge-uris (puri:parse-uri (xtree:attribute-value node "href"))
url)
(merge-pathnames (make-pathname :directory (list :relative title))
target)
:recursive nil
:verbose verbose))))))
Выделение содержательной части из html-страниц весьма тривиально и реализуется за счёт использования языка запросов XPath. Теперь скачать, например, все стихи моей жены можно таким вызовом:
(load-all-poems #U"http://www.stihi.ru/avtor/mari_mishon"
#P"/var/kubart/poems/"
При написании данного кода я обнаружил, что libxml2 не может правильно определить кодировку страниц стихиры, так что пришлось несколько доработать cl-libxml2, добавив возможность явно указывать кодировку html-страниц.

понедельник, 8 ноября 2010 г.

RESTAS и Quicklisp

Стала возможна лёгкая установка RESTAS (а также restas-directory-publisher) с помощью Quicklisp.

пятница, 29 октября 2010 г.

RESTAS и специальные страницы

Если в обработчике запроса для сервера Hunchentoot выставляется код возврата в какой-либо из кодов ошибок, то Hunchentoot игнорирует возвращаемое диспетчером значение и самостоятельно формирует соответствующую стандартную специальную страницу. Такое поведение не всегда является приемлемым. Изменить его можно с помощью переменной hunchentoot:*handle-http-errors-p* - если она выставлена в NIL, то вся ответственность за формирование специальных страниц ложится на пользовательский код. Однако, изменять её глобально (например, при загрузке системы) тоже не очень хорошо (потому что глобально). На самом деле я бы хотел, что бы пользовательский код смог указать будет ли он сам формировать специальную страницу или же пусть Hunchentoot генерирует стандартную. Самое простое решение - изменять значение переменной hunchentoot:*handle-http-errors-p* в пользовательском коде, но так, что бы эти изменения происходили не глобально, а только в контексте обрабатываемого запроса. Превратить глобальную переменную hunchentoot:*handle-http-errors-p* в "более локальную" очень просто, для RESTAS я сделал это так:
(defmethod hunchentoot:process-request ((request restas-request))
(let ((hunchentoot:*handle-http-errors-p* hunchentoot:*handle-http-errors-p*))
(call-next-method)))
Теперь можно смело менять значение переменной hunchentoot:*handle-http-errors-p* без какого-либо страха что это отразится на функционировании всей системы.

Если обработчик маршрута в RESTAS возвращает целое число, то оно интерпретируется как код возврата, за что отвечает соответствующая специализация метода restas:render-object. Что бы упростить определение для модулей своих способов формирования специальных страниц я переписал эту специализацию следующим образом:
(defmethod render-object :before (designer (code integer))
(setf hunchentoot:*handle-http-errors-p* nil
(hunchentoot:return-code*) code))

(defmethod render-object (designer (code integer))
"Default handler for HTTP status code"
(declare (ignore designer))
(setf hunchentoot:*handle-http-errors-p* t))
Это реализация по-умолчанию, которая в :before методе выставляет код возврата и присваивает переменной hunchentoot:*handle-http-errors-p* значение NIL, а в основном методе присваивает переменной hunchentoot:*handle-http-errors-p* значение T. Таким образом, по-умолчанию при возврате из маршрута целого числа за формирование специальной страницы будет отвечать Hunchentoot. Но, определив свой тип designer (и присвоив объект этого типа переменной модуля *default-render-method*) можно изменить это поведение и формировать специальные страницы в пользовательском коде. Очень удобно, что при этом также можно использовать и eql-специализаторы, например:
(defmethod restas:render-object ((designer mydrawer) (code (eql hunchentoot:+http-internal-server-error+)))
(setf (hunchentoot:content-type*) "text/plain")
"Шеф, всё очень плохо!")
При этом, может случить так, что если маршрут возвращает hunchentoot:+http-not-found+, то будет формироваться "красивое" сообщение о том, что на данном ресурсе такой страницы нет, но если RESTAS просто не найдёт подходящего маршрута, то будет отдаваться стандартная страница Hunchentoot. Для решение данной проблемы проще всего определить "универсальный" маршрут, который будет проверяться в последнюю очередь:
(restas:define-route not-found ("*any")
hunchentoot:+http-not-found+)
Описанный подход (который можно использовать с git-версией RESTAS) хорошо подходит для обработки специальных страниц общим для всего модуля образом. Но сейчас у меня есть один модуль, который предоставляет некоторое API для клиентского кода. Если маршрут в этом модуле отрабатывает успешно, то клиенту возвращаются данные в формате JSON. А в случае ошибки я бы хотел возвращать код возврата hunchentoot:+http-internal-server-error+ и прикреплять к нему сообщение, описывающие тип ошибки. Для упрощения реализации данного функционала я ввёл следующую функцию:
(defun abort-route-handler (obj &key return-code content-type) ..)
Эта функция немедленно прекращает обработку маршрута, а obj передаётся в restas:render-object для формирования ответа. Тонкость в том, что если в данную функцию передан return-code, то значение переменной hunchentoot:*handle-http-errors-p* выставляется в NIL. Теперь, вызов
(restas:abort-route-handler "Очень плохие данные!"
:return-code hunchentoot:+http-internal-server-error+
:content-type "text/plain")
отправит клиенту ответ с кодом 500 и сообщением "Очень плохие данные!".

понедельник, 25 октября 2010 г.

RESTAS-0.1

Оформил релиз RESTAS-0.1, скачать можно здесь, посмотреть документацию здесь, задавать вопросы на русском можно здесь, а на английском здесь (там сейчас вообще пусто).

Установка через мой форк clbuild больше не поддерживается, ну его.

среда, 20 октября 2010 г.

Изменения в RESTAS

Провёл два изменения в RESTAS.

Первое связано с механизмом субмодулей. При использовании макроса restas:mount-submodule можно настроить переменные модуля, но статическим образом, т.е. эти настройка никак не связаны с настройками родительского модуля. Порой это очень не удобно. Теперь, после проведённых изменений, можно делать примерно так:
(restas:mount-submodule -publisher- (#:restas.directory-publisher))

(restas:define-initialization (context)
(restas:with-context context
(setf (restas:context-symbol-value (restas:submodule-context (restas:find-submodule '-publisher-))
'restas.directory-publisher:*directory*)
*restas-documentation-dir*)))
Здесь переменной restas.directory-publisher:*directory* модуля restas.directory-publisher присваивается значение *restas-directory-dir* из модуля, к которому монтируется публишер и происходит это в момент инициализации основного модуля. Данный код взят из файла doc.lisp.


Второе изменение состоит в том, что я удалил поддержку garbage-pools и таким образом переменная restas:*request-pool* больше не доступна. Это сделано для упрощения, уменьшения зависимостей и для решения проблемы с LispWorks.

четверг, 14 октября 2010 г.

SLIME на службе автоматизации

В предыдущем посте я рассказал как можно ускорить компиляцию шаблонов cl-closure-template и тем самым упростить процесс разработки. Кроме того, мне также досаждает другая проблема. Для перекомпиляции этих шаблонов я обычно переключаюсь в файл, в котором расположен код компилирующий шаблоны, ставлю курсов на этот код и нажимаю M-C-x. Другой вариант - оформить это в виде функции и вызывать через REPL. Оба способа достаточно не удобны и могут вызывать некоторое раздражение при активной правке шаблонов. Поэтому, для упрощения этой задачи я использую следующий приём (который можно использовать и для других подобных задач):

в конфигурационный файл Emacs (~/.emacs, ~/.emacs.d/init.el или т.п.) я добавляю такой код:
(global-set-key "\C-c\C-p" 
(lambda ()
(interactive)
(slime-eval-async '(mypackage:compile-all-templates)
(lambda (obj) (message "Compilation templates finished")))))
Теперь при нажатии в Emacs сочетания C-c C-p будет вызвана функция mypackage:compile-all-templates, которая вызывает перекомпиляцию шаблонов (или что либо другое, что нужно именно вам).

Ускоряем компиляции шаблонов cl-closure-template в SBCL

Система шаблонов cl-closure-template транслирует код шаблонов в код на CL и компилирует его в исполняемый, что на SBCL ведёт к генерации машинного кода и может занимать существенное время (поскольку SBCL очень медленный компилятор), например, по моему основному проекту пере-компиляция шаблонов занимает чуть более 12 секунда, что довольно сильно напрягает в процесс разработки. Мне бы хотелось, что бы в режиме разработки можно было бы значительно ускорить время компиляции шаблонов, пусть и за счёт снижения их производительности. Как оказалось, решить эту проблему для SBCL можно очень просто за счёт использования переменной sb-ext:*evaluator-mode*:
(defparameter *developer-mode* nil)

(let ((sb-ext:*evaluator-mode* (if *developer-mode* :interpret :compile)))
(closure-template:compile-template :common-lisp-backend ..))
Здесь, когда переменная *developer-mode* установлена в значение отличное от NIL, шаблоны не компилируются, а интерпретируются. Работают они после это, конечно, не так быстро, но в процессе разработки это и не так важно. В результате, время перекомпиляции шаблонов (в режиме разработки) в моём проекте сократилось до 0.6 секунды, т.е. уменьшилось примерно в 20 раз.

среда, 13 октября 2010 г.

ANSI Common Lisp на русском

Судя по всему, издательство Символ-Плюс начало подготовительные работы к выпуску ANSI Common Lisp на русском языке. Если эти планы осуществляться, то это будет фактически первое за 20 лет издание книги о CL на "великом и могучем". Будем ждать.

понедельник, 11 октября 2010 г.

mount-submodule

В RESTAS произошло важное переименование: макрос restas:define-submodule теперь называется restas:mount-submodule - такое имя более точно отражает суть данного макроса. Сейчас изменения доступны в git-версии. Следующий релиз, скорей всего, будет иметь версию 0.1.

среда, 6 октября 2010 г.

Конфликт имён

Я тут всёми силами готовлюсь к выпуску RESTAS-0.1 и под это дело (да с чужой подачи) заинтересовался запустить RESTAS под LispWorks. Вроде, проблем быть было не должно. Но вот засада - при попытке загрузить систему сразу ошибка. Проблема c моим пакетом garbage-pools - его nickname конфликтует с nickname пакета graphics-ports, который содержится в образе LispWorks.

Уже до этого я несколько раз задумывался о возможном конфликте nickname различных пакетов, но реальной ситуации возникало и я особо не пытался разобраться. И вот, шанс проверить гибкость CL.

В общем, найденное решение кажется мне достаточно тривиальным:
#+:lispworks (rename-package '#:graphics-ports '#:graphics-ports nil)
(asdf:operate 'asdf:load-op :restas)
#+:lispworks (rename-package '#:garbage-pools '#:garbage-pools)
#+:lispworks (rename-package '#:graphics-ports '#:graphics-ports '(:gp))
Т.е. до загрузки RESTAS удаляем nickanmes пакета graphics-ports, загружаем RESTAS, удаляем nicknames у пакета garbage-pools, восстанавливаем nicknames у пакета graphics-ports.

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

вторник, 5 октября 2010 г.

RESTAS-0.0.11

Выпустил новую версию RESTAS - 0.0.11.

Хотел бы немого пояснить принцип, по которому я определяю релизы. У меня есть, скажем так, основное приложение, надо которым я работаю в рабочее время и за которое мне платят деньги. Размещено оно на отдельном сервере под управлением Gentoo, установку основных lisp пакетов на нём я произвожу с помощью archimag-lisp-overlay. Разработку RESTAS я произвожу параллельно с разработкой этого приложения. Как только я задействую какие-то новые функции RESTAS в этом приложении и появляется необходимость обновить его на рабочем сервере, так сразу я делаю новый релиз, обновляю ebuild в оверлее и произвожу обновление нужных пакетов. Вот и вся логика ))

воскресенье, 3 октября 2010 г.

parse-route-url

Добавил в RESTAS, новую функцию:
(defun parse-route-url (url route-symbol &optional submodule-symbol)
...)
Поскольку польза от неё, как мне кажется, не вполне очевидна, то должен немного пояснить для чего я её использую.

Я широко использую REST-стиль, в том числе, для взаимодействия между JavaScript-клиентом и веб-сервером. При этом, все данные представляются как ресурсы и идентифицируются с помощью URL, а в качестве обменного формата используется JSON (можно и XML). Сейчас я столкнулся с тем, что часто необходимо с клиента (JavaScript) отправлять данные на сервер для какой-либо обработки, а в отсылаемых на сервер данных содержатся url-идентификаторы объектов. На сервере во время обработки необходимо разбирать эти url-индикаторы на составляющие их параметры (например, для составления запросов к базе). Делать это вручную (с помощью регулярных выражений) очень непродуктивно и совершенно не масштабируемо, поскольку формат этих url зависит от конфигурации среды и для их генерации используется функция restas:genurl. Таким образом, функция restas:parse-route-url является обратной к семейству функций genurl.

Данный функционал оказался очень востребованным для моего текущего приложения и, скорей всего, будет полезен во многих AJAX-приложениях на базе RESTAS.

четверг, 30 сентября 2010 г.

RESTAS. Изменения в схеме инициализации модулей

Внёс небольшие изменения в систему инициализации субмодулей, которые решают следующие проблемы:
  • Функция инициализации, которая определялась при помощи restas:define-initilization, не вызывалась при непосредственном запуске модуля с помощью restas:start
  • Функция инициализации вызывалась непосредственно при определении субмодуля с помощью restas:define-submodule, а не при непосредственном включении его в состав родительского модуля - это могло вызвать проблемы при множественном использовании модуля в качестве субмодуля (немного путанно).
API остался прежним, но изменились некоторые макросы, так что необходимо удалить старые .fasl для систем, использующих RESTAS.

Эти изменения доступны в git-версии. Давно надо было сделать, но как-то руки не доходили ((

пятница, 27 августа 2010 г.

Отпуск

Уезжаю в отпуск. Не будет около 20 дней.

Хакеры и худоджники. Любимый фрагмент.

Вероятно все читали замечательное эссе Пола Грэма "Хакеры и художники", но очень хочется привести из него один фрагмент, который мне нравится особенно сильно:
Хакерам теория вычислений нужна не больше, чем художникам - химия красок. Надо знать, как высчитывать время и пространственную сложность, еще - про Тьюрингову полноту. Ну, еще не помешает помнить хотя бы концепцию конечного автомата - на случай, если пишешь синтаксический анализатор или библиотеку регулярных выражений. Вообще-то художникам приходится помнить о химическом составе красок гораздо больше.

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

К примеру, меня в колледже учили: прежде, чем приблизиться к компьютеру, программу следует целиком записать на бумаге. Оказывается, я программировал не так. Мне нравилось программировать перед компьютером, а не над листом бумаги. Хуже того, я не писал терпеливо всю программу, проверяя, нет ли ошибок, я извергал безнадежно кривой код и постепенно приводил его в норму. Отладка, учили меня, - это последний заход, когда вылавливаешь опечатки и упущения. Я же работал так, что программирование походило на бесконечную отладку.

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

Осознание этого факта реально влияет на разработку ПО. Язык программирования прежде всего должен быть гибким. Язык программирования - чтобы думать о программах, а не формулировать программы, которые уже обдуманы. Карандаш, а не ручка. Статический контроль типов - неплохая штука, если б программировали и впрямь так, как учат в колледже. Но ни один известный мне хакер так не работает. Нам требуется язык, что позволит карябать, сажать кляксы и стирать, а не вроде как сидишь с чашкой типов данных и вежливо беседуешь со строгой престарелой тетушкой-компилятором.

Раз уж мы заговорили о статическом контроле, то вот что. Назвавшись творцами, мы избавимся еще от одной проблемы, что терзает науки: от зависти к математике. Любой ученый втайне верит, что математики всех умнее. Кажется, математики в это верят не меньше остальных. А в итоге ученые стараются, чтобы их работа на вид получалась математической до предела. Может, в какой-нибудь физике это и не беда, но чем дальше от естественных наук, тем больше усугубляется проблема.

Ну, разумеется - страница формул впечатляет. (Совет: для особой выразительности вводите греческие переменные.) И потому так соблазнительно заняться проблемами, к которым можно подойти формально, а не над теми, что, скажем так, важны.

Если бы хакеры видели в себе творцов - писателей или художников, - такого соблазна бы не возникало. Писатели и художники математикам не завидуют. Они считают, что занимаются чем-то совершенно не связанным с математикой. Как и хакеры, я полагаю.

понедельник, 23 августа 2010 г.

RESTAS - обновление документации и версия 0.0.10

Обновил документацию на RESTAS - добавил API Reference. Теперь все упоминания каких-либо функций или макросов превратились ссылки на соответствующее описание в API Reference. А что бы документация не отличалась от текущий возможностей сделал версию 0.0.10.

Ну и пара слов о cl-sphinx, с помощью которой я делаю документацию. Немного допилил её для обеспечения ссылок на определения, теперь можно в одном месте вставить, например, описание функции:
.. defun:: restas:reconnect-all-routes
:args:

Реинициализирует диспетчер запросов. и т.п.
а в других ссылаться на это определение с помощью
:fun:`restas:reconnect-all-routes`
Получается очень удобно и просто.

пятница, 20 августа 2010 г.

Common Lisp и DSL. Другой взгляд.

swizard опубликовал статью о Common Lisp и DSL, в которой достаточно ясно изложил мнение, что наиболее эффективное использование Common Lisp заключается в разработке DSL. Мало того, он предлагает начинающим начинать именно с этого. Позвольте не согласится.

Кратко - DSL не нужны кроме тех редких случаев, когда они очень хороши и реально упрощают решение задачи. Это не имеет отношения к Common Lisp, либо к какому либо другому языку. Например, Эрик Раймонд писал о DSL как о классической традиции программирования под UNIX и всяческих расхваливал преимущества данного подхода. Однако на практике лишь малая часть Unix-программ используют какие-либо DSL. Это однозначно свидетельствует о том, что разработчики, несмотря на все разрекламированные свойства DSL, стараются избегать их использования (да, я действительно считаю, что "миллионы мух не могут ошибаться", поскольку речь идёт не о мухах, а о, по большей части, талантливых разработчиках).

Если говорить о Common Lisp, то мне совершенно не понятно на чём основано мнение, что "common lisp и заслужил себе славу мета-языка " (с). В реальной практике программирования на Common Lisp использование DSL встречается редко. Связано это с тем, что, на мой взгляд, использование DSL просто противоречит духу Common Lisp, по крайней мере, в его современном виде.

Наиболее сильной и характерной чертой Common Lisp является мощная поддержка интерактивной разработки, что значительно упрощает постоянный рефакторинг кода и проектирование "снизу вверх", которые характерны для современных подходов к разработке. Программирования на основе DSL, однако, требует другого подхода: задача делится на две подзадачи - проектирование DSL и описание решения на этом DSL. Проектирования языка программирования, пусть даже это маленький DSL, это сложная задача, повышающая требования к качеству проектирования системы. В контексте современных задач, когда приложение работает сразу во многих областях, также существенно усложняется выделения предметной области для конкретного DSL. Сложность возникающих проблем дизайна приводит к необходимости проектирования "сверху вниз", что стразу убивает большую часть преимуществ интерактивной разработки. Поскольку для современных задач характерна неточность в требованиях, которые выясняются по мере разработки (обычно, ближе к концу), то проектирование "сверху вниз" связано со слишком большими рисками и сопутствующими накладными расходами.

Использование DSL возможно и целесообразно лишь в некоторых областях, но не для широкого круга задач. ИМХО, мнение о том, что при программировании на Common Lisp следует широко использовать DSL основано на позиции старой "lisp-гвардии", представители которой застряли где-то в начале 80-ых.

четверг, 19 августа 2010 г.

Обновление документации на RESTAS

Обновил документацию на RESTAS, дополнив инструкции по установке, а также добавил статья про интеграцию SLIME и RESTAS.

среда, 18 августа 2010 г.

Ebuild на iolib-0.7.0

Наконец появился ebuild на iolib-0.7.0, но он зависит от cffi-9999.ebuild, с чем я, конечно смириться не могу (ибо это исключает возможность использования данного пакета на боевых серверах). Поэтому, в своём оверлее сделал cffi-0.10.5_p20100818.ebuild и установил зависимость iolib от него как
>=dev-lisp/cffi-0.10.5_p20100818
Немного протестил - вроде работает нормально.

вторник, 17 августа 2010 г.

Выравнивание в closure-template-html-mode

Благодаря помощи cvb удалось реализовать мою мечту - прикрутить выравнивание кода в closure-template-html-mode.el (из состава cl-closure-template). Осталось ещё несколько тонких моментов, которые не учитываются реализованным механизмом, но такие ситуации встречаются довольно редко. Теперь править код шаблонов стало намного, намного приятней. Ну и заодно немного разобрался как это всё реализуется в Emacs.

четверг, 12 августа 2010 г.

Chrome: Edit with Emacs

Давно хотел заполучить для Chromium (Chrome) аналог It's All Text! и вот, наконец, с подсказки dmitry_vk нашёл Edit with Emacs. Данный пост написан с помощью этого расширения. Жизнь, однако, налаживается...

среда, 11 августа 2010 г.

Запретить drag-and-drop изображений в Firefox

В Firefox есть очень противная вещь - если нажать на мышкой на изображение, то инициируется совершенно бессмысленный drag-and-drop. Мне это просто не нравится, но тут ещё и мешает моему приложению, ибо у меня своя реализация drag-and-drop и она должна срабатывать на div, который в том числе содерижт img, но если пользователь щёлкает на img, то начинается совершенно левый перенос картинки. В webkit есть css-свойство -webkit-user-drag, которое можно выставить в none, но для Firefox его аналога нет. Немного потыкал гугл, но нужного мне рецепта не нашёл. Пришлось найти самому, впрочем, он тривиален:
$(document).bind("dragstart", function (evt) { evt.preventDefault(); } );
Всё, никакого левого drag-and-drop.

вторник, 10 августа 2010 г.

CL vs Haskell, динамика vs статика и прочий бред

Тут кажется наметилась новая волна срача на обозначенные темы. При этом, типично спор сводится к выяснению программист с каким инструментом будет более эффективен. Типа что можно сделать за 8 часов на Haskell, а что на CL. Даже если не принимать во внимание тот факт, что для самых типовых задач обычно больше можно сделать на Java, ибо просто библиотек больше, то всё равно это бессмыслица. Пять дней в неделю по восемь часов (или типо того) мы якобы пишем код. Если бы мы действительно тратили это время на написание кода, тогда имело бы наверное смысл проводить подобные сравнения. Я не знаю как все, но я не могу. Если я трачу 4 часа в день на код, то это обычно удачный день. Многие дни у меня уходят просто в пустую. Я просто не могу собраться и сосредоточиться на работе. Поскольку при этом я обычно всегда делал намного больше окружающих, то имею основания полагать, что описанная ситуация является весьма типичной. Да, ещё иногда случаются приступы вдохновения, когда я работаю по 14-16 часов с полной концентрацией, чувствую себя счастливым и суммарный эффект от таких редких периодов намного превышает эффект от всех остальных дней.

Если говорить о повышении производительности, то технические аспекты языка: есть автоматическая сборка мусора или нет, есть вывод типов или нет, функциональный или императивный и т.п. вообще не имеют особого значения. Ибо основой способ повышения производительности заключается в увеличении времени, которое я реально трачу на решение задачи: в уменьшении количества "пустых" дней, в увеличении числа эффективных часов в сутки, в повышении частоты "приступов вдохновения". Для этого наверное есть разные приёмы. Например, Хемингуэй (видимо, в писательской деятельности есть те же самые проблемы) писал, что нельзя выписываться полностью, всегда нужно останавливаться когда ещё есть что сказать, тогда на следующий день можно эффективно включиться работу.

Peter Seibel пишет в PCL, что
Выгоды от использования Lisp заключены в переживаниях и впечатлениях от его использования
И это ключевой аспект, о котором часто забывают. Лично для меня Common Lisp это язык, который лучше других способствует поддержанию интереса к разработке и позволяет более эффективно справляться с периодами бездействия. Для кого-то, видимо, подобным языком является Haskell (хотя мне и трудно это представить) и он выбирает его. А другие способны не засыпать, когда пишут на Java.

пятница, 6 августа 2010 г.

iolib-0.7.0 издевается, да и не только она

На днях вышел новый, и давно ожидаемый, релиз библиотеки IOLib - 0.7.0. Поскольку Stelian Ionescu, разработчик этой библиотеки, так же является основным разработчиком и gentoo-lisp-overlay, то я ожидал, что сразу же появится и соответствующий ebuild. Однако, его пока нет. И раздумывая над этим фактом я вдруг сообразил, что IOLib-0.7.0 зависит от нестабильных git-версий alexandria и CFFI. Вот на этом факте я совершенно запутался и перестал понимать: что может означать релиз библиотеки, которая зависит от функционала, который ещё не был выпущен в виде какого-либо релиза? При чём, ситуация с alexandria вообще является клинической, ибо проект существует уже не один год, а ни одного релиза до сих пор не было! Ситуация с CFFI тоже интересна, ибо Stelian является один из её разработчиков и мог бы повлиять на выход нового релиза, да бы совместить с релизом IOLib, но нет, этого не происходит. Блин, давно же известно, что делать релизы надо чаще и раньше (ну если вы не в команде Debian), но нет, среди CL-разработчиком этому правилу следуют, кажется, только разработчики SBCL.

Вообще, в принципе, мне нравится что делает Stelian Ionescu, по мысли, по заложенным идеям очень хорошо. Но... степень хаоса в связанных с ним проектов, по моим представлениям (и наблюдениям), просто зашкаливает. Что вызывает серьёзное беспокойство и опасения насчёт целесообразности использования.

вторник, 3 августа 2010 г.

Статья об использовании RESTAS для создания pastebin-приложения

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

Статья: http://restas.lisper.ru/tutorial/pastebin.html

суббота, 31 июля 2010 г.

Обновление restas.lisper.ru

Привёл документацию на RESTAS к более-менее приемлемому состоянию, так что теперь можно читать, задавать вопросы и сообщать о разных ошибках и глупостях в тексте.

http://restas.lisper.ru/

пятница, 30 июля 2010 г.

cl-sphinx и restas.lisper.ru

Накидал первую версию cl-sphinx, аналог http://sphinx.pocoo.org/ (на мой взгляд, лучшего пакета для создания документации). Возможности весьма ограниченны, а стадия разработки самая начальная, но кое-что уже делать можно. С помощью уже имеющегося функционала сделал типа документации к RESTAS (на самом деле это не документация, а несколько предыдущих статей из блога), посмотреть можно здесь: restas.lisper.ru. Надеюсь, что теперь таки смогу заняться документацией (раньше мне не хватало приемлемого инструмента). Посмотреть исходники этой "документации" можно здесь: http://github.com/archimag/restas/tree/master/docs/ru/.

вторник, 27 июля 2010 г.

Исходный код cliki

Из любопытства посмотрел исходный код cliki - думаю, что его стоит засекретить, что бы никто его не увидел, не ужаснулся и не объявил, что CL полное г..., особенно для разработки web-приложений.

вторник, 20 июля 2010 г.

layman и archimag-lisp-overlay

Немного упростил использования моего lisp-оверлея (который форк стандартного gentoo-lisp-overlay) с помощью layman (Gentoo). Теперь, для подключения моего оверлея необходимо установить layman
$ emerge layman
добавить http://github.com/archimag/archimag-lisp-overlay/raw/master/layman-list.xml в "список списков оверлеев" (!), который находиться в файле /etc/layman/layman.cfg, например
overlays  : http://www.gentoo.org/proj/en/overlays/repositories.xml
http://github.com/archimag/archimag-lisp-overlay/raw/master/layman-list.xml
и наконец
layman -f -a archimag-lisp
Всё.

понедельник, 12 июля 2010 г.

RESTAS: упрощение отладки удалённых серверов

Я писал ранее, что в репозиторий Hunchentoot был принят мой патч, который должен был упростить отладку удалённых серверов. Однако, в последующем он был отменён с сообщением: "Fix breakage of LW version". LispWorks у меня нет, в чём проблема там я не знаю, но Edi мог хотя бы сообщить об этом в рассылку, а то ведь этого можно было и не заметить. Ну да ладно, немного поразмыслив решил просто перенести данный функционал в код RESTAS. Теперь отладочные режимы RESTAS и Hunchentoot независимы друг от друга. Теперь в RESTAS:
  • Постоянно поддерживается список отлаживаемых в данный момент потоков
  • Добавлен параметр *max-debugging-threads*: максимально возможное количество одновременно отлаживаемых потоков, значение по умолчанию - 5.
  • Функция debug-mode-on - активизирует отладочный режим.
  • Функция debug-mode-off - отменяет отладочный режим, имеет необязательный параметр kill-debugging-threads (по-умолчанию T) , который определяет надо ли уничтожать отлаживаемые в данный момент потоки.
  • В случая наличия в системе swank-сервера, в переменную swank::*connection-closed-hook* добавляется вызов debug-mode-off , который обеспечивает отмену отладочного режима и уничтожение отлаживаемых потоков после разрыва соединения.
  • Описанная схема применяется только к потокам, которые созданы Hunchentoot для обработки запросов.

вторник, 6 июля 2010 г.

Made with Common Lisp 2: screencast

Я уже показывал скриншот моего нового интерфеса, но тогда не было скринкаста - обещал его чуть попозже. Вот, теперь могу кое-что показать:



Некоторые товары отображаются не очень качество, но это проблема Chronium - с Firefox и Opera всё отображается прекрасно, но на Linux они подттормаживают (на Windows всё работает прекрасно).

четверг, 1 июля 2010 г.

В ожидании...

Итак, слухи потвердились, Google купил таки ITA за 700 млн. долларов: http://www.google.com/press/ita/. Поскольку для ITA ключевой технологией является Common Lisp, то встаёт интересный вопрос - какое влияние эта сделка окажет на дальнейшее развитие Common Lisp?

Ностальгия по C++

Что-то временами хочется пописать на C++, но куда его приложить в окружающих задачах не знаю. Надо бы собраться с духом и сесть патчить Chromium, а то он фильтры feImage в SVG очень криво поддерживает, а они мне нужны. Будет и польза и навыки в C++ теряться не будут...

среда, 30 июня 2010 г.

Common Lisp на миллиард

Самой известной организацией, использующей Common Lisp для разработки коммерческих систем, является, вероятно, ITA Software и кажется это единственная организация, озабоченная проблемами разработки крупномасштабного ПО c помощью Common Lisp (XCVB родилась, как раз, ради нужд этой организации). До не давнего времени я был совершенно не в курсе о размере этой организации, но тут с некоторого времени стали курсировать слухи, что Google пытается её купить и речь идёт о сумме, близкой к одному миллиарду долларов. Мне кажется, что это достаточно крупная сумма для того, что бы навсегда закрыть вопрос о возможности/целесообразности коммерческого применения Common Lisp...

понедельник, 28 июня 2010 г.

Секс, Java и рок-н-ролл

http://www.youtube.com/watch?v=A1zySeNpW20

archimag-lisp-overlay

Сегодня с моим форком gentoo-lisp-overlay, который базировался на github.com, случилось несчастье - он отказался push-ится, мотивируя это тем, что мол нет доступа к ./objects, как будто это и не мой репозиторий вовсе. В поддержке gitnub есть зарегистрированное подобное сообщение, но там человека сразу послали, мол это не проблемы github. Интересно, а чьи это проблемы, если проекты пушился-пушился и вдруг перестал? Потыкавшись, решил попробовать удалить его и создать заново - но создаваться заново с таким же именем он не захотел, мотивируя это уже совсем чем-то странным. Так что, пришлось окончательно его грохнуть и создать новый archimag-lisp-overlay, куда всё и залил. Вроде поломанные при этом ссылки (таких было несколько) починил и теперь всё должно быть как прежде, но только в немного другом месте.

пятница, 11 июня 2010 г.

Продолжение дискуссии: дизайн

Собственно, продолжаем обсуждение проблем разработки программного обеспечения. Тут придётся немного апеллировать к утверждениям из поста thesz, так что прощу прощения за перепечатки.
Если программа может быть написана двумя или более способами, то необходимо произвести выбор одного из них. Archimag предлагает выбрать дизайн наугад и потом оценить, подошёл ли он.
Во-первых, любая программа может быть написана бесконечным количеством способом. Т.е. ситуация, когда имеется только один вариант дизайна не бывает в принципе. Во-вторых, я никогда не действую наугад и, естественно, не рекомендую это делать другим.
Желательно бы делать лучше, например, уметь сравнивать дизайны между собой.
В том самом обсуждении я предложил следующий вариант: сперва сравниваем дизайны по тому, как много сценариев программы они покрывают, потом сравниваем по количеству кода, потребному для реализации дизайна. Такая несложная иерархия.

Если включить в рассмотрение важность сценариев и сложность их реализации и отсортировать их, как это рекомендует gaperton, то получится хорошая система оценки дизайна программы, ещё более иерархичная, быстрее отсеивающая относительно плохие дизайны.
Слава роботам! Так действуют машины, когда им надо решить какую-нибудь более-менее сложную задачу. Т.е. в идеале нужно перебрать все варианты и из них выбрать самый подходящий. Когда вариантов бесконечно много (как в случае с дизайном) надо действовать чуть хитрее. Поиск в пространстве состояний сейчас достаточно хорошо проработанная область, но она содержит алгоритмы для машин, не для людей. Точнее, мы используем эти алгоритмы для того, что бы научить машину делать нечто, для чего ранее требовался человеческий интеллект. Возможно, когда-нибудь программы будут писать роботы, который действительно будут действовать схожим образом. Хотя тоже сомнительно, ибо простой перебор вариантов не работает даже в шахматах, которые хоть и являются довольно сложной проблемой, но довольно ограниченной и, мало того, конечной. В целом, я считаю предложенный способ сравнения, по крайней мере, наивным.

Человек действует не так. Мы не может позволить себе писать программу несколько раз и выбирать лучший вариант (впрочем, подобный процесс происходит на более высоком уровне: разные команды пишут схожие продукты, среди которых практика использование отбирает лучшие.).
типы языка Хаскель основываются на логике, по-моему, на классическом её варианте (могу быть неправ, но в прилагательном;). Выражая типы компонент и пробуя прикинуть приблизительную их реализацию Хаскеле мы проверяем наш дизайн с помощью системы автоматического доказательства теорем. Это помогает отсевать те варианты дизайна, что не могут быть логически стройно сформулированы.
Слава роботам! Или не слава... Любое доказательство теорем основывается на других теоремах и аксиомах. Если нижележащие теоремы/аксиомы неверны, то любое основанное на них доказательство таковым не является и вообще ничего не значит, т.е. не имеет никакой ценности. При разработке программного обеспечения, в большинстве случаев (за исключением некоторых, достаточно редких в повседневной практике областей) мы не обладаем полнотой информации о создаваемой системе, а значит просто не можем физически положить в основу корректный набор аксиом (и теорем).

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

Цели

Я считаю, что разработка дизайна преследует следующие цели (необходимость удовлетворения требованиям задачи я считаю само-самой разумеющимся и отдельного рассмотрения не требующим, в конце концов - это цель всей программы, а не одного дизайна):
  • Уменьшение сложности
  • Устранение дублирования кода
  • Повышение гибкости, готовность к изменениям
Кстати, с этих позиций довольно просто обосновать, например, опасность перегрузки операторов в С++, ибо с точки зрения дизайна она только повышает сложность и больше ничего.

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

Можно ещё раз обратить внимание на особенную значимость простоты, поскольку она определённо снижает количество ошибок (в том числе, нетривиальных) и значительно упрощает понимание системы другими людьми.

Инструмент

Основным инструментов для работы над дизайном является декомпозиция. Если говорить в целом, то моя позиция заключается в том, что цель декомпозиции найти "хорошие простые ответы на правильные вопросы", при чём, как обычно "правильные вопросы" это больше половины дела. Применительно к языкам программирования имеет смысл выделять какие средства предоставляет язык для отображения выбранного способа декомпозиции в коде. Для Common Lisp я выделяю следующие возможности, которые определяют дизайн системы:
  • Функции (включая замыкания)
  • Структуры данных (структуры, классы, различные виды списков и т.д.)
  • Generic-фукнции (которые следует рассматривать отдельно и от функций, и от классов)
  • Макросы
  • Динамические переменные
  • Рестарты
  • Пакеты
Это довольно много (по сравнению с большинством других языков) и с одной стороны предоставляет весьма мощный набор инструментов, которые могу облегчить процесс создания качественного дизайна, а с другой такое богатство может завести разработчика в дебри. Кстати, проблема многообразия средств также характерна и для С++, который заслужил благодаря этому довольно дурную славу, но это скорей не проблема языка, а проблема уровня самодисциплины у разработчиков. В Common Lisp же эта проблема выражена ещё сильнее и разработчик должен постоянно контролировать свое желание "заюзать ещё пару крутых фич".

Кстати, насчёт "хороших простых ответов на правильные вопросы" хочу привести однин, как мне кажется, вполне уместный исторический пример: Коперник vs Птолемей. С точки зрения математики, особой разницы между подходами Коперника и Птолемея нет, они оба вполне корректны, мало того, опять же с точки зрения математики последователи Птолемея делали совершенно потрясающую вещь - раскладывали орбиты планет в ряды Фурье и это за 1000 лет до появления математического анализа! Коперник же задал правильный вопрос (что вокруг чего вращается?) и дал на него простой ответ, в результате смог объяснить наблюдаемое движение небесных тел с помощь значительно более простого математического аппарата.

Процесс
Определившись с целями и доступным инструментом можно попробовать разобраться в процессе создания дизайна. Если рассматривать вопросы декомпозиции, то думаю все согласятся, что "правильная декомпозиция с первого раза" для достаточно сложных проектов может является замечательным примером человеческого гения, периодически освещающего наш мир. В большинстве же случаев необходим достаточно длительный поиск подходящего решения, основанный на интуиции, способности предвидеть и понимании создаваемой системы. Ключевым фактором является склонность человека к ошибкам, а также замечательная способность мозга создавать иллюзию того, что мы обладаем полнотой информации. Единственным известным мне действенным способ проверки правильности принятых решений является непосредственное написание кода (который можно реально запустить), во время которого выявляются все не-стыковки и противоречия в дизайне. Одним из, уже можно считать, традиционных и устоявшихся способов проверки дизайна является написание unit-тестов: хотя разработка программы может находиться ещё только в самой ранней стадии, благодаря им мы получаем реальный код, который можно запустить и проверить принятые решения. Другим, ещё более заслуженным способом является создание прототипов. Common Lisp предоставляет ещё одну блестящую возможность, которая может быть эффективно использована для работы над дизайном - REPL. Тут я хочу отдельно отметить, что под REPL я понимаю не только командную строку (которая есть сейчас для многих динамических языков: Python, Ruby, JavaScript и т.п.), но целый комплекс средств, предоставляемых SLIME или IDE коммерческих реализаций, которые значительно упрощают интерактивную разработку (например, простая перекомпиляция отдельной функции или класса непосредственно во время работы над исходным кодом, наглядное раскрытие макросов и т.п.). Очень простая возможность проверки решений буквально подталкивает разработчика к большому количеству экспериментов с исходным кодом, что создаёт все предпосылки для создания качественного дизайна. При этом, весьма важную роль играет динамическая типизация, которая позволяет нарушать общую корректность программы ради проверки какой-либо идеи. В языках со статической проверкой типов внесение изменений ради эксперимента может оказаться болезненным, что препятствует эффективному поиску (обычно люди избегают боли).

В целом, хоть я и считаю, что Common Lisp предоставляет выдающийся набор инструментов для декомпозиции, но полагаю, что наиболее сильная его сторона заключается в предлагаемом процессе разработки (для поддержки которого, правда, нужен соответствующий инструмент, например SLIME).

четверг, 10 июня 2010 г.

cl-json и plists

Сначала несколько слов о JSON и Common Lisp.

Есть достаточно распространённое мнение, что CL хорошо подходит для генерации всяких XML, HTML и т.п., и для решения подобных задач предлагается множество решений на базе s-выражений. Из своего опыта могу заключить, что подобное мнения однозначно является заблуждением и для генерации XML/HTML лучше всего подходят шаблоны, но в этом случае CL не имеет специфичных преимуществ над другими языками (ну кроме тех, что CL просто лучший язык).

А вот JSON действительно очень хорошо подходит для использования с Common Lisp, ибо может непосредственно отображаться на списки, которые являются основной структурой данных в CL. Поскольку JSON также непосредственно отображается и на хэш-таблицы, которые являются основным типов данных для JavaScript (можно утверждать, что хэш-таблицы в JavaScript имеют такое же ключевое значение, как списки в CL), то связка CL<=>JavaScript с взаимодействием на основе JSON вообще смотрится очень выигрышной: и там и там без каких-либо дополнительных усилий можно непосредственно работать с наиболее естественной формой представления данных.

В какой-то момент для работы с JSON в Common Lisp я выбрал библиотеку cl-json, уже точно не помню, что мной двигало, но наверное то, что это простое наиболее известное решение. Сейчас я подумываю о том, что бы изменить это решение, ибо авторы cl-json кажется уж очень увлеклись своими концепциями. Но не суть. Так вот, в этой библиотеке для представления объектов/хэшов используются alists, которые я считаю не удобными для использования человеком и предпочитаю строить свои решения на базе plists. Соответственно, имеющаяся у меня инфраструктура гораздо более дружественна к plists и я захотел переоренитровать cl-json на работу с plists. Добился я этого следующим кодом, который поясняет моё отношение к данной библиотеке - он хотя и довольно краток, но его нельзя написать без изучения исходников библиотеки:
(defun encode-json (obj)
(flet ((encode-json-list (list stream)
(if (keywordp (car list))
(json:encode-json-plist list stream)
(json::encode-json-list-guessing-encoder list stream))))
(let ((json::*json-list-encoder-fn* #'encode-json-list))
(json:encode-json-to-string obj))))

(defun decode-json (str)
(flet ((accumulator-add-pkey (key)
(json::accumulator-add (funcall json:*identifier-name-to-key*
(funcall json:*json-identifier-name-to-lisp* key))))
(accumulator-add-pvalue (value)
(json::accumulator-add value)))
(let ((json:*object-key-handler* #'accumulator-add-pkey)
(json:*object-value-handler* #'accumulator-add-pvalue))
(json:decode-json-from-string str))))


CL-USER> (encode-json '(:a 1 :b (:c 2 :d 3) :e 4))
"{\"a\":1,\"b\":{\"c\":2,\"d\":3},\"e\":4}"
CL-USER> (decode-json (encode-json '(:a 1 :b (:c 2 :d 3) :e 4)))
(:A 1 :B (:C 2 :D 3) :E 4)
Да, я использую darcs-версию библиотеки, которая содержит важное изменение, почему-то не оформленное в виде какого-либо релиза.

среда, 9 июня 2010 г.

Дискуссия

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


Здесь речь идёт о повышении в течении последних 20 лет популярности языков с динамической типизацией, что вряд ли является следствием развития CS. Ответ заключается в том, что развитие "практического программирования" это прежде всего не появление новых фич в языках и теоретических подходов, а прежде всего накопление и критическое переосмысление опыта разработки реальных систем. Результатом данного процесса, в первую очередь, является появление методик разработки, непосредственно не привязанных к используемым инструментам. Такие практики, как "парное программирование", "code review" или "тесты вперёд" вряд ли даже с натяжкой могут быть отнесены к области CS, но именно они вносят наиболее серьёзные изменения в процесс разработки ПО. Поэтому, процесс популяризации динамических языков я называю именно развитие поскольку в результате этого произошло (и продолжает происходить) существенное изменение методологии, а инструмент нельзя рассматривать отдельно от методик его использования. Возможно, Python и не блещет глубиной идей, но на развитие методологии разработки он оказал гораздо большее влияние, чем все наследники ML вместе взятые.

> почему вы считаете, что дизайн не имеет критериев сравнения

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

> в моём посте приведён пример "дизайна системы" на основе типов, причём присутствует не менее двух итераций, попроще и посложнее; и до этого писал похожие посты; почему вы считаете, что никто и никогда не показывает, как пользоваться типами при дизайне систем,

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

> почему вы считаете, что для программирования не нужен ум (и это особенно интересно),

Вообще-то, ничего такого я не говорил. А говорил, что количество ошибок в коде не связано с умом, а скорей со свойствами характера. Собственно, такое наблюдение я сделал как в результате непосредственной работы с другими программистами, так и в результате наблюдения за некоторым open-source проектами.

Впрочем, у меня есть кое-какое мнение и о затронутой теме. Как мерить ум я не знаю, мне известна только методика измерения IQ, которая вызывает много нареканий. Но, за неимением другой, можно попробовать осторожно опереться на неё. Так вот, большинство программистов, с которыми я общался, имели уровень интеллекта выше среднего и думаю, что вполне можно считать, что большинство программистов имеют уровень IQ в районе 120-140. Где-то мне попадалась информация, что большинство Нобелевских лауреатов имеют уровень IQ в районе 130, а 120 встречается чаще, чем 140. Поскольку вряд ли деятельность, за которую вручают Нобелевские премии, является менее сложной, чем программирование, то можно утверждать, что для достижения выдающихся результатов после превышения определённого уровня IQ наиболее значимым являются личностные качества. Т.е. уровень IQ в 120 позволяет эффективно работать даже над весьма сложными задачами, а определяющим фактором для успеха являются свойства характера. Я не вижу каких-либо оснований утверждать, что программист с уровнем IQ в 140 будет более эффективен, чем программист с уровнем IQ в 120.

> почему вы считаете не зафиксированное в документе ТЗ отсутствующим ТЗ

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

> известна ли вам теория тестирования

В своё время прочитал довольно нудную книгу "Введение в тестирование программного обеспечения" (Луиза Тамре), но там излагается скорей практическое руководство, чем какая-либо теория.

> знаете ли вы об использовании типов для автоматической генерации тестов в библиотеке QuackCheck

Нет.

> можно ли протестировать корректность параллельной программы

Протестировать можно всё, что угодно, а вот гарантировать обычно ничего нельзя. Впрочем, для веб-приложений используются системы, которые генерируют нагрузку на веб-сервер аналогичную реальному трафику. Повышая интенсивность нагрузки можно в достаточно сжатые сроки провести весьма качественное тестирование, которое, хотя ничего и не гарантирует, но обычно позволяет выявить практически значимые проблемы. Наверняка, схожие подходы можно использовать для тестирования большинства параллельных программ. Кстати, для тестирования потоков в SBCL под Windows, реализацией которых сейчас занимается dmitry_vk, как раз в том числе использовалось тестирование веб-сервера и тривиального генератора нагрузки.

Кажется всё. Хотя пост и не посвящен обсуждение Common Lisp, но тем не менее я использую тэг lisp для того, что бы данный пост попал в Russian Lambda Planet.

вторник, 8 июня 2010 г.

Made with Common Lisp 2

Я уже показывал ранее скриншот и скринкаст моего основного рабочего проекта, но у меня уже почти готова новая версия, я решил показать новый скриншот. Собственно, вот он:


Скринкаста пока не будет, ибо всё таки функционал ещё готов не полностью.

В отличие от предыдущей версии, эта работает не только с Firefox, но также с Chrome и Opera. И вообще, ориентируюсь на HTML5.

Ключевое значение в данном проекте приобрела cl-closure-template, ибо позволила сделать интерфейс более сложным и одновременно заметно сократить размер и сложность JavaScript кода (а также оказывает заметное влияние на дизайн серверного кода), так что теперь я могу смело утверждать, что Common Lisp используется не только на серверной стороне, но так же имеет ключевое значение и на стороне клиента.

пятница, 4 июня 2010 г.

iolib.process

Давно хотел, но то времени не было, то мотивация подводила, и вот вчера звёзды совпали и я написал iolib.process - очень простую библиотеку для создания дочерних процессов и взаимодействия с ними через стандартные потоки ввода/вывода. Её можно рассматривать как не зависимую от реализации альтернативу sb-ext:run-programm - реализация полностью выполнена на основе возможностей библиотеки iolib. По дизайну она существенно отличается от sb-ext:run-programm, которая представляется мне излишне запутанной: sb-ext:run-programm это, конечно, крутая вещь, но мне не очень понятны потребности человека, которые её разрабатывал. iolib.process требует git-версию iolib и должна работать, в принципе, на любых UNIX-системах. Можно было бы сделать и windows-версию (я писал когда-то соответствующий код на C++), но iolib не содержит необходимый инфраструктуры (да и windows у меня уже давно нигде нет). Для запуска дочерних процессов используется sh (которая, кажется есть на всех юниксах), так что нет потребности указывать полный путь или там каталоги поиска и можно даже вызывать конвейеры, например:
CL-USER> (iproc:with-child-process (conveyer "cat | grep good" :stdin t :stdout t)
(let ((*standard-output* (iproc:process-input conveyer)))
(write-line "Haskell is bad")
(write-line "Python is bad")
(write-line "Common Lisp is good")
(write-line "IMHO"))
(iproc:process-input-close conveyer)
(read-line (iproc:process-output conveyer)))
"Common Lisp is good"

среда, 2 июня 2010 г.

GPL 3 - кусается

Эх, первый раз испытал отрицательные эмоции от того, что какой-либо проект использует GPL 3. Речь о библиотеке cl-data-format-validation. Я связался с автором на предмет возможного распространения этой библиотеки на условиях Lisp LGPL и получил следующий ответ:
Given Common-Lisps dynamic nature and the shortage of libraries (compared to more recent toy languages) I consider it appropriate to just use GPL3 - not LGPL. In the unlikely event someone wants to use my library code in a proprietary derivative they can contact me for a different license however I am not going to support them for free.
Вот ну очень жаль :( И дело не в том, что данная библиотека делает что-то фантастическое. На самом деле это довольно просто решение. Но, при этом, это очень точно решение, предоставляющее общий подход для упрощения сразу целого ряда типовых для web-разработки проблем. И это очень соответствует моему пониманию разработки, что даже сложные проблемы имеют достаточно простые решения если найти "критические точки", проблема лишь в том, что бы правильно определить эти самые "точки приложения силы" (а это всегда не просто, хоть потом решение и кажется очевидным). И с этой позиции cl-data-format-validation представляется мне очень точным и грамотным решением.

Отказаться от данной идеи выше моих сил, так что, видимо, придётся писать свою библиотеку на данную тему, но под Lisp LGPL, а также с блэкджеком и развратными женщинами, ну т.е. с тестами и поддержкой локализации. Где вот только время на это найти...

пятница, 7 мая 2010 г.

Форум на lisper.ru стал лучше

Вчера на lisper.ru было побито два рекорда:
  • Было создано самое большое обсуждение
  • Аналика от Google показала рекордную посещаемость (она конечно мала, но самая большая за историю ресурса)
В связи с этим (особенно первым пунктом) произвёл давно нужные улучшения форума:
  • Обсуждения теперь бьются на страницы, на одной может быть максимум 50 сообщений
  • Пришлось таки сделать более нормальные RSS-ленты - теперь при переходе на комментарий из RSS-ридера страница позиционируется по этому комментарию, а сам он подсвечивается.

вторник, 4 мая 2010 г.

Недоступность lisper.ru

В течении некоторого времени (кажется, больше суток) http://lisper.ru/ (а также, например, http://pcl.catap.ru/) был не доступен (даже ни пинговался). Если кто не в курсе ситуации и беспокоился о судьбе ресурса ;), то сообщаю, что всё хорошо, просто были некоторые технические трудности у провайдера хостинга (угадайте какого; но он всё равно остаётся самым лучшим провайдером в мире!) :) Сейчас работаем в штатном режиме.

воскресенье, 2 мая 2010 г.

Парсим reStructuredText на Common Lisp. Часть 2..

В предыдущей части я показал, как можно очень легко парсить простые элементы. Но в размеке reStructuredText также имеются сложные элементы, простейшим из которых является Section, вот несколько примеров:
=============
Hello world
=============

Hello world
-----------

Hello world
!!!!!!!!!!!
Секция обозначается с помощью "украшающей" линии, которая должна быть под заголовком секции. Также, "украшающая" линия может быть дополнительно и над заголовков. Если имеется только нижняя декорация, то количество символов в ней должно совпадать с количеством символов в названии секции, а само название не может начинаться с пробельного символа. Если есть и нижняя и верхняя декорация, то в начале названия можно вставлять пробельные символы, но длина заголовка должна быть меньше длины декорации. Верхняя (если есть) декорация должна совпадать с нижней. Декорация должна состоять из одинаковых символов, разрешены следующие символы: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ . В общем, как-то так :)

Ядро wiki-parser - это чуть более 200 строк кода, которые обеспечивают конечный автомат, почти полностью управляемый с помощью регулярных выражений. Код несколько запутан, но концептуально это очень простая схема, позволяющая легко описывать разметку, например, подобную разметке dokuwiki. Однако, statemashin.py (из docutils) это значительно более сложная схема, которая допускает значительно большую гибкость, но и требует значительно больших усилий по программированию. И связано это с необходимостью обрабатывать конструкции, подобные описанным Section, а в разметке reStructuredText есть ещё, например, куда более сложные таблицы. Ключевая проблема - невозможность описания подобной разметки на основе регулярных выражений.

Однако, тут следует сделать оговорку, что нельзя составить необходимое регулярное выражение в Perl или Python, но библиотека cl-ppcre поддерживает фантастическую возможность - фильтры.

cl-ppcre имеет два варианта описания регулярных выражений - традиционный с помощью строки в стиле Perl, и вариант описания на основе s-выражений. Фактически, строковое представление всегда сначала парсится именно в форму s-выражений. Синтаксис символьной формы можно узнать с помощью функции ppcre:parse-string, например для тэга :entry в описании strong-элемента (см. предыдущую часть) получается следующее:
CL-USER> (ppcre:parse-string "\\*\\*(?=.*\\*\\*)")
(:SEQUENCE "**"
(:POSITIVE-LOOKAHEAD (:SEQUENCE (:GREEDY-REPETITION 0 NIL :EVERYTHING) "**")))
Эта возможность используется в ядре wiki-parser для построения "большого" регулярного выражения (оперировать s-выражениями проще, чем строками), также я порой использую эту возможность, когда затрудняюсь описать нужное мне в строковом виде (обычно, строковое представление удобней, но s-форма допускает большую гибкость). А ещё - s-представление даёт возможность указать произвольную функцию, которая будет работать как часть регулярного выражения. Т.е. когда нельзя написать традиционное регулярное выражение для описания синтаксиса, всегда можно указать произвольную функцию, которая будет производить необходимые проверки!. Для обработки секций я написал следующий код:
(defparameter +section-adornment+
'(#\! #\" #\# #\$ #\% #\&
#\' #\( #\) #\* #\+ #\,
#\- #\. #\/ #\: #\; #\<
#\= #\> #\? #\@ #\[ #\\
#\] #\^ #\_ #\` #\{ #\|
#\} #\~))

(defun regex-section-filter (pos)
(ignore-errors
(flet ((check-condition (flag)
(unless flag (return-from regex-section-filter nil))))
(let ((adornment nil)
(title nil)
(overline-p nil))
(check-condition (char= #\Newline
(char ppcre::*string* pos)))
(incf pos)
(check-condition (null (char= #\Newline
(char ppcre::*string* pos))))
(when (member (char ppcre::*string* pos)
+section-adornment+)
(let ((pos2 (position (char ppcre::*string* pos)
ppcre::*string*
:start (1+ pos)
:test-not #'char=)))
(check-condition (char= #\Newline
(char ppcre::*string* pos2)))
(setf adornment (subseq ppcre::*string* pos pos2)
overline-p t
pos (+ pos2 1))))
(unless overline-p
(check-condition (let ((sp (ppcre:scan "\\s" title)))
(or (null sp)
(> sp 0)))))
(let ((pos2 (position #\Newline
ppcre::*string*
:start pos
:test #'char=)))
(setf title
(subseq ppcre::*string* pos pos2))
(setf pos
(+ pos2 1)))
(let ((pos2 (position #\Newline
ppcre::*string*
:start pos
:test #'char=)))
(cond
(adornment (check-condition (string= adornment
(subseq ppcre::*string* pos pos2))))
(t (check-condition (> pos2 pos))
(setf adornment
(subseq ppcre::*string* pos pos2))
(check-condition (member (char adornment 0)
+section-adornment+))
(check-condition (null (find (char adornment 0)
adornment
:test-not #'char=)))))
(setf pos pos2))
(check-condition (if overline-p
(<= (length title)
(length adornment))
(= (length title)
(length adornment))))
pos))))

(define-mode section (30 :sections)
(:special (:filter regex-section-filter))
(:post-handler (item)
(let* ((lines (cdr (ppcre:split "\\n" (second item))))
(decoration (car (last lines)))
(overline-p (= (length lines) 3))
(title (if overline-p
(second lines)
(first lines))))
(list 'section
(trim-whitespaces title)
(char decoration 0)
overline-p))))
Функция regex-section-filter несколько нудновата, но главное это работает:
CL-USER> (wiki-parser:parse :re-structured-text
"**strong**
=============
Hello world
=============
*em*")
(WIKI-PARSER.RE-STRUCTURED-TEXT:TOPLEVEL
(WIKI-PARSER.RE-STRUCTURED-TEXT:STRONG "strong"))
(WIKI-PARSER.RE-STRUCTURED-TEXT:SECTION "Hello world" #\= T)
(WIKI-PARSER.RE-STRUCTURED-TEXT:EMPHASIS "em")))
Здесь распарсенный элемент WIKI-PARSER.RE-STRUCTURED-TEXT:SECTION имеет название, символ декорации и признак того, что имеется верхняя декорация.

Таким образом, при сохранении предельной простоты для описания тривиальных элементов имеется возможность и для обработки весьма сложной разметки (а код для обработки подобных элементов будет достаточно сложен при любом подходе). Эта схема кажется мне наиболее простой, как в понимании, так и в кодировании и тех, что я видел для обработки подобных разметок.