24 октября 2011

Common Lisp. Файловый http сервер.

В cl можно предоставить http доступ к папке за две формы, за что архимагу и спасибо.

(ql:quickload :restas-directory-publisher)

(restas:start :restas.directory-publisher
              :port 8080
              :context (restas:make-context (restas.directory-publisher:*baseurl* '("tmp"))
                                            (restas.directory-publisher:*directory* #P"/tmp/")
                                            (restas.directory-publisher:*autoindex* t)))

23 октября 2011

Common Lisp. Останов restas

Updated 20.11.2011

Теперь можно так:


(restas:stop-all :soft t)


Чтобы не забыть, останов restas выполняется командой:

(mapcar #'hunchentoot:stop restas::*acceptors*)
(setf restas::*acceptors* nil)

19 октября 2011

Удивительное рядом: потокобезопасность в cl

Обычная задача: выполнить функцию в отдельном потоке, в моем случае это inc-counter.

Для этого можно выполнить форму:

* (defparameter *counter* 1)
* (defun inc-counter ()
    (incf *counter*))
* (bordeaux-threads:make-thread (lambda () (inc-counter)))
* *counter*
2

Усложненная задача: потоков может быть несколько. Определенно функция может изменять какие-то глобальные переменные (в терминах лиспа: динамические переменные созданные функциями defvar, defparameter). Если бы передо мной была библиотека на c/c++, можно было бы опускать руки, так как с/с++ не позволяет изменять окружение выполнения функции. Думаю, что и java так же этого не позволяет. А javascript, кстати, позволяет окружение менять. А вот в common lispе для того, чтобы сделать из любой функции с побочными эфектами, функцию без них, достаточно использовать форму let. Давайте выполним теперь такой поток:

* (bordeaux-threads:make-thread (lambda () (let ((*counter* 0)) (inc-counter))))
* *counter*
2

Вот ведь, к хорошему быстро привыкаешь. Я уже два дня этим финтом пользуюсь, а только сегодня его осознал.

16 октября 2011

Common Lisp. Restas. Maxima. #3

Сразу же иллюстрации:


И на телефоне:


Вышло обновление проекта restmax, в рамках которого я пытаюсь создать web оболочку для программы maxima.

Репозитарий потолстел за счет встроенных зависимостей, в частности, за счет mathjax.

Проект уже сейчас можно протестировать по адресу http://asvil.dyndns.info:8081/index.html. Внимание доступность сервера зависит от того, включил ли я его:), поэтому он работает не всегда.

Пример простого TeX документа: https://github.com/filonenko-mikhail/restmax/raw/master/example/new.tex

Пожелания/ошибки и вообще критику можно писать в комментариях, а также по адресу https://github.com/filonenko-mikhail/restmax/issues.

Changelog

Встроенная maxima. Теперь maxima запускается внутри restmax на каждую сессию отдельным потоком. Поток живет 6 секунд после того, как вы закрыли страничку с repl-ом.
Отдельный поток maxima на LaTeX. Теперь для преобразования TeX документа запускается отдельный от repl-а поток maxima.
Скругленные углы у кнопок убраны.
Добавлено отображение графиков. Для этого предназначены функции семейства wx* (wxplot2d, wxplot3d, и т.д.), позаимствованные и модифицированные из wxMaxima. Отображение графиков также возможно и в документах TeX.

Известные ошибки

Сложные графики отображаются только после следующей команды. Надо поставить sleep.
maxima содержит глобальные переменные. Пока только две из них изолируются в потоке let-ом.
embedded maxima in TeX не содержит экранирования для символа }, надо поправить.
Команда quit(), приводит к зависанию hunchetoot client потока. Надо переопределить quit, добавив вывод специального маркера.

В будущем:

Сделать историю в repl.
Наладить справочную систему для maxima, TeX в виде wiki.
Возможно поменять название проекта, нынешнее излишне созвучно, да и вообще хочеться использовать красивое женское имя.
Решить проблему изоляции/безопасности сессий одним махом.
Кроссбраузерность, включающая гаджеты.

14 октября 2011

Common Lisp tips.

Напоминаю, что Зак Бин открыл ресурс для хранения советов для common lisp.

15.10.11 Updated

Вот переводы некоторых.

Поменять местами

Простой путь поменять местами значения двух символов, a и b, выглядит так:

;; BOGUS
(setf temp a)
(setf a b)
(setf b temp)

psetf (параллельный setf) может сделать это за одну форму:

(psetf a b b a)

Но самая лучшая функция для этого rotatef:

(rotatef a b)

Перенаправить вывод

Есть функция, которая что-то выводит, но вы хотите, чтобы она выводила в другое место? Вы можете связать специальный символ *standard-output* в любых макросах, которые создают временные потоки.

Например, для перенаправления вывода в строку:

* (with-output-to-string (*standard-output*) 
    (print-marketing-report))
"Source,Hits
twitter,243
google,805
direct,47
"

Для перенаправления в файл:

* (with-open-file (*standard-output* #p"file.txt" :direction :output)
    (print-marketing-report))
NIL

Чтение чисел с плавающей точкой

Когда reader встречает число наподобие "3.0" без маркера экспоненты, он по умолчанию конвертирует его в single-float. Вы можете изменить тип используемый в конвертации связав символ *read-default-float-format* с другим типом числа с плавающей точкой.

Например:

* (/ 22.0 7.0)
3.142857

* (setf *read-default-float-format* 'double-float)
DOUBLE-FLOAT

* (/ 22.0 7.0)
3.142857142857143

При выводе также опускается маркер экспоненты, если тип выводимого числа совпадает с типом из *read-default-float-format*.

"Прикосновение" к файлу

* Прикосновение к файлу - действие, которое меняет дату модификации файла, если он существовал, иначе сздают файл.

Для создания пустого файла, как, например делает Unix команда touch, , вы можете использовать следующий код:

;; BOGUS
(close (open "foo.txt" :direction :output 
             :if-does-not-exist :create 
             :if-exists :append))

open принимает для аргумент :direction специальное значения для "прикосновения":

(open "foo.txt" :direction :probe :if-does-not-exist :create)

Если "foo.txt" не существует, он будет создан. Поток возвращается уже закрытым. Документация говорит следующее:

"[:probe] создание "no-directional" файлового потока; файловый поток создается и закрывается перед тем, как возвращается в качестве результата."

Многострочная строка форматирования

Вы можете разбить длинную стоку форматирования с помощью тильды ~ в концетильды ~ в конце каждой подстроки каждой подстроки. Например:

* (format t "It was the best of times, ~
             it was the worst of times.")
It was the best of times, it was the worst of times.

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

"двоеточие" и "собака" модификаторы имеют дополнительный смысл:

При двоеточии, перевод строки игнорируется, но все пробелы на следующей подстроке не удаляются. При собаке перевод строки сохраняется, а все пробелы в начале подстроки игнорируются.

Преобразование символов в числа

Если у вас есть символ #\7 и вы хотите получить число 7, вы можете использовать форму (parse-integer (string char)) или ASCII-ориентированный алгоритм.

(- (char-int char) (char-int #\0))

В то время, как первый вариант будет давать правильный ответ всегда, второй вариант зависит от реализации. Спецификация описывает порядок символов в таблице, однако не дает никаких гарантий относительно возвращаемых значений функций char-int и char-code.

Все равно хотите их использовать? digit-char-p не только возвращает "истину", если первый аргумент является цифрой, но возвращает данную цифру для переданного символа.

* (digit-char-p #\7)
7

Это также работает с другими системами счислений:

* (digit-char-p #\a 16)
10

Если символ не является цифрой, digit-char-p возвращает nil.

:start и :end параметры для parse-integer

parse-integer принимает :start и :end аргументы, теперь вам не нужно вытаскивать подстроки из строки для передачи в функцию parse-integer. Например, для разбора такой строки, как "2011-10-01" в числа год, месяц и день, вы можете сделать так:

(defun parse-date (string)
  "Parse a date string in the form YYYY-MM-DD and return the
   year, month, and day as multiple values."
  (values (parse-integer string :start 0 :end 4)
          (parse-integer string :start 5 :end 7)
          (parse-integer string :start 8 :end 10)))

Сравнение нескольких объектов

Функции сравнения чисел =, /=, <, <=, >, >= могут принимает больше чем два аргумента. Теперь проверить, например, что числа составляют возврастающую последовательность, просто:

(< a b c d)
Для небольших списков чисел, используйте следующий алгоритм:
(apply #'< list)
Функции сравнения не чисел в основном принимают только два аргумента для сравнения, например, (string= x y z) неправильная форма, и для списка из трех и более объектов вы не можете применить данную функцию. Однако, вы можете сравнить попарно все элементы списка с помощью every. Например, для проверки все ли строки в списке эквивалентны с помощью string=.
(every #'string= list (rest list))
Следует отметить, что /= особенна; следующие вызовы не эквивалентны:
* (apply #'/= list)
T
* (every #'/= list (rest list))
T
Почему? Управление ходом исполнения У макроса "do" (do, do*, dolist, dotimes) тело, которое ведет себя как tagbody. Вы можете поместить got tags где угодно в теле и использовать go для перехода в отмеченные места. Это может быть полезно для пропусков, повторов или других изменений выполнения итерации. Например:
(dolist (users (get-user-list)) 
  :retry
  ...
  (when some-condition
    (go :retry)
  ...)

Common Lisp. Esrap.

Передо мной встала задача анализировать результат выполнения команд maxima (с включенным режимом imaxima). Сначала я выполнял это с помощью cl-ppcre, но регулярные выражения, будучи, несомненно, удобными, сложно расширяются.

Итак вот задача:

Пользователь имеет возможность вводить подряд несколько команд, не заглушая или заглушая вывод некоторых из них.

Пример:

1+1;
2+5/0;
wxplot2d(sin(x),[x,0,2]);
sin(x);
12345$
"some string";

Здесь присутствуют выводы числа, исключения, графика, символа, числа *заглушено*, строки.

Вот как будет выглядеть вывод программы maxima с загруженным пакетом imaxima.lisp.

^B^W\%o2^W2^E
^C(%i3) ^D
expt: undefined: 0 to a negative exponent.
 -- an error. To debug this try: debugmode(true);
^C(%i4) ^D
^B^W\%t4^W^Gmaxout_1.png^G^E
^B^W\%o4^W^E
^C(%i5) ^D
^B^W\%o5^W\sin x^E
^C(%i6) ^D
^C(%i7) ^D
^B^W\%o7^W\verb|some string|^E
^C(%i8) ^D

Буквы с предшевствующим символом '^' являются управляющими символами.
Эти управляющие символы являются маркерами того, какой смысл имеет строка между ними.

Простая строка вывода результата выглядит как:

^B^W\%o2^W2^E

Маркеры ^B и ^E обозначают, что строка является результатом команды.
Маркеры ^W отделяют подпись текущего вывода и по совместительству название символы связанного с данным выводом.

^C(%i3) ^D

Маркеры ^C и ^D обозначают подпись приглашения к вводу команды. Данная подпись также является символом, который будет связан с введенной командой.

^B^W\%t4^W^Gmaxout_1.png^G^E

Маркеры ^B и ^E нам уже знакомы, только подпись имеет формат %t. Переменная %t будет связана с именем файла.

expt: undefined: 0 to a negative exponent.
 -- an error. To debug this try: debugmode(true);

Исключение представлено просто текстом, без каких-либо маркеров.

Вывод разделяется переводом строк.

Сначала я наладил было парсинг на PEG.js на клиентской стороне, но вовремя опомнился и портировал правила на esrap. Esrap небольшая библиотека для построения парсеров. Ее использовал archimag в своем проекте cl-closure-templates, и отзывался о ней довольно положительно.

Начнем. Основная функция, которая будет нами использоваться defrule. Данная функция принимает первым аргументом правило, по которому производить разбор текста, и в другом параметре она принимает функцию, которая будет структурировать разобранные выражения. Правила напоминают простые регулярные выражения. Построение правил - нетрудное дело, если правильно думать.

Правила

Вот первая мысль для первого правила.

"У нас есть неограниченный список выражений".

Так и запишем.

(defrule expressions (* expression)
  (:lambda (list)
    list))

Правило (* expression) означает в expressions expression может встречаться 0 и более раз.

Форма :lambda задает функцию, которая будет иметь аргумент - список разобранных expression. Мы просто вернем этот список.

Вторая мысль:

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

Вот соответственно правило:

(defrule expression (and (? whitespace) (or out in simpletext))
  (:destructure (w exp)
    (declare (ignore w))
    exp))

(and subexpression1 subexpression1 ... subexpressionN) означает совпадение последовательно расположенных выражений.

(? whitespace) означает, что выражение может встречаться 0 или 1 раз. При 0 будет возвращен nil.

(or subexpression1 subexpression1 ... subexpressionN) означает, что на данной позиции может встречаться одно из N выражений.

Здесь мы используем форму :destructure для того, чтобы "перенаправить" результат разбора в аргументы функции. Аргумент w будет результатом (? whitespace), и exp - (or out in simpletext). Игнорируем пробелы и возвращаем результат разбора выражения.

Следующая мысль:

"Пустое место - это 1 или несколько пробельных символов (#\space #\tab #\newline)"

(defrule whitespace (+ (or #\space #\tab #\newline))
  (:constant nil))

(+ subexpression) означает, что выражение встречает 1 и более раз.

Здесь мы не задаем функцию обработки, указывая какой результат для данного разбора всегда должен быть: (:constant nil).

Мысль:

"Строка вывода это маркеры ^B и ^E, а между ними выражение вывода, которое может содержать подпись, например, \%o2, строку имени файла, maxout_1.png, и текст."

Обозначим правила для маркеров:

(defrule startout #\Stx)

(defrule endout #\Enq)

Обозначим правило для строки вывода:

(defrule out (and startout (? outlbl)
              (? outimg)
              (* outtext) endout)
  (:destructure (m1 outlbl outimg expr m2)
    (declare (ignore m1 m2))
    (list (cons :lbl outlbl) (cons :img outimg) (cons :expr (text expr)) (cons :tex t))))

Данным выражением

(list (cons :lbl outlbl) (cons :img outimg) (cons :expr (text expr)) (cons :tex t))))

мы создали alist содержащий структуру разбора вывода. Здесь функция text соединяет переданный ей список строк, в данном случае expr будет содержать список символов и они будут объединены в одну строку.

Далее определяем правила для outlbl, outimg, и outtext.

;; Возращаем символ, если он не является маркером конца вывода
(defrule outtext (and (! endout) character)
  (:destructure (m1 ch)
    (declare (ignore m1))
    ch))

;; Определяем маркер для подписи
(defrule outlblbrace #\Etb
  )

;; Возвращаем подпись, игнорируя маркеры
(defrule outlbl (and outlblbrace (* outlbltext) outlblbrace)
  (:destructure (m1 expr m2)
    (declare (ignore m1 m2))
    (text expr)))

;; Возвращаем символ, если не является маркером конца подписи
(defrule outlbltext (and (! outlblbrace) character)
  (:destructure (m1 ch)
    (declare (ignore m1))
    ch))

;; Определяем маркер для имени файла
(defrule outimgbrace #\Bel
  )

;; Возвращаем имя файла, игнорируя маркеры
(defrule outimg (and outimgbrace (* outimgtext) outimgbrace)
  (:destructure (m1 expr m2)
    (declare (ignore m1 m2))
    (text expr)))

;; Возвращаем символ, если он не является маркером конца имени файла
(defrule outimgtext (and (! outimgbrace) character)
  (:destructure (m1 ch)
    (declare (ignore m1))
    ch))

Теперь по аналогии определим правила для строки приглашения ввода.

;; маркер начала
(defrule startin #\Etx
  )

;; маркер конца
(defrule endin #\Eot
  )

;; Записываем все что между маркерами
(defrule in (and startin (* intext) endin)
  (:destructure (m1 expr m2)
    (declare (ignore m1 m2))
    (list (cons :expr (text expr)))))

;; Возвращаем символ, если он не является маркером конца
(defrule intext (and (! endin) character)
  (:destructure (m1 ch)
    (declare (ignore m1))
    ch))

Если никакие выше правила не сработали значит перед нами просто текст вывода, это может быть текст исключения, или сессия работы maxima в lisp repl режиме.

;; Записываем текст
(defrule simpletext (+ simpletextcontent)
  (:lambda (list)
    (list (cons :expr (text list)))))

;; Возвращаем символ, если он не является каким-нибудь маркером начала
(defrule simpletextcontent (and (! (or startout startin)) character)
  (:destructure (m1 ch)
    (declare (ignore m1))
    ch))

Парсинг


Создадим функцию, которая осуществит разбор текста и возврат список alist'ов с выделенными частями текста.

(defun parse-expression (text)
  "Parsing imaxima output"
  (parse 'expressions text))

Пример выполнения:

CL-USER> (imaxima-esrap:parse-expression "^B^W\%o7^W9^E
^C(%i8) ^D
^B^W\%o8^W9^E
^C(%i9) ^D 
expt: undefined: 0 to a negative exponent.
    -- an error. To debug this try: debugmode(true);
^C(%i10) ^D 
^B\verb|asdfasdf|\verb| |^E 
^B^W\%o10^W\verb|asdfasdf|^E
 ^C(%i11) ^D                 
expt: undefined: 0 to a negative exponent.
     -- an error. To debug this try: debugmode(true);
^C(%i12) ^D 
 ^B^W\%t12^W/home/michael/maxout_1.png^E
 ^B^W\%o12^W^E")
(((:LBL . "%o7") (:IMG) (:EXPR . "9") (:TEX . T)) ((:EXPR . "(%i8) "))
 ((:LBL . "%o8") (:IMG) (:EXPR . "9") (:TEX . T)) ((:EXPR . "(%i9) "))
 ((:EXPR . "expt: undefined: 0 to a negative exponent.
    -- an error. To debug this try: debugmode(true);
"))
 ((:EXPR . "(%i10) "))
 ((:LBL) (:IMG) (:EXPR . "verb|asdfasdf|verb| |") (:TEX . T))
 ((:LBL . "%o10") (:IMG) (:EXPR . "verb|asdfasdf|") (:TEX . T))
 ((:EXPR . "(%i11) "))
 ((:EXPR . "expt: undefined: 0 to a negative exponent.
     -- an error. To debug this try: debugmode(true);
"))
 ((:EXPR . "(%i12) "))
 ((:LBL . "%t12") (:IMG) (:EXPR . "/home/michael/maxout_1.png") (:TEX . T))
 ((:LBL . "%o12") (:IMG) (:EXPR . "") (:TEX . T)))
NIL
CL-USER> 

alist я выбрал не случайно, после того как я разобрал вывод, я преобразовываю alist в json с помощью функции json:encode-json-to-string и отправляю клиентскому javascript'у.

А, вообще, так как в лиспе код является данными и наоборот, при разборе выражений можно возращать некоторый код, который затем выполнять, тем самым выполучаете интерпретатор (а на sbcl компилятороинтерпретатор) очень малой ценой. archimag в cl-closure-templates так вроде и делает, пойдя еще дальше и генерируя с помощью parenscript, на основе сгенерированного кода, код javascript . Для сравнения: разработчики Qt до сих пор не прикрутили свои классы к QtScript, то что предлагается qtscriptbindingsgenerator - это через гланды.