Есть проблема: На рабочих серверах (у нас используется политика тонких клиентов, толстых серверов) не установлено никакой lisp машины, и я, разумеется, не администрирую их.
На ум сразу приходят 2 решения:
- Уговорить администратора.
- Справиться самостоятельно.
Первый вариант подходит для настоящих внедренцев. Я к сожалению не владею достаточными аргументами, почему вдруг все терминальные сервера должны обзавестись ещё и лисп машиной.
Поэтому здесь пойдёт речь о втором варианте. (А именно о ECL на linux в custom каталоге).
Идя по этому пути, я в первую очередь вспомнил 2 реализации, с которыми работаю дома:
- ANSI Common Lisp (clisp)
- Steel Bank Common Lisp (sbcl)
Ни одну из них мне не удалось заставить работать из нестандартной директории, если кто может подсказать, милости прошу. Тут я вспомнил про ECL, уже однажды выручившим меня, когда мне понадобилось встроить lisp движок в сишный проект.
Рабочее окружение
В нашей конторе есть множество идентчиных терминальных серверов, а также несколько сетевых шар. Домашние директории ограничены объёмом 100 МиБ поэтому устанавливать сюда что-либо мне очень не хочется. Но есть человеческое сетевое файловое хранилище, на которое в моей домашней директории указывает символьная ссылка «ns»:
$ ls ~ -l
. . .
lrwxrwxrwx 1 necto users 14 Jul 22 12:22 ns -> /network/file/system/users/necto
. . .
Установка
Как забрать дистрибутив написано на странице проекта. Самый простой путь (imho):
$ cd tmp && git clone git://ecls.git.sourceforge.net/gitroot/ecls/ecl && cd ecl
Из ./configure --help можно почерпнуть, что для того, чтобы инсталлировать ecl в нестандартную директорию, достаточно указать пару префиксов, от которых он будет отталкиваться. Не долго думая устанавливаю нужные префиксы:
$ ./configure --prefix "~/ns/ecl/" --exec-prefix "~/ns/ecl/"
Но не тут-то было:
Switching to directory `build' to continue configuration.
configure: error: expected an absolute directory name for --exec_prefix: ~/ns/ecl/
Что ж, хорошо ещё, что симлинк не заставил разворачивать.
./configure --prefix "/home/necto/ns/ecl/" --exec-prefix "/home/necto/ns/ecl/"
Теперь канонично:
$ mkdir ~/ns/ecl && make && make install
У меня всё прошло без проблем, и в результате:
$ ~/ns/ecl/bin/ecl
ECL (Embeddable Common-Lisp) 11.1.1
Copyright (C) 1984 Taiichi Yuasa and Masami Hagiya
Copyright (C) 1993 Giuseppe Attardi
Copyright (C) 2000 Juan J. Garcia-Ripoll
ECL is free software, and you are welcome to redistribute it
under certain conditions; see file 'Copyright' for details.
Type :h for Help.
Top level.
> (quit)
Если после бесконфликтного make всё же:
/home/necto/ns/ecl/bin/ecl: error while loading shared libraries: libecl.so.11.1: cannot open shared object file: No such file or directory
Можно будет добавить /home/necto/ns/ecl/lib в LD_LIBRARY_PATH ($ export LD_LIBRARY_PATH=LD_LIBRARY_PATH:/home/necto/ns/ecl/lib ).«Встройка»
В первую очередь я устанавливал ecl для того, чтобы писать скрипты (например для сбора статистика по дампам). А значит вот такой запуск интерпретатора отнюдь не удобен:
$~/ns/ecl/bin/ecl -load tst.lisp
Поэтому необходимо пропатчить ./.bashrc. В той же директории ~/ns/ecl добавляю файлик to-bashrc:
#/bin/bash
# should be included into your .bashrc
# should be in .../ecl folder
# defines two commands:
# ecl - to run lisp interpreter in interactive mode [usage: ecl --help]
# cl $file - to execute lisp source with +cmd-args+ bind to
# list of commandline arguments
path_to_ecl=$(dirname ${BASH_SOURCE[0]})
# Use the next line, if ecl can't find libecl.so
export LD_LIBRARY_PATH="$path_to_ecl"/lib:$LD_LIBRARY_PATH
alias ecl="$path_to_ecl"'/bin/ecl'
function ecl-run
{
line="(progn (defconstant +cmd-args+ '(${@})) (defconstant +/cl+ \"$path_to_ecl\/\") (load \"$path_to_ecl/my/common.lisp\" :verbose nil) (load \"$1\") (quit))"
ecl -eval "$line"
}
alias cl='ecl-run'
Здесь, помимо тривиально алиаса добавлен вариант интерпретации файлов, с необходимыми функциями определёнными в ~/ns/ecl/my/common.lisp в котором помимо прочих удобных плюшек есть такие функции:
;;; common.lisp - aggregator of different tools
;;; being used in scripts
;; add standart lybrary path e. g. /home/necto/ns/ecl
:: use it on such way (load (lib/ "my/common.lisp"))
(defun lib/ (str)
(if (find-symbol "+/CL+")
(concatenate 'string +/cl+ str)
str))
;; Another function useful in everyday scripting:
;; determine is a symb given as argument
(if (find-symbol "+CMD-ARGS+")
(defun symbol-mentioned-in-params (symb)
(find symb +cmd-args+)))
;; Use "verbose" or "talkative" to see debug info from
;; your script
(defun verbose-enabled-p ()
(and (find-symbol "+CMD-ARGS+")
(or (find 'talkative +cmd-args+)
(find 'verbose +cmd-args+))))
;; A output stream which you can enable by taking option "talkative"
;; to a script using it
(defconstant extra-out (if (verbose-enabled-p)
t
(make-string-output-stream)))
;; Also if user doesn't want to view all it's loads, disable it:
(setf *load-verbose* (verbose-enabled-p))
;;...
Полный код
Кстати очень рекомендую pregexp — весьма лёгкая и библиотечка для работы с regexp'ами. Очень маленькая библиотечка всего 1 файл в ~800 строк. Она используется в ecl/my/common.lisp ну очень широко.
Также можно видеть, что будет определена константа +cmd-args+ содержащая список всех аргументов, переданных скрипту.
Теперь для интеграции всего этого хозяйства достаточно дописать в .bashrc:
source ~/ns/ecl/to-bashrc
Использование
Теперь достаточно создать, например, all-funs.lisp:
(defun print-funcs ()
(process-every-file file (make-pathname :directory '(:relative) :name "*" :type "lisp") :input
(for-every-appropriate-line file "defun ([^ ]+) \(([^)]*)\)" (funame args)
(print-hello funame)
(print args extra-out))))
(defun print-hello (name)
(format t "~%Hello, ~a." name))
(print-funcs)
И запустить его:
$ cl all-funs.lisp
Hello, print-funcs.
Hello, print-hello.
А если указать флаг talkative:
$ cl all-funs.lisp talkative
Hello, print-funcs.
Hello, print-hello.
name
Но самое интересное, что если теперь коллеге захочется попользоваться вашими наработками, ему достаточно будет включить /net/file/system/users/necto/ecl/to-bashrc в свой .bashrc и всё!
Заключение
Собственно этим я хотел сказать, что встраивание common lisp в качестве удобного скриптового интерпретатора, на ряду с тем же python или perl довольно просто, и отнюдь не требует административных привилегий.
Один минус мне пока не удалось устранить: Как известно лисп славится своим repl в рантайме, а ECL имеет чрезвычайно неудобную консоль, и если вы случайно ошиблись, она вываливается в качестве обработчика исключения (конкретно выводит из себя невозможность стереть ошибочный символ). Я здесь разбираюсь с трудом, поэтому интересует мнение lisp общественности: как можно заменить её на что-нибудь более редактируемое?
Необходимые компоненты:
Embeddable Common Lisp
bash
Pregexp(кстати несколько медленновата, и порой ей не хватает стека (на длинных строках) кто что посоветует такого же переносимого?)
P. s.: Эх, не нашёл логотипа ecl, а сам рисовать не умею, поэтому поставил обобщённый лейбл.
P. s.2: Интересно, ещё как кто форматирует сорцы на lisp? Ибо тег source lang=«lisp» только добавляет серый фон для длинных исходников, не более (или это ограничение предпросмотра?).