среда, 22 сентября 2010 г.

Увеличить размер history

Чтобы увеличить размер history в Ubuntu, достаточно добавить две строчки в файл ~/.bashrc:

# увеличение размера истории
export HISTSIZE=2000
export HISTFILESIZE=2000

вторник, 21 сентября 2010 г.

Печать из Linux в Windows

Задача: печатать из Linux на принтере Canon, к которому под Linux нет драйверов. Принтер подключён к компьютеру, который работает под Windows.

Решение.

1. На компьютере с Windows ставим четвёртый акробат. Файл называется acrobat405.exe, где я его нашёл, уже не помню.

2. Пишем нехитрый CGI-скрипт на newLISP, который будет по команде снаружи брать файл из указанной папки и печатать его. Скрипт я назвал printit.cgi

--- Начало printit.cgi ---
#!c:\pdfpipe\NewLisp.exe

(module "cgi.lsp")



(set 'pool-path "C:\\pdfpipe\\pdfpool\\") ;

(set 'acrobat-path "\"c:\\Program Files\\Adobe\\Acrobat 4.0\\Reader\\AcroRd32.exe\"")

(set 'printer-name "OneNote")



(set 'file-to-print (CGI:get "file"))



(exec (append acrobat-path " /t /n " pool-path file-to-print " " printer-name))

(exec (append "del " pool-path file-to-print))
--- Конец printit.cgi ---


Как можно заметить, в скрипте указан путь к Акробату и путь к расшаренной папке, куда будут скидываться файлы из Linux. Наверное, излишне говорить, что для работы скрипта нужен установленный в папку c:\pdfpipe\NewLisp.exe.

3. Делаем батничек start.bat с единственной строчкой внутри:

start /b newlisp.exe newlisp -c -d 8003

4. Делаем батничек startup.vbs, который будет запускать start.bat тихо, не нервируя пользователя. Внутри батничка уже две строчки:

Set oShell = WScript.CreateObject("WScript.Shell")

oShell.Run "server.bat", 0, False

5. Создаём папку для документов и расшариваем её.

6. Дальше работаем на компьютере с Linux. Подразумевается, что cups-pdf и lynx у нас установлены.

7. Открываем файл /etc/cups/cups-pdf.conf. Сообщаем, что печатать теперь надо в другую папку: вместо «Out ${HOME}/PDF» пишем «Out /var/spool/pdfprint». Папку /var/spool/pdfprint надо будет создать и проставить на неё разрешения.

8. Ближе к концу cups-pdf.conf вставляем вместо закомментированного PostProcessing строчку «PostProcessing /var/spool/pdfprint/script.lsp»

9. А вот и сам файлик script.lsp:

--- Начало script.lsp ---
#!/usr/bin/newlisp

(set 'pdf-path "/var/spool/pdfprint/")

(dolist (x (directory pdf-path "\\.pdf"))
(exec (append "mv " pdf-path x " " pdf-path "pool"))
(exec (append "lynx 192.168.25.101:8003/printit.cgi?file=" x " -dump")))

(exit)
--- Конец script.lsp ---

Где править айпишник видно. Это айпишник компьютера с Windows к которому подключён принтер.

10. Тонкий момент. С помощью apparmor надо разрешить cups-pdf печатать в выбранную нами папку. Как именно это сделать я уже забыл, но в Интернетах про это много.

11. Наконец, надо подсоединить сетевую папку. В /etc/fstab пишем что-то типа:
//192.168.25.101/canon /var/spool/pdfprint/pool cifs rw,user=print,pass=print,iocharset=utf8,file_mode=0777,file_mode=0777,dir_mode=0777

Зачем два раза писать file_mode не спрашивайте. У меня стоит так и работает. Наверное, будет работать и с одним file_mode, но мне было лень проверять.

12. Вот, в общем, и всё. На всякий случай, как всё работает.

а) Печатаем в PDF
б) файл blablabla.pdf кладётся в /var/spool/pdfprint
в) исполняется скрипт /var/spool/pdfprint/script.lsp
г) Скрипт кладёт файл blablabla.pdf в сетевую папку /var/spool/pdfprint/pool
д) Скрипт через lynx даёт команду cgi-скрипту на компьютере windows печатать файл blablabla.pdf
е) Скрипт printit.cgi запускает акробат, чтобы тот напечатал blablabla.pdf и стирает файл.

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

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

CentOS, crypto.lsp

Модуль crypto.lsp не заработал сразу. Так как для него нужна библиотека libcrypto.so, которая в моём CentOS по умолчанию установлена не была. Проблема решилась в две команды.

Команда первая — выдать список пакетов с этой библиотекой:

yum provides "*/libcrypto.so*"


Команда вторая — установить один из пакетов, содержащих нужную библиотеку:

sudo yum install openssl-devel-0.9.8e-12.el5_4.6.i386


Вуаля.

среда, 21 июля 2010 г.

Скрипты из Windows не запускаются под Linux

Скрипты из Windows не запускались, выдавая ошибку (2)No such file or directory: exec of '.../index.cgi' failed.

Двухчасовое расследование показало, что проблема в DOS-формате скриптов: конец строки обозначался двумя символами, а не одним, поэтому Апач не мог правильно обработать первую строчку.

Установленный vim, однако, не показывал привычного ^M, поэтому я понял это только когда начал сравнивать размеры файлов.

Команда "dos2unix index.cgi" решила проблему.

Установка newLISP на CentOS

Как установить newLISP на CentOS автоматически я не нашёл. Зато замечательно сработала установка через компиляцию исходников:

wget http://www.newlisp.org/downloads/newlisp-10.2.8.tgz
tar -xf newlisp-10.2.8.tgz
cd newlisp-10.2.8
sudo ./configure-alt
sudo make
sudo make install

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

Ubuntu: переименовать файлы по маске

Мне потребовалось переименовать файлы по маске: заменить файлы вида 1003.123 на 1003.10123. Получилось вот такой командой:

find . -maxdepth 1 -iname "1003.*" -execdir rename "s/1003\./1003\.10/" \{\} \;


При этом maxdepth 1 означает "не спускаться ниже по дереву", а команда rename имеет шаблон "/s/from/to/"

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

newLISP: curry-all

Обнаружил, что после перехода на новую версию newLISP перестал работать мой curry-all (который должен делать то же самое, что и curry, только с несколькими аргументами). Вот переставший работать вариант:

(context 'curry-all)
(define-macro (curry-all:curry-all f)
(letex (f1 f lst (map string (args)))
(fn (z) (eval-string (append "(" (name 'f1) " " (join 'lst " ") " curry-all:z)")))))
(context 'MAIN)


А вот вариант работающий, позаимствованный у Лутца:

(define (curry-all f)
(append (lambda (z)) (list (cons f (append (args) '(z))))))


Кстати, Лутц предлагает ещё одно решение, вот здесь:
http://newlispfanclub.alh.net/forum/viewtopic.php?f=16&t=3169

newLISP 10.2.8 и Ubuntu 8.10

Установил новую версию newLISP: 10.2.8. Пишет, что ему нужна библиотека libreadline6, а у меня только libreadline5.

Обновил свою 8.10 до 9.04. Не помогло.

Заглянул на newlisp.org. Там Lutz пишет, что можно настроить автоматическую установку newLISP через sudo apt-get install newlisp, но... только в 9.10 и выше.

Мне кажется, это намёк. Сейчас обновлюсь до 9.10 и, очень надеюсь, всё заработает.

среда, 21 апреля 2010 г.

Lock: продолжение.

Придумал новый алгоритм для лоченья файлов. Где можно наколоться пока не вижу. Но опыт, конечно, покажет. Также у меня всегда остётся запасной вариант: man 1 flock, man 2 flock.

Допустим, нам надо изменить test.txt

1. Проверяем, нет ли test.txt.lock, изменённого менее 30 секунд назад.

2. Если есть, ждём 100 ms, идём на пункт 1.

3. Стираем лок-файл, изменённый старше 30 секунд:
find . -maxdepth 1 -mmin +0,5 -name test.txt.lock -delete
(Команду надо ещё проверить). Стирать хочется именно одной командой, чтобы во время её выполнения другой скрипт не вставил свой файл.

4. Делаем аппенд к лок-файлу:
(append-file "test.txt.lock" (append id "\n"))

5. Если мы не успели, идём на пункт 1.

6. Мы успели. Делаем необходимые манипуляции с test.txt.

7. Стираем lock-файл.

вторник, 20 апреля 2010 г.

newLISP: самодельная блокировка текстовых файлов

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

Решение.

1. Делаем в папке с заявками файл lock.

2. Убеждаемся, что генератор случайных чисел сеется. Я сею его на основе выбранного порта и системного времени. Подозреваю, в windows может не работать, если там time-of-day возвращается с точностью до секунды, а не до миллисекунды.

(seed (div (time-of-day) (int (env "REMOTE_PORT")) 0.000001))


3. Вот собственно процедура получения номера. Суть простая. Пишем через append в файл "lock" номер, который мы хотим занять и подписываем его случайным числом. Проверяем, что никто не успел вклиниться перед нами. Если мы опоздали (номер уже занят), повторяем попытку.

(define (occupy-number) (local (id num res)
(set 'id (rand-string))
(set 'num (last (or (sort (map int (directory "." "txt$"))) '(0))))
(do-until (= (or (find (s-a num ":" id) res 0) "no") (find (s-a num ":.*") res 0))
(inc num)
(append-file "lock" (s-a num ":" id "\n"))
(set 'res (read-tail "lock" 100)))
num))


Я тестировал на одновременных 100 подключениях. Работало с двойным запасом.

4. Вот воспомогательные процедуры:

(define (string-append) (apply append (map string (args))))
(set 's-a string-append)

; В Windows реальный rand (насколько я помню) имеет диапазон в 32768,
; поэтому я его для надёжности "утроил".
(define (rand-string)
(join (map string (list (rand 65536) (rand 65536) (rand 65536))) "_"))

; Возвращает последние 'lines строк файла в виде списка.
; Работает, по понятным причинам, только под *nix
(define (read-tail fname (lines 10))
(exec (append "tail " fname " -n " (string lines))))


5. Замечания.

5.1. Лог может сильно разрастись, надо как-то его стирать изредка.

5.2. Если мы стёрли файл, а в lock он остался, в нумерации будут пропуски. Это скорее фича, но тем не менее.

5.3. Процедура блокировки файлов для изменения не проработана. Если мы хотим обезопаситься ещё и от изменения, нужно думать дальше.

5.4. Схема "велосипедна". То есть, лучше бы, вообще, пользоваться базами данных.

JavaScript: закрывать псевдопопап по Escape

Как закрывать псевдопопапы по нажатию Esc.

1. Ставим обработчик в body:

<body onkeydown="if (event.keyCode == 27) closePopup();">


2. Создаём массив, в котором будет храниться список открытых окон:

// Список открытых окон
var popupList = new Array();


3. Запиливаем вот эти функции:

// Закрывает открытое окно
function closePopup() {
if (popupList.length == 0) return;
document.getElementById(popupList.pop()).style.display = 'none';
}

// Удаляет элемент из массива
function popAssoc(arr,itm) {
var arr1 = new Array();
for (i=0;i<arr.length;i++) {
if (arr[i] != itm) arr1.push(arr[i]);
}
return arr1;
}

// Показывает всплывающее окно по центру экрана рядом с курсором мыши
function showWin(id, event, firstField) {
// Если окно уже показано, прячем его
if (document.getElementById(id).style.display == 'block') {
popupList = popAssoc(popupList,id);
document.getElementById(id).style.display = 'none';
} else {
// Запоминаем окно в списке открытых
popupList.push(id);
// Вычисляем координаты
thisWin = document.getElementById(id);
thisWin.style.top = getYCoord(parseInt(thisWin.style.height), event);
thisWin.style.left = (document.body.clientWidth - parseInt(thisWin.style.width)) / 2;
// Показываем окно
thisWin.style.display = 'block';
// Фокусируемся на первом поле
if (firstField) setFocus(firstField);
}
}


Про вычисление координат для окна я уже писал раньше:
http://hilocomod.blogspot.com/2010/03/js.html

суббота, 17 апреля 2010 г.

SICP: малая теорема Ферма

Кусочек кода с распознаванием простых чисел по Малой Теореме Ферма.

; Разное встроенное
(define (even? x) (= (% x 2)))
(define (square x) (* x x))
(define (prime? p) (= (first (factor p)) p))

; Вычисляет (% (pow a b) p)
(define (expmod a b p)
(cond ((= b) 1)
((even? b) (% (square (expmod a (/ b 2) p)) p))
(true (% (* a (expmod a (dec b) p)) p))))

; Проверяет число на простоту 1 раз
(define (fermat-test p)
(let (n (+ (rand (- p 1)) 1))
(= (expmod n p p) n)))

; Проверяет число на простоту много раз
(define (fast-prime p (n 1))
(local (f)
(while (and (set 'f (fermat-test p)) (!= (dec n))))
f))

; Проверяет число на принадлежность к Кармайкловым Числам.
(define (carmaicl? x)
(and (not (prime? x))
(apply and (map (fn (n) (= (expmod n x x) n)) (sequence 1 (- x 1))))))


Функция carmaicl? работает довольно медленно. Чтобы прогнать её на числах от 2 до 5 000 потребовалось несколько минут. При этом на проверку 2х функция затрачивает примерно в 2 раза больше времени, чем на проверку х. То есть, если я правильно понимаю, на проверку диапазона 5k-10k уйдёт в три раза больше времени, чем на проверку диапазона 0k-5k.

Uptime в среде Windows

Как известно, uptime в среде Windows можно получить, набрав "net stats server".

Чтобы перевести выдаваемый текст в секунды аптайма, пришлось написать вот такую функцию:

; Показывает Uptime в среде Windows
(define (uptime)
(let (wt (regex {(\d+)/(\d+)/(\d{4}) (\d+):(\d+) ([AP]M)} (join (exec "net stats server"))))
(if (= (wt 18) "PM") (setf (wt 12) (+ (int (wt 12)) 12)))
(- (date-value)
(* (% (+ 24 (int ((regex {\d+(?=:)} (date)) 0)) (- ((now) 3))) 24) -3600)
(apply date-value (append (select (map int wt) 9 3 6 12 15) '(0))))))


Комментарии.

1. parse-date не работает в Win32.
2. (date-value параметры) возвращает время GMT. В моём случае разница составила 4 часа. Чтобы скорректировать пришлось отнять часы в (date) от часов в (now).
3. Время выполнения функции — примерно 125 миллисекунд.

пятница, 16 апреля 2010 г.

SICP 1.19: Фибоначчи по логарифму

Вот эта гнусная функция:

(define (even? x) (if (= (% x 2))))

(define (fib n (a 1) (b 0) (p 0) (q 1))
(cond ((= n) b)
((even? n) (fib (/ n 2)
a
b
(+ (* p p) (* q q))
(+ (* 2 p q) (* q q))))
(true (fib (dec n)
(+ (* b q) (* a q) (* a p))
(+ (* b p) (* a q))
p
q))))


Программирования, тут, на мой взгляд, гораздо меньше, чем математики. Думаю, это неправильно.

Чётные числа

Проверил, как быстрее считать чётность.

; Вариант 1, хитропопый
(define (even-1 x)
(if (find (last (string x)) '("0" "2" "4" "6" "8")) true))

; Вариант 2, "в лоб"
(define (even-2 x)
(if (= (% x 2))))


Как я и ожидал, вариант "в лоб" оказался примерно на порядок быстрее, да.

вторник, 13 апреля 2010 г.

SICP: итеративное возведение в степень

Долго думал, как сделать итеративный процесс возведения в степень (SICP, упражнение 1.16). Я сразу догадался, что брать за основу надо число в двоичной системе счисления, но мне было непонятно, как переводить это число в алгоритм.

Вот например, как выглядели алгоритмы и двоичная запись для некоторых чисел:

7 *+*+ 111
8 *** 1000
9 ***+ 1001
10 **+* 1010

В итоге оказалось всё просто. Надо отбрасывать самый первый знак, а дальше трактовать единицу как "возвести в квадрат и умножить", а ноль — как "просто возвести в квадрат".

Также я впервые использовал здесь функцию cons, которая объединяет first и rest.

; Возвести число b в степень p (p должно быть больше 1)
(define (pow-hm b p)
(apply (fn (x y) (if (= y) (* x x) (* x x b)))
(cons b (map int (1 (explode (bits p)))))
2))


А вот то же самое, только по "итеративному алгоритму" предложенному в SICP:

; Возвести b в степень n
(define (pow-sicp-1 b n)
(cond ((= n 1) b)
((= (% n 2)) (pow-sicp-1 (* b b) (/ n 2)))
(true (* (pow-sicp-1 (* b b) (/ n 2)) b))))


Вроде бы как, ошибиться тут просто негде. Однако… цитирую SICP:

«Храните, помимо значения степени n и основания b, дополнительную переменную состояния a, и определите переход между состояниями так, чтобы произведение abn от шага к шагу не менялось».

Спрашивается, зачем козе баян? Разве что, перехерачить алгоритм вот так:

; То же самое, но с инвариантом
(define (pow-sicp-2 b n (a 1))
(if (= n 1)
(* b a)
(* a (pow-sicp-2 (* b b) (/ n 2) (if (= (% n 2)) 1 b)))))


Согласно тестам, кстати, самый быстрый алгоритм — это рекурсия без инварианта. Алгоритмы с инвариантом и с разложением на биты немного медленнее. Впрочем, скорости там одного порядка, существенной разницы нет.

Дополнение.

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

(define (double x) (* x 2))
(define (halve x) (/ x 2))
(define (even? x) (if (= (% x 2))))


Из них надо склепать умножение. Вот труе итеративный вариант:

; Умножить а на b
(define (mul-sicp-i a b (p 0))
(let (p 0)
(until (= b 1)
(set 'p (+ p (if (even? b) 0 a)))
(set 'a (double a))
(set 'b (halve b)))
(+ a p)))


А вот то же самое, но через жопу концевую рекурсию:

; Умножить a на b
(define (mul-sicp-t a b (p 0))
(if (= b 1)
(+ a p)
(mul-sicp-t (double a) (halve b) (+ p (if (even? b) 0 a)))))

воскресенье, 11 апреля 2010 г.

Способы разменять монеты

Хитроумный алгоритм по размену монет из первой главы SICP. Мне потребовалось значительное время, чтобы понять как он работает и воспроизвести его:

#
# Число способов разменять n центов монетами по 1, 5, 10, 25 и 50 центов.
#

; Доступные монеты
(set 'монеты '(1 5 10 25 50))

; Число разменов n центов с помощью c монет.
(define (размены n (c 5))
(cond ((< n) 0)
((= n) 1)
((= c 1) 1)
(true (+ (размены n (- c 1))
(размены (- n (монеты (- c 1))) c)))))


Это воссозданный мной алгоритм из книжки. Вначале же я сделал иначе: считал все возможные методы размена, потом через unique отбирал уникальные, потом через memoize добивался терпимой скорости. Тоже работало, плюс кроме числа способов размена я видел в списке ещё и сами способы.

Но решение из книжки, конечно, красивее.

Кстати, в самой книжке алгоритм реализован, на первый взгляд, примерно так же, но там есть слова: «на то, чтобы получить ответ 292 уйдёт какое-то время». В реальности время выполнения процедуры на моём компьютере — 0,3 миллисекунды.

То ли эта фраза писалась в семидесятые года, то ли newLISP гораздо круче чем Scheme.

PS: Разменять доллар можно 292 способами. Способов разменять 10 долларов уже 800 тысяч.

PPS: Те же яйца, но с печатью дерева.

(define (размены n (c 5))
(print "Р" n " (" c "): ")
(cond ((< n) (println 0) 0)
((= n) (println 1) 1)
((= c 1) (println 1) 1)
(true (println "Р" n " (" (- c 1) ") + Р" (- n (монеты (- c 1))) " (" c ")")
(+ (размены n (- c 1)) (размены (- n (монеты (- c 1))) c)))))

пятница, 9 апреля 2010 г.

Хвостовая рекурсия

Немного доброты по ссылке с форума newLISP (Ссылка)

Подводя итог, эта точка зрения на хвостовую рекурсию основана на следующих идеях:

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

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

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

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

Фриддл, ДКА и НКА

Закончил читать книгу Фриддла про регулярные выражения.

Фриддл, конечно, молодец, сказать нечего. Но про автоматы и про разницу между ними он написал крайне непонятно. Гораздо понятнее вот здесь:

http://www.rsdn.ru/article/alg/nka.xml

Если бы я сначала потратил 10 минут на чтение этого текста, а только потом принялся бы за Фриддла, полагаю, я бы вынес из книги Фриддла гораздо больше пользы.

четверг, 8 апреля 2010 г.

SICP

Начал читать волшебную книгу: SICP

Вот моя программка получения квадратного корня:

(define (more-exact x pre)
(let (next-pre (div (add (div x pre) pre) 2))
(if (= pre next-pre) pre (more-exact x next-pre))))

(define (squa x) (more-exact x 1))


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

Алсо, в newLISP есть функция constant. Если хочется переопределить часть символов, чтобы использовать + вместо add и - вместо div, надо использовать её.

А вот функция для куба.

(define (cube x)
(let (more-exact (fn (pre)
(let (next-pre (div (add (div x pre pre) pre pre) 3))
(if (= pre next-pre) pre (more-exact next-pre)))))
(more-exact 1)))


Что характерно, на этот раз функция more-exact сделана локальной и, следовательно, не только не будет занимать ценное имя, но и сможет использовать родительский "х".

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

JavaScript: навигация стрелками и Enter

Убил восемь (!) часов на скорбный труд по созданию этого монстрика.

/**
*
* Keyboard Form Navigation
* Использование: в конце документа исполните KFN.init ('form1');
*
**/

// Фокусируется на элементе
function setFocus(id) {
document.getElementById(id).focus();
setTimeout("document.getElementById('"+id+"').select();", 100);
}

// Возвращает случайный ID, не присутствующий ещё в документе
function randomId (){
do {
var newId = "rnd" + Math.floor((Math.random() * 1000000));
}
while (document.getElementById(newId) != null);
return newId;
}

// Набор функций, обеспечивающий навигацию по стрелкам и Enter
var KFN = {

// Список полей формы, по которым надо перемещаться стрелками
_elements : [],

// Форма субмиттится только если _flag == true
_allow_submit : false,

// Субмитит форму. Нужен, чтобы форма не субмитилась по каждому энтеру
_submit : function () {
if (this._allow_submit)
document.getElementById(this._elements[0]).form.submit();
},

// Инициализирует форму для работы со стрелками и Enter
// Меняет onFocus у всех полей формы.
init : function (formId) {
var thisForm = document.getElementById(formId);
thisForm.setAttribute("autocomplete", "off")
if (thisForm.addEventListener) {
thisForm.addEventListener("submit", function(e) { e.preventDefault(); }, false);
thisForm.addEventListener("keydown", function(e) { KFN.next(e); }, false);
}
else if (thisForm.attachEvent) {
// Ослик вместо preventDefault использует event.returnValue = false;
thisForm.attachEvent("onsubmit", function(e) { e.returnValue = false; });
thisForm.attachEvent("onkeydown", function(e) { KFN.next(e); });
}
for (var i = 0 , e = thisForm.elements, len = e.length; i < len; i++ ) {
if (/text|password|file|checkbox|radio|select/.test (e[i].type)) {
// Если у поля нет id, ставим какой-нибудь
if (!e[i].id) e[i].id = randomId();
this._elements.push (e[i].id);
// Автокомплект должен быть отключён как на форме, так и на всех полях.
e[i].setAttribute("autocomplete", "off")
// Без этих самодельных обработок мы бы не узнали, на каком элементе фокус
e[i].hasFocus=false;
e[i].onfocus=function(){this.hasFocus=true;};
e[i].onblur =function(){this.hasFocus=false;};
} else if (e[i].type == "submit") {
// Обработка нажатия мышью на обычную субмит-кнопку
e[i].onfocus=function(){KFN._allow_submit=true;}
e[i].onblur=function(){KFN._allow_submit=false;}
if (e[i].addEventListener) {
e[i].addEventListener("click", function() { KFN._submit(); }, false);
} else if (e[i].attachEvent) {
e[i].attachEvent("onclick", function() { KFN._submit(); });
}
}
}
// Фокусируемся на первом поле формы
setFocus (this._elements[0]);
},

// Переходит к следующему полю формы
next: function (input) {
// Обрабатываем только три клавиши. 13: Enter, 38: Up, 40: Down
if ((input.keyCode != 13) && (input.keyCode != 38) && (input.keyCode != 40))
return true;
// Находим текущий элемент (по фокусу)
for (var i=0; i<this._elements.length; i++)
if (document.getElementById(this._elements[i]).hasFocus)
var current = i;
if (current === undefined) return true;
// Перемещаем фокус
switch (input.keyCode) {
case 13: // Enter
if ((current + 1) < this._elements.length) {
setFocus (this._elements[1+current]);
} else {
document.getElementById(this._elements[current]).form.submit();
}
break;
case 38: // Вверх
current--;
if (current < 0) current = 0;
setFocus (this._elements[current]);
break;
case 40: // Вниз
current++;
if (current >= this._elements.length) current = this._elements.length - 1;
setFocus (this._elements[current]);
break;
}
return false;
}
}

Windows -> Linux: ошибка sh: ./index.cgi: not found

Перенёс скрипт из Windows в Linux, поменял первую строчку с #newlisp на #/usr/bin/newlisp.

Всё равно http-сервер пишет "not found". Через несколько минут догадался открыть файл в vim.

Оказывается, у меня было написано не #/usr/bin/newlisp, а #/usr/bin/newlisp^M.

Убрал с конца Windows-перевод строки и всё заработало.

Перекодировка также сработала нормально:
http://hilocomod.blogspot.com/2010/04/php-windows-linux.html

воскресенье, 4 апреля 2010 г.

Обработка html в newLISP

Лутц таки убрал "анафорическую" переменную $it из replace. Теперь моя функция обработки html выглядит вот так:

; Отображает несколько html-файлов с вкраплениями кода на newLISP
(define (process-html)
(let (html (join (map read-file (args))))
(do-while (> $0)
(replace "<@((?!<@).)+?@>" html
(if (catch (eval-string (2 -2 $0)) 'result) (string result) "") 4))
(print "Content-type: text/html\r\n\r\n" html)))

CSS: отцентровать весь документ

По центру страницы идёт полоса шириной 800 пикселей. Как это сделать через CSS.

1. Прописываем стили:

<style>
body { font-family: Tahoma; text-align: center;} /* для Ослика */
.wrapper { width: 800; margin: 0 auto; } /* для остальных браузеров */
</style>


2. Обёртываем данные в дивы:

<body>
<div class="wrapper">
Наши данные.
</div>
<div class="wrapper">
...

пятница, 2 апреля 2010 г.

Три добродетели программиста

Источник: http://en.wikipedia.org/wiki/Larry_Wall

Во время работы над второй редакцией книги "Программируем на Перл" Ларри Волл, вместе с Рэндаллом Л. Шварцем и Томом Христиансеном, сформулировал три добродетели программиста. Вот они.

1. Лень — качество, которое заставляет тебя прикладывать большие усилия, чтобы уменьшить общие затраты энергии. Оно заставляет тебя писать трудосберегающие программы, которые другие люди находят полезными, и документировать эти программы, чтобы тебе не приходилось отвечать на кучу вопросов пользователей.

2. Нетерпеливость — гнев, который ты испытываешь, когда компьютер ленится. Этот гнев заставляет тебя писать программы, которые не только выполняют твои команды, но и предвидят их. Или, по меньшей мере, притязают на это.

3. Высокомерие — непомерная гордость, что-то из разряда тех пороков, за которые Зевс поражает молнией. Также это качество, которое заставляет тебя писать (и поддерживать) программы, которыми другие люди будут восхщаться.

четверг, 1 апреля 2010 г.

PuTTY и слепой синий цвет

В PuTTY из-под Windows синий цвет на чёрном фоне практически неразличим. Решение нашёл вот здесь:

1. Переходим в Window > Colours.
2. Ставим ANSI Blue в Red:74 Green:74 Blue:255.
3. Ставим ANSI Blue Bold в Red:140 Green:140 Blue:255.


Ещё хорошая идея сменить шрифт.

Я поставил себе Lucida 12.

Миграция PHP программы с Windows на Linux

Переводил один небольшой проект с Windows на Ubuntu. Проект был написан на быдло-PHP: аккуратно, но весьма малоопытным программистом. Вот несколько встретившихся мелочей.

1. PHP минут 15 упорно не хотел запускать index.php, предлагая вместо этого скачать phtml-файл. Дело оказалось в кэше браузера.

2. Проект хранил свои настройки в файлах с русскими именами и с русскими переменными внутри. После переконвертации всех программных и настроечных файлов из cp1251 в utf8 проект замечательно заработал. Переконвертировал я так:

find . -name "*php" -exec iconv -f cp1251 -t utf8 {} -o {}.utf8 \;
find . -iregex ".+.php$" -exec rm {} \;
find . -name '.utf8' -prune -o -exec rename 's/\.utf8$//i' {} +

3. Русские файлы прикреплялись с потерей кусков имени. Проблема оказалась в некорректной работе функции basename. Чтобы нормально заработало, пришлось добавить строку

setlocale(LC_ALL, 'ru_RU.UTF8');

4. Выяснилось, что IE, получая файлы в UTF8 через скрипт getfile.php?filename=xxx, присваивает им безумные имена. Переписал функцию, чтобы ссылка вела сразу прямо на файл, всё заработало.

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

Установка Subversion

Поставил Subversion по вот этому HowTo:

http://habrahabr.ru/blogs/ubuntu/26117/

Ниже он же с моими комментариями.

Установка Subversion и Trac

1. Ставим trac, python и subversion:

sudo apt-get install trac libapache2-svn subversion python-subversion libapache2-mod-python

2. Включаем модуль питона:

sudo a2enmod mod_python

Настройка Subversion

1. Создаем группу для работы с SVN:

sudo groupadd svn

2. Добавляем себя (ваше имя пользователя в Ubuntu) в группу svn:

sudo usermod -a -G svn имя_пользователя

3. Добавляем apache в группу svn:

sudo usermod -a -G svn www-data

4. Создаем папку для будущего репозитория:

sudo mkdir /var/svn

5. Создаем репозиторий:

sudo svnadmin create /var/svn

6. Меняем права к папке для доступа пользователям из группы svn:

sudo chown -R www-data:svn /var/svn

7. Разрешаем на запись группе и владельцу:

sudo chmod -R g+ws /var/svn

8. Создаем пароль для доступа к папке с репозиторием, который впоследствии будет использоваться apache'ем:

sudo htpasswd -c -m /etc/apache2/svn.htpasswd имя_пользователя

Примечание Hilo: это отдельный пароль для svn, с паролем входа в систему совпадать не обязан. При создании пароля для следующего пользователя надо запускать htpasswd уже без ключа -c, который означает "создать новый файл, стерев, что как, предыдущий".

9. Теперь создадим правило для Apache для доступа к svn-репозиторию:

Alt+F2 gksu gedit /etc/apache2/conf.d/svn

<Location "/svn">
DAV svn
SVNPath /var/svn
AuthType Basic
AuthName "SVN Repo"
AuthUserFile /etc/apache2/svn.htpasswd
Require valid-user
</Location>

Примечание Hilo: я вместо этого добавил следующее в /etc/apache2/sites-available/default:

# 
# Subversion
#
Listen 82
ServerName *:82
NameVirtualHost *:82
<VirtualHost *:82>
<Location "/">
DAV svn
SVNPath /var/svn
AuthType Basic
AuthName "SVN Repo"
AuthUserFile /etc/apache2/svn.htpasswd
Require valid-user
</Location>
</VirtualHost>


Идея в том, чтобы svn был доступен по localhost:82


10. SVN установлен! Он доступен по адресу http://localhost/svn

Примечание Hilo: sudo /etc/init.d/apache2 restart

Настройка Trac

1. Создаем папку для Trac'а :

sudo mkdir /var/trac

2. Создаем среду для работы Trac с SVN :

sudo trac-admin /var/trac initenv

3. Меняем права к папке с trac'ом :

sudo chown -R www-data:svn /var/trac
sudo chmod -R g+ws /var/trac

4. Создаем пароль для админ-доступа к папке с trac'ом, который впоследствии будет использоваться apache'ем :

sudo htpasswd -c -m /etc/apache2/trac.htpasswd имя_пользователя

5. Создадим правило для Apache для доступа к trac'у :

Alt+F2 gksu gedit /etc/apache2/conf.d/trac

<LocationMatch "/trac/login">
AuthType Basic
AuthName "Projects"
AuthUserFile /etc/apache2/trac.htpasswd
Require valid-user
</LocationMatch>

<Location /trac>
SetHandler mod_python
PythonInterpreter main_interpreter
PythonHandler trac.web.modpython_frontend
PythonOption TracEnv /var/trac
PythonOption TracUriRoot /trac
</Location>

Примечание Hilo: то же самое, повесил на порт 83, добавив в
/etc/apache2/sites-available/default вот что:

#
# Trac
#
Listen 83
ServerName *:83
NameVirtualHost *:83
<VirtualHost *:83>

<LocationMatch "/login">
AuthType Basic
AuthName "Projects"
AuthUserFile /etc/apache2/trac.htpasswd
Require valid-user
</LocationMatch>

<Location "/">
SetHandler mod_python
PythonInterpreter main_interpreter
PythonHandler trac.web.modpython_frontend
PythonOption TracEnv /var/trac
PythonOption TracUriRoot /
</Location>

</VirtualHost>


6. Trac установлен! Он доступен по адресу http://localhost/trac

Установка плагина TracWebAdmin

1. Чтобы устанавливать плагины для trac'а - нам нужен пакет "python-setuptools" :
sudo apt-get install python-setuptools

2. Переходим в папку, где лежат trac-плагины :
cd /usr/share/trac/plugins

3. Будем ставить TracWebAdmin из svn-репозитария :
sudo svn co http://svn.edgewall.org/repos/trac/sandbox/webadmin/

Примечание Hilo: после выполнения команды скачался один файл README, в котором
было предложено сделать switch. Сработало следующее:

cd webadmin
sudo svn switch http://svn.edgewall.org/repos/trac/plugins/0.10/webadmin


4. Переходим в папку с webadmin'ом и собираем его установочный egg-файл :

cd webadmin
sudo python setup.py bdist_egg

5. Переходим в папку со свежеиспеченным установщиком командой...

cd /usr/share/trac/plugins/webadmin/dist
...выводим создержимое папки...
dir
... и копируем имя egg-файла.

6. Устанавливаем TracWebAdmin :

sudo easy_install-2.5 имя_egg_файла

7. Включаем наш плагин в конфигурацию trac'а :

Alt+F2 gksu gedit /etc/trac/trac.ini
[components]
webadmin.* = enabled

Подсчитать число строк в проекте

Как подсчитать общее число строк во всех скриптах проекта?

Например, так:

find . -name "*cgi" | xargs wc -l


Правда, я делал чуть иначе. Сначала я вывел список всех файлов в файл:

find . -regex ".*\(lsp\|cgi\|scm\)$" > test.txt


Потом убрал в файле лишнее (типа резервных копий) текстовым редактором. Потом уже направил оставшиеся файлы на wc:

cat test.txt > xargs wc -l

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

PHP: парсинг лога

Базовый инструментарий для парсинга логов в PHP практически совпадает с ЛИСП’овским:

explode — разбить строку в массив
preg_match_all — найти все вхождения (в массив)
count — длина массива
array_unique — уникальные элементы массива
file — прочесть файл в массив
file_get_contents — прочесть файл в строку

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

воскресенье, 28 марта 2010 г.

Ubuntu — смонтировать флешку

Ситуация: на удалённом компьютере вставлена, но не смонтирована флешка. Ребут не помогает, так как флешка была отключена через «отсоединить том» на рабочем столе Гнома.

Решение:

sudo mkdir /media/flash
sudo mount /dev/sdb1 /media/flash -o utf8

суббота, 27 марта 2010 г.

Большие числа

Сделал мини-библиотеку для работы с большими числами (типа стозначных или пятисотзначных).

Странное дело: первое возведение тройки в тысячную степень занимает 1 секунду, а каждое следующее возведение увеличивает это время примерно на секунду, пока к пятой итерации не достигает 4,5 секунд. На этой скорости происходит стабилизация: сотое повторение занимает те же 4,5 секунды.

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

; Добить нулями num до длины len
(define (big-leading-zero len num)
(append (dup 0 (- len (length num))) num))

; Выровнять большие числа по длине
(define (big-align)
(let (len (apply max (map length (args))))
(map (curry big-leading-zero len) (args))))

; 123 -> '(1 2 3)
(define (int-to-big num)
(map int (explode (string num))))

; '(1 2 3) -> 123
(define (big-to-int big)
(int (join (apply string big))))

; '(1 2 3) -> "123"
(define (big-to-string big)
(join (map string big)))

; "123" -> '(1 2 3)
(define (string-to-big big)
(map int (explode big)))

; "Упаковать" число так, чтобы в каждом разряде сидело не больше 9
(define (big-pack num)
(let (num (reverse num) o-num '() dig nil tra 0 lead nil)
(while (or (> tra) (not (null? num)))
(set 'dig (+ (or (pop num) 0) tra))
(push (% dig 10) o-num)
(set 'tra (/ dig 10)))
(if (set 'lead (find 0 o-num <)) (lead o-num) '(0))))

; Сложить большие числа
(define (big-add)
(big-pack (map (fn (x) (apply + x)) (transpose (apply big-align (args))))))

; Умножить два больших числа
(define (big-mul-2 num1 num2)
(apply big-add
(map (fn (y) (append y (dup 0 $idx)))
(reverse (map (fn (x) (map (curry mul x) num2)) num1)))))

; Перемножить большие числа
(define (big-mul)
(apply big-mul-2 (args) 2))

; Возвести большое число в большую степень
(define (big-pow num pw)
(let (pw2 (list num))
(dotimes (x (- (length (bits pw)) 1))
(push (apply big-mul-2 (dup (if pw2 (first pw2) num) 2)) pw2))
(apply big-mul
(map (fn (x y) (if (= x "1") y '(1))) (explode (bits pw)) pw2))))


А вот довесочек, проверяющий скорость:

; Проверка скорости
(dotimes (x 1000)
(println x ": " (time (big-pow '(3) 1000))))

russian.lsp

Обновил модуль "russian.lsp".

1. Нашёл баг в функции cyr-translit: "explode" неправильно работал из-под Windows с русскими буквами. Заменил конструкцию (explode linea) на (cyr-explode linea) и всё заработало. В функции cyr-explode пришлось выяснять текущую кодировку и использовать для препарирования строки или explode или find-all ".", смотря какой из вариантов работает.

2. Нашёл мелкий баг в функции cyr-utf-win: в конце строки возвращался (char nil), то есть, "\000". На печати он был не виден, поэтому баг я раньше не замечал.

; Russian encoding support by Anon. 27 Mar 2010.
; Varning: this module and codepages inside are far not complete. Some special
; symbols are not included.
;
; Functions:
;
; Usage: (cyr-win-utf "text in windows-1251 encoding")
; Decodes text from windows-1251 to utf-8
;
; Usage: (cyr-koi-utf "text in KOI8R encoding")
; Decodes text from KOI8R to utf-8
;
; Usage: (cyr-utf-win "text in utf-8 encoding")
; Decodes text from utf-8 to win-1251
;
; Usage: (url-encode-utf "В. Пупкин")
; Encodes strings for urls (utf)
; Output: "%D0%92.%20%D0%9F%D1%83%D0%BF%D0%BA%D0%B8%D0%BD"
;
; Usage: (url-encode-win "Василий Пупкин")
; Encodes strings for urls (windows-1251)
; Output: "%C2.%20%CF%F3%EF%EA%E8%ED"
;
; Usage: (cyr-rtf "\\u1055\\'3F\\u1086\\'3F\\u1089\\'3F\\u1083\\'3F")
; Decodes russian letters sequences in the .rtf file.
; Example: (cyr-rtf (read-file "report.rtf"))
;
; Usage: (cyr-rtf-r "спецификация")
; Encodes a string to the rtf russian letters format (useful for searching
; in raw-rtf). Expect problems with spaces and other \u special symbols.
;
; Usage: (cyr-translit "Щёлочь, кислота, алюминий") => "SCHyolochy, kislota, alyuminiy"
;
; Usage: (cyr-translit_ "Василий Пупкин") => "Vasiliy_Pupkin"

; Russian alphabet (33 small and 33 big letters)
(set 'cyr-alphabet (list "а" "б" "в" "г" "д" "е" "ё" "ж" "з" "и" "й" "к" "л" "м" "н" "о" "п" "р" "с" "т" "у" "ф" "х" "ц" "ч" "ш" "щ" "ъ" "ы" "ь" "э" "ю" "я" "А" "Б" "В" "Г" "Д" "Е" "Ё" "Ж" "З" "И" "Й" "К" "Л" "М" "Н" "О" "П" "Р" "С" "Т" "У" "Ф" "Х" "Ц" "Ч" "Ш" "Щ" "Ъ" "Ы" "Ь" "Э" "Ю" "Я"))

; Url-encoding for russian letters UTF
(set 'en-url-utf '(("а" "%D0%B0") ("б" "%D0%B1") ("в" "%D0%B2") ("г" "%D0%B3")
("д" "%D0%B4") ("е" "%D0%B5") ("ё" "%D1%91") ("ж" "%D0%B6") ("з" "%D0%B7")
("и" "%D0%B8") ("й" "%D0%B9") ("к" "%D0%BA") ("л" "%D0%BB") ("м" "%D0%BC")
("н" "%D0%BD") ("о" "%D0%BE") ("п" "%D0%BF") ("р" "%D1%80") ("с" "%D1%81")
("т" "%D1%82") ("у" "%D1%83") ("ф" "%D1%84") ("х" "%D1%85") ("ц" "%D1%86")
("ч" "%D1%87") ("ш" "%D1%88") ("щ" "%D1%89") ("ъ" "%D1%8A") ("ы" "%D1%8B")
("ь" "%D1%8C") ("э" "%D1%8D") ("ю" "%D1%8E") ("я" "%D1%8F") ("А" "%D0%90")
("Б" "%D0%91") ("В" "%D0%92") ("Г" "%D0%93") ("Д" "%D0%94") ("Е" "%D0%95")
("Ё" "%D0%81") ("Ж" "%D0%96") ("З" "%D0%97") ("И" "%D0%98") ("Й" "%D0%99")
("К" "%D0%9A") ("Л" "%D0%9B") ("М" "%D0%9C") ("Н" "%D0%9D") ("О" "%D0%9E")
("П" "%D0%9F") ("Р" "%D0%A0") ("С" "%D0%A1") ("Т" "%D0%A2") ("У" "%D0%A3")
("Ф" "%D0%A4") ("Х" "%D0%A5") ("Ц" "%D0%A6") ("Ч" "%D0%A7") ("Ш" "%D0%A8")
("Щ" "%D0%A9") ("Ъ" "%D0%AA") ("Ы" "%D0%AB") ("Ь" "%D0%AC") ("Э" "%D0%AD")
("Ю" "%D0%AE") ("Я" "%D0%AF")))

; Url-encoding for russian letters windows-1251
(set 'en-url-win '(("я" "%FF") ("ю" "%FE") ("э" "%FD") ("ь" "%FC") ("ы" "%FB")
("ъ" "%FA") ("щ" "%F9") ("ш" "%F8") ("ч" "%F7") ("ц" "%F6") ("х" "%F5")
("ф" "%F4") ("у" "%F3") ("т" "%F2") ("с" "%F1") ("р" "%F0") ("п" "%EF")
("о" "%EE") ("н" "%ED") ("м" "%EC") ("л" "%EB") ("к" "%EA") ("й" "%E9")
("и" "%E8") ("з" "%E7") ("ж" "%E6") ("ё" "%B8") ("е" "%E5") ("д" "%E4")
("г" "%E3") ("в" "%E2") ("б" "%E1") ("а" "%E0") ("Я" "%DF") ("Ю" "%DE")
("Э" "%DD") ("Ь" "%DC") ("Ы" "%DB") ("Ъ" "%DA") ("Щ" "%D9") ("Ш" "%D8")
("Ч" "%D7") ("Ц" "%D6") ("Х" "%D5") ("Ф" "%D4") ("У" "%D3") ("Т" "%D2")
("С" "%D1") ("Р" "%D0") ("П" "%CF") ("О" "%CE") ("Н" "%CD") ("М" "%CC")
("Л" "%CB") ("К" "%CA") ("Й" "%C9") ("И" "%C8") ("З" "%C7") ("Ж" "%C6")
("Ё" "%A8") ("Е" "%C5") ("Д" "%C4") ("Г" "%C3") ("В" "%C2") ("Б" "%C1")
("А" "%C0")))

; Url-encoding for special symbols
(set 'en-url-sym '((" " "%20") ("$" "%24") ("&" "%26") ("+" "%2B")
("," "%2C") ("/" "%2F") (":" "%3A") (";" "%3B") ("=" "%3D") ("?" "%3F") ("@" "%40")
(" " "%20") ("\"" "%22") ("<" "%3C") (">" "%3E") ("#" "%23") ("%" "%25") ("{" "%7B")
("}" "%7D") ("|" "%7C") ("\\" "%5C") ("^" "%5E") ("~" "%7E") ("[" "%5B") ("]" "%5D")
("`" "%60")))

; Encoding table utf-8
(set 'en-utf-8 '((53424 "а") (53425 "б") (53426 "в") (53427 "г") (53428 "д")
(53429 "е") (53649 "ё") (53430 "ж") (53431 "з") (53432 "и") (53433 "й")
(53434 "к") (53435 "л") (53436 "м") (53437 "н") (53438 "о") (53439 "п")
(53632 "р") (53633 "с") (53634 "т") (53635 "у") (53636 "ф") (53637 "х")
(53638 "ц") (53639 "ч") (53640 "ш") (53641 "щ") (53642 "ъ") (53643 "ы")
(53644 "ь") (53645 "э") (53646 "ю") (53647 "я") (53392 "А") (53393 "Б")
(53394 "В") (53395 "Г") (53396 "Д") (53397 "Е") (53377 "Ё") (53398 "Ж")
(53399 "З") (53400 "И") (53401 "Й") (53402 "К") (53403 "Л") (53404 "М")
(53405 "Н") (53406 "О") (53407 "П") (53408 "Р") (53409 "С") (53410 "Т")
(53411 "У") (53412 "Ф") (53413 "Х") (53414 "Ц") (53415 "Ч") (53416 "Ш")
(53417 "Щ") (53418 "Ъ") (53419 "Ы") (53420 "Ь") (53421 "Э") (53422 "Ю")
(53423 "Я") (14844052 "—") (14845078 "№")))

; Encoding table windows-1251
(set 'en-win-1251 '((255 "я") (254 "ю") (253 "э") (252 "ь") (251 "ы")
(250 "ъ") (249 "щ") (248 "ш") (247 "ч") (246 "ц") (245 "х") (244 "ф")
(243 "у") (242 "т") (241 "с") (240 "р") (239 "п") (238 "о") (237 "н")
(236 "м") (235 "л") (234 "к") (233 "й") (232 "и") (231 "з") (230 "ж")
(184 "ё") (229 "е") (228 "д") (227 "г") (226 "в") (225 "б") (224 "а")
(223 "Я") (222 "Ю") (221 "Э") (220 "Ь") (219 "Ы") (218 "Ъ") (217 "Щ")
(216 "Ш") (215 "Ч") (214 "Ц") (213 "Х") (212 "Ф") (211 "У") (210 "Т")
(209 "С") (208 "Р") (207 "П") (206 "О") (205 "Н") (204 "М") (203 "Л")
(202 "К") (201 "Й") (200 "И") (199 "З") (198 "Ж") (168 "Ё") (197 "Е")
(196 "Д") (195 "Г") (194 "В") (193 "Б") (192 "А") (185 "№")))

; Encoding table KOI8R
(set 'en-koi8r '((241 "Я") (224 "Ю") (252 "Э") (248 "Ь") (249 "Ы")
(255 "Ъ") (253 "Щ") (251 "Ш") (254 "Ч") (227 "Ц") (232 "Х") (230 "Ф")
(245 "У") (244 "Т") (243 "С") (242 "Р") (240 "П") (239 "О") (238 "Н")
(237 "М") (236 "Л") (235 "К") (234 "Й") (233 "И") (250 "З") (246 "Ж")
(179 "Ё") (229 "Е") (228 "Д") (231 "Г") (247 "В") (226 "Б") (225 "А")
(209 "я") (192 "ю") (220 "э") (216 "ь") (217 "ы") (223 "ъ") (221 "щ")
(219 "ш") (222 "ч") (195 "ц") (200 "х") (198 "ф") (213 "у") (212 "т")
(211 "с") (210 "р") (208 "п") (207 "о") (206 "н") (205 "м") (204 "л")
(203 "к") (202 "й") (201 "и") (218 "з") (214 "ж") (163 "ё") (197 "е")
(196 "д") (199 "г") (215 "в") (194 "б") (193 "а")))

; RTF-encoding (4-digit one). Warning: instead of "3f" sometimes you can see "3F".
(set 'en-rtf '(({\u1072\'3f} {а}) ({\u1073\'3f} {б}) ({\u1074\'3f} {в})
({\u1075\'3f} {г}) ({\u1076\'3f} {д}) ({\u1077\'3f} {е}) ({\u1105\'3f} {ё})
({\u1078\'3f} {ж}) ({\u1079\'3f} {з}) ({\u1080\'3f} {и}) ({\u1081\'3f} {й})
({\u1082\'3f} {к}) ({\u1083\'3f} {л}) ({\u1084\'3f} {м}) ({\u1085\'3f} {н})
({\u1086\'3f} {о}) ({\u1087\'3f} {п}) ({\u1088\'3f} {р}) ({\u1089\'3f} {с})
({\u1090\'3f} {т}) ({\u1091\'3f} {у}) ({\u1092\'3f} {ф}) ({\u1093\'3f} {х})
({\u1094\'3f} {ц}) ({\u1095\'3f} {ч}) ({\u1096\'3f} {ш}) ({\u1097\'3f} {щ})
({\u1098\'3f} {ъ}) ({\u1099\'3f} {ы}) ({\u1100\'3f} {ь}) ({\u1101\'3f} {э})
({\u1102\'3f} {ю}) ({\u1103\'3f} {я}) ({\u1040\'3f} {А}) ({\u1041\'3f} {Б})
({\u1042\'3f} {В}) ({\u1043\'3f} {Г}) ({\u1044\'3f} {Д}) ({\u1045\'3f} {Е})
({\u1025\'3f} {Ё}) ({\u1046\'3f} {Ж}) ({\u1047\'3f} {З}) ({\u1048\'3f} {И})
({\u1049\'3f} {Й}) ({\u1050\'3f} {К}) ({\u1051\'3f} {Л}) ({\u1052\'3f} {М})
({\u1053\'3f} {Н}) ({\u1054\'3f} {О}) ({\u1055\'3f} {П}) ({\u1056\'3f} {Р})
({\u1057\'3f} {С}) ({\u1058\'3f} {Т}) ({\u1059\'3f} {У}) ({\u1060\'3f} {Ф})
({\u1061\'3f} {Х}) ({\u1062\'3f} {Ц}) ({\u1063\'3f} {Ч}) ({\u1064\'3f} {Ш})
({\u1065\'3f} {Щ}) ({\u1066\'3f} {Ъ}) ({\u1067\'3f} {Ы}) ({\u1068\'3f} {Ь})
({\u1069\'3f} {Э}) ({\u1070\'3f} {Ю}) ({\u1071\'3f} {Я})
({\u160\'3f} { }) ({\u8470\'3f} {№})))

; Translit encoding
(set 'translit '(("а" "a") ("б" "b") ("в" "v") ("г" "g") ("д" "d") ("е" "e") ("ё" "yo")
("ж" "zh") ("з" "z") ("и" "i") ("й" "y") ("к" "k") ("л" "l") ("м" "m") ("н" "n")
("о" "o") ("п" "p") ("р" "r") ("с" "s") ("т" "t") ("у" "u") ("ф" "f") ("х" "h")
("ц" "c") ("ч" "ch") ("ш" "sh") ("щ" "sch") ("ъ" "y") ("ы" "y") ("ь" "y") ("э" "e")
("ю" "yu") ("я" "ya") ("А" "A") ("Б" "B") ("В" "V") ("Г" "G") ("Д" "D") ("Е" "E")
("Ё" "YO") ("Ж" "ZH") ("З" "Z") ("И" "I") ("Й" "Y") ("К" "K") ("Л" "L") ("М" "M")
("Н" "N") ("О" "O") ("П" "P") ("Р" "R") ("С" "S") ("Т" "T") ("У" "U") ("Ф" "F")
("Х" "H") ("Ц" "C") ("Ч" "CH") ("Ш" "SH") ("Щ" "SCH") ("Ъ" "Y") ("Ы" "Y") ("Ь" "Y")
("Э" "E") ("Ю" "YU") ("Я" "YA")))

; Internal function. Explodes string to bytes
(define (cortar linea)
(unpack (dup "b" (length linea)) linea))

; Internal function. Applies codetable to the list of bytes
(define (en-de-byte linea tabla (o-linea ""))
(dolist (x (cortar linea))
(push (or (lookup x tabla) (char x)) o-linea -1))
o-linea)

; Internal function. Explodes string to letters
; There can be surprises, becouse both find-all and explode
; should work (in theory) with any encoding.
(define (cyr-explode linea)
(if (= (last (parse ((set-locale) 0) ".")) "1251")
(find-all "." linea)
(explode linea)))

; Usage: (cyr-translit "Щёлочь, кислота, аллюминий") => "SCHyolochy, kislota, allyuminiy"
(define (cyr-translit linea)
(join (map (fn (x) (or (lookup x translit) x)) (cyr-explode linea))))

; Usage: (cyr-translit_ "Василий Пупкин") => "Vasiliy_Pupkin"
(define (cyr-translit_ linea)
(replace " " (cyr-translit linea) "_"))

; Usage: (cyr-rtf "\\u1055\\'3F\\u1086\\'3F\\u1089\\'3F\\u1083\\'3F")
; Decodes russian letters in the .rtf file.
(define (cyr-rtf linea)
(replace {\\u[18][0-9]+\\'3f} linea (lookup (lower-case $it) en-rtf) 1))

; Usage: (cyr-rtf-r "спецификация")
; Encodes a string to the rtf russian letters format (useful for searching
; in raw-rtf). Expect problems with spaces and other \u special symbols.
(define (cyr-rtf-r linea)
(let (letra "" efecto "")
(while (!= (set 'letra (pop linea)) "")
(push (or (lookup letra (map reverse en-rtf)) letra) efecto -1))
(replace "\\" efecto "\\\\")
(replace "'3f" efecto "'3F")))

; Usage: (url-encode-utf "Василий Пупкин")
; Encodes strings for urls (utf)
(define (url-encode-utf t-linea)
(set 't-out "")
(dostring (t-char t-linea)
(push (or (lookup (char t-char) en-url-utf)
(lookup (char t-char) en-url-sym)
(char t-char)) t-out -1))
t-out)

; Usage: (url-encode-win "Василий Пупкин")
; Encodes strings for urls (windows-1251)
(define (url-encode-win t-linea)
(set 't-out "")
(dostring (t-char t-linea)
(push (or (lookup (char t-char) en-url-win)
(lookup (char t-char) en-url-sym)
(char t-char)) t-out -1))
t-out)

; Usage: (cyr-win-utf "text in windows-1251 encoding")
; Decodes text from windows-1251 to utf-8
(define (cyr-win-utf linea)
(en-de-byte linea en-win-1251))

; Usage: (cyr-koi-utf "text in KOI8R encoding")
; Decodes text from KOI8R to utf-8
(define (cyr-koi-utf linea)
(en-de-byte linea en-koi8r))

; Usage: (cyr-utf-win "text in utf-8 encoding")
; Decodes text from utf-8 to win-1251
(define (cyr-utf-win linea)
(let (linea (cortar linea) o-linea "" f-byte nil)
(while (set 'f-byte (pop linea))
(if (or (= f-byte 208) (= f-byte 209))
(push (or (lookup (+ (mul f-byte 256) (or (pop linea) 0)) en-utf-8) "?") o-linea -1)
(if (= f-byte 226)
(push (or
(lookup (+ 14811136 (mul (or (pop linea) 0) 256) (or (pop linea) 0)) en-utf-8)
"?") o-linea -1)
(push (char f-byte) o-linea -1))))
o-linea))

; Functions commented below will be useful for adding another charsets,
; like CP866 or ISO-8859-5 or whatever
;
; Encodes one russian utf char to url-ready form
;(define (url-encode-char t-char)
; (append
; "%" (format "%X" ((unpack "b b" t-char) 0))
; "%" (format "%X" ((unpack "b b" t-char) 1))))
;
;(set 'url-charset '())
;(dolist (x cyr-alpabet)
; (push (append (list x) (list (url-encode-char x))) url-charset -1))
;
; Types list ready to insert to the code
;(set 'counter 2)
;(dolist (x en-win-1251)
; (print "(\"" (x 1) "\" \"%" (format "%X" (x 0)) "\") ")
; (if (= counter 6)
; (begin (print "\n ") (set 'counter 0)))
; (set 'counter (+ counter 1)))

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

rand в newLISP + Windows (RAND_MAX)

Где-то в недрах исходников newLISP есть константа RAND_MAX. Для Windows она равна 32767, для Tru64Unix , соответственно, 2147483647. Это, что как, (- (pow 2 31) 1).

Из-за этого в Windows функция rand физически не может сгенерить больше чем 32768 разных случайных чисел. Проверить это довольно просто:

> (length (unique (rand 1000000 1000000)))
32767


Что делать, если нам таки нужно больше случайных чисел?

Лично я сделал функцию (rand_64):

(define (rand_64 top num)
(map (fn (x y) (/ (* (+ (* x 32768) y) top) 1073741824))
(rand 32768 num) (rand 32768 num)))


Если я правильно всё рассчитал (в чём я ни разу не уверен) она умеет генерить уже не 32 тысячи, а примерно миллиард разных случайных чисел.

Сократить список

Задача из олимпиады для детей.

Получив на входе "1,3,4,5,6,7,8,10,12,16,17,20,21,22,23,24" надо отдать на выходе "1,3-8,10,12,16-17,20-24"

Вот моё решение:
(define (pack-2 n n1)
(if (= (- (int n1) (int ((parse (-7 n) "[-,]" 0) -1))) 1)
(append n "-" n1)
(append n "," n1)))

(define (compact in)
(replace "(-([^,]+-)+)" (apply pack-2 (parse in ",") 2) "-" 0))


Всё бы ничего, только скорость не радует. На последовательности в 100 000 символов программа думает аж четыре минуты. А надо миллион, да...

Оптимизировал немного код и свёл его в одну функцию, убрал медленный apply/reduce:

(define (compact in)
(replace "(-([^,]+-)+)"
(replace "([0-9]+),(?=([0-9]+))" in
(if (= (- (int $2) (int $1)) 1) (append $1 "-") $it)
0) "-" 0))


Время обсчёта 100 000 — 0,7 секунды. Время на 1М — 7 секунд. Время на 10М — 66 секунд. Задача выполнена, не?

четверг, 25 марта 2010 г.

Сумма цифр в числе

Прочёл ещё одну историю из IT-happens. Там школьникам поставили задачу подсчитать сумму цифр в числе. Хитропопый школьник заставил пользователя вводить число по одной цифре, а потом сложил все введённые цифры.

Я попробовал повторить подвиг школьника на newLISP. Программа, как и предполагал, уложилась в полстрочки:

(define (sum-digit x) (apply + (map int (explode (string x)))))

bind, sym, save and load

Допустим, мы хотим хранить настройки нашей программы в файле, считывать их оттуда и изменять. Как и что делать:

1. Сохранение настроек

> (set 'params '((var1 1) (var2 2) (var3 3)))
((var1 1) (var2 2) (var3 3))
> (save "params.lsp" 'params)
true


2. Загрузка настроек

> (load "params.lsp")
((var1 1) (var2 2) (var3 3))
> (bind params)
3
> var1
1


Команда bind, грубо говоря, выполняет над элементам списка типа (a b) операцию (set 'a b).

3. Изменение настроек, если имя переменной известно

> (pop-assoc 'var1 params)
(var1 1)
> (push '(var1 5) params)
((var1 5) (var2 2) (var3 3))


4. Изменение настроек, если имя переменной приходит в другой переменной

> (set 'text-var "var1" 'text-value "7")
"7"
> (pop-assoc (sym text-var) params)
(var1 5)
> (push (list (sym text-var) (int text-value)) params)
((var1 7) (var2 2) (var3 3))


Оператор sym преобразует строку "x" в переменную 'x.

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

smtp (AUTH PLAIN)

Пример короткой беседы через телнет с одним моим знакомым почтовым сервером (пароль и логин пишутся в base64)

telnet smtp.testmail.ru 25
Trying 77.222.41.7...
Connected to smtp.testmail.ru.
Escape character is '^]'.
220 smtp.testmail.ru ESMTP
EHLO robot // Здесь, вероятно, должно быть robot.domain.ru
250-smtp.testmail.ru Hello robot [204.200.222.136]
250-SIZE 26214400
250-PIPELINING
250-AUTH LOGIN PLAIN
250-STARTTLS
250 HELP
AUTH LOGIN
334 VXNlcm5hbWU6 // "Username:"
cm9ib3Q= // robot
334 UGFzc3dvcmQ6 // "Password:"
MTIzNDU= // 12345
235 Authentication succeeded
MAIL FROM: // В пришедшем письме "FROM" было пусто
250 OK
RCPT TO:
250 Accepted
DATA
354 Enter message, ending with "." on a line by itself
Hello, robot!
Привет, робот!
.
250 OK id=1NuY0E-0005EH-QE
quit
221 smtp.testmail.ru closing connection
Connection closed by foreign host.

Ubuntu 10 + Java

В Ubuntu 10 мне почему-то не удалось поставить стандартную Java от Sun. Зато замечательно сработала установка некой "открытой Java":

sudo apt-get install openjdk-6-jdk


Не знаю как другие приложения, а newLISP запустился на ней замечательно.

Клавиша Микрософт плюс шорткаты в Убунту

HowTo: Как запускать в Убунту Writer по нажатию Флаг+W и Calc по нажатию Флаг+X.

1. Система -- Параметры -- Клавиатура -- Раскладки -- Параметры раскладки -- клавиши Alt/Win -- Hyper соответствует клавишам Win
2. Система -- Параметры -- Комбинации клавиш клавиатуры -- управление окнами -- скрыть все окна и активировать рабочий стол -- Miscrosoft+D
3. sudo apt-get install compizconfig-settings-manager
4. ccsm -- General options -- commands -- key bindings -- Run command 0 -- изменить на W.
5. ccsm -- General options -- commands -- key bindings -- Run command 1 -- изменить на X.
6. ccsm -- General options -- commands -- commands -- Run command 0 -- поставить "soffice -writer"
7. ccsm -- General options -- commands -- commands -- Run command 1 -- поставить "soffice -calc"
8. ?????
9. Profit

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

Простые числа

Прочёл сейчас в IT happens историю, про мальчика, который решил за 10 минут задачу "написать программу, которая печатает простые числа от 1 до 100". Его программа выглядела так:

10 CLS
20 PRINT "2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97"
30 END


А вот как выглядит моя "десятиминутная" программа, показывающая превосходство newLISP над другими высокоуровневыми языками программирования:

(define (prime? x) (= (first (factor x)) x))
(println (filter prime? (sequence 2 100)))


Забавно, что открыв мануал на странице с описанием функции factor, я наткнулся на готовое решение, написанное, вероятно, самим Лутцем:

(define (primes n , p)
(dotimes (e n)
(if (= (length (factor e)) 1)
(push e p -1))) p)
(print (primes 100))


Решение Лутца, что характерно, работает на 20% быстрее быстрее.

> (time (filter prime? (sequence 2 1000000)))
2203,125
> (time (primes 1000000))
1796,875

Стандартные лог-файлы в Linux

Вот список стандартных лог-файлов в Linux (источник):

/var/log/message: General message and system related stuff
/var/log/auth.log: Authenication logs
/var/log/kern.log: Kernel logs
/var/log/cron.log: Crond logs (cron job)
/var/log/maillog: Mail server logs
/var/log/qmail/ : Qmail log directory (more files inside this directory)
/var/log/httpd/: Apache access and error logs directory
/var/log/lighttpd: Lighttpd access and error logs directory
/var/log/boot.log : System boot log
/var/log/mysqld.log: MySQL database server log file
/var/log/secure: Authentication log
/var/log/utmp or /var/log/wtmp : Login records file
/var/log/yum.log: Yum log files


Надо отметить, что в моей Ubuntu 8.10 присутствуют не все файлы, а кое-какие логи лежат в других местах. Ещё там же предлагаются команды для просмотра лог-файлов:

# tail -f /var/log/messages


Тэйл выводит последние 10 строк файла (можно изменить, набрав tail -20 filename.log). Ключ -f означает "следить за файлом". Если лог меняется, это отображается в реальном времени.

# less /var/log/messages
# more -f /var/log/messages


less и more -- утилиты для просмотра файла с прокруткой. Пробел -- на страницу вниз. Выход -- Q. Ключ -f в more означает "считать только логические строки". Что это значит, я не понял.

newLISP: запись/чтение ini-файлов

; Экранирует строку, помещая в "" и защищая спецсимволы слешами
(define (gnirts-lave x)
(1 -1 (string (list x))))

; Записывает список в .ini-файл
(define (list-ini fl lst)
(write-file fl (join
(map (fn (x) (join (map gnirts-lave x) " ")) lst) "\n")))

; Читает список из .ini-файла
(define (ini-list fl)
(map (fn (x) (map eval-string
(select (regex "([^ ]+) (.+)" x) 3 6)))
(clean null? (parse (or (read-file fl) "") "\n"))))

; Обновить строку в .ini-файле
; Usage: (update-ini "april.ini" "days" "30")
(define (update-ini fl v d)
(let (lst (ini-list fl))
(pop-assoc v lst)
(push (list v d) lst -1)
(list-ini fl lst)))


Пример ini-файла:

"path" "/var/www/main"
"len" "Салют пионерам"
"score" "Special <>\"'\"' symbols"


Примечания:

1. Пустые строки оставлять можно.
2. Комментариями (#) не заморачивался.
3. Если файла нет, вернёт пустой список/создаст файл

newLISP: имена переменных в виде текстовых строк

Нам выдают передают имя переменной в виде текстовой строки. Например:

(set 'mylist '(1 2 3 4 5))
(set 'var "mylist")


Задача. Передать эту переменную в функцию.

Решение 1.

(define (add-list x) (apply + (eval x)))

(add-list (sym var))


Решение 2.

(define (add-list-1 x) (eval-string (append "(apply + " x ")")))

(add-list-1 var)

Base64 на JavaScript

А вот цельнотянутый из webtoolkit.info код для Base64 с моим небольшим инородным вкраплением в виде Base62 кодировки.

/**
*
* Base64 encode / decode
* http://www.webtoolkit.info/
*
**/

//
// Кодирует в формат, удобный для пересылки через GET- и POST- запросы.
// Base62.encode('Смайлик (.Y.)'); => '0KHQvNCw0LnQu9C40LogKC5ZLik_e'
// Base62.decode('0KHQvNCw0LnQu9C40LogKC5ZLik_e'); => 'Смайлик (.Y.)'
//
var Base62 = {

// Кодирует данные в букво-цифро-подчёркивание
encode : function (input) {
return Base64.encode(input).replace(/\//g, "_s").replace(/\+/g, "_p").replace(/=/g, "_e");
},

// Раскодирует данные из букво-цифро-подчёркивания
decode : function (input) {
return Base64.decode(input.replace(/_s/g, "/").replace(/_p/g, "+").replace(/_e/g, "="));
}
}

var Base64 = {

// private property
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

// public method for encoding
encode : function (input) {
var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0;

input = Base64._utf8_encode(input);

while (i < input.length) {

chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);

enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;

if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}

output = output +
this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

}

return output;
},

// public method for decoding
decode : function (input) {
var output = "";
var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4;
var i = 0;

input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

while (i < input.length) {

enc1 = this._keyStr.indexOf(input.charAt(i++));
enc2 = this._keyStr.indexOf(input.charAt(i++));
enc3 = this._keyStr.indexOf(input.charAt(i++));
enc4 = this._keyStr.indexOf(input.charAt(i++));

chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;

output = output + String.fromCharCode(chr1);

if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}

}

output = Base64._utf8_decode(output);

return output;

},

// private method for UTF-8 encoding
_utf8_encode : function (string) {
string = string.replace(/\r\n/g,"\n");
var utftext = "";

for (var n = 0; n < string.length; n++) {

var c = string.charCodeAt(n);

if (c < 128) {
utftext += String.fromCharCode(c);
}
else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
}
else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}

}

return utftext;
},

// private method for UTF-8 decoding
_utf8_decode : function (utftext) {
var string = "";
var i = 0;
var c = c1 = c2 = 0;

while ( i < utftext.length ) {

c = utftext.charCodeAt(i);

if (c < 128) {
string += String.fromCharCode(c);
i++;
}
else if((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
}
else {
c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}

}

return string;
}

}

Перенаправление поддомена

Как перенаправить поддомен сайта на другой IP адрес.

1. Заходим на свой хостинг, на страницу управления DNS.
2. Добавляем запись вида forum A 204.200.222.136
3. Теперь адрес forum.mysite.ru резольвится как 204.200.222.136

понедельник, 22 марта 2010 г.

JavaScript онлайн-отладчик

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

<html>
<head>
</head>

<body>
<textarea style="width: 1000; height: 600; border: 1px solid black;" id="script"></textarea>
<br><br>
<button style="width: 1000;" onclick="eval(getElementById('script').value)">Go!</button>
</body>
</html>

Base62 (newLISP)

newLISP вариант:

; Кодирует в буквоцифры и подчёркивание
(define (base62-enc text)
(replace "=" (replace "+" (replace "/" (base64-enc text) "_s") "_p") "_e"))

; Раскодирует из буквоцифр и подчёркивания
(define (base62-dec text)
(base64-dec (replace "_e" (replace "_p" (replace "_s" text "/") "+") "=")))


JavaScript вариант:

var Base62 = {

// Кодирует данные в букво-цифро-подчёркивание
encode : function (input) {
return Base64.encode(input).replace(/\//g, "_s").replace(/\+/g, "_p").replace(/=/g, "_e");
},

// Раскодирует данные из букво-цифро-подчёркивания
decode : function (input) {
return Base64.decode(input.replace(/_s/g, "/").replace(/_p/g, "+").replace(/_e/g, "="));
}
}

Javascript: передать данные в скрипт

Как передать данные в файл, не прогоняя пользователя со страницы?

Пункт первый. Делаем невидимый iframe:

<iframe style='display: none' id='fieldPsFrm' src='save.cgi' width='0' height='0' align='left'></iframe>

Пункт второй. Изменяем его location на нужный:

document.getElementById('fieldPsFrm').contentWindow.location = 'save.cgi?test=test1&test2=test3';

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

1. Вообще, наверное, надо бы делать эту хрень через AJAX.
2. Надо будет запилить отдельную функцию, чтобы передавала параметры сама.

(time) и newLISP

Функция time, оказывается, считает в Linux чуть ли не микросекунды, а вот в Windows минимальный квант времени -- 1/64 секунды (15.625 ms).

Ещё в time есть вызванный переполнением int32 баг: 0x7FFFFFFF микросекунд -- это примерно 35 минут. Поэтому интервалы длиннее 35 минут считаются неправильно.

суббота, 20 марта 2010 г.

Windows: много файлов в одной папке

Решил из любопытства повторить вот этот опыт с максимальным числом открытых файлов в Windows.

Создал скрипт, начал генерить файлы:

(module "crypto.lsp")
(make-dir "c:/tmp/testdir")
(change-dir "c:/tmp/testdir")

(define (rand-2k)
(join (map (fn (x) (append (string x) ": " (join
(map string (rand 10 50))))) (sequence 0 37)) "\n"))

(time (dotimes (x 10000)
(write-file (append (crypto:md5 (string (random))) ".txt") (rand-2k))))
(time (dotimes (x 90000)
(write-file (append (crypto:md5 (string (random))) ".txt") (rand-2k))))


Первые десять тысяч файлов генерировались 123 секунды. Следующие 90 тысяч файлов -- уже 1615 секунд.

Скорость работы, в целом, меня устроила.

Получить список всех файлов в папке через (directory) —- 0.1 секунды
Отобрать 200 файлов и прочесть их -- те же 0.1 секунды.
Сделать то же самое командой "dir" -- примерно столько же.
Прочесть файл из папки -- 0 миллисекунд (!).

И всё бы ничего, да вот только, блджад, dir показал, что у меня в папке 31 169 файлов! Я вначале даже подумал было, что это предел. Однако краткое расследование показало, что это не предел, во всём виноват генератор случайных чисел.

Вот здесь:
http://www.oszone.ru/1296/
http://forum.searchengines.ru/showthread.php?t=290056

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

Для продолжения эксперимента оставлю как-нибудь компьютер на ночь создавать файлы безо всяких md5, просто по номерам (sequence 1 500000). Если предположить, что скорость не будет меняться значительно, это должно занять несколько часов. Посмотрим, как поведёт себя Windows в равных условиях, а не на детских 32k файлов.

Кстати, del. удалил эти файлы за 20 секунд.

--- Продолжение ---

Видоизменил скрипт, поставил на ночь создаваться 500 тысяч файлов. Дотерпел где-то до 100 тысяч, потом остановил скрипт. Винчестер жужжал так, как будто он сломан, больно было слушать.

На ста тысячах скорость была более-менее, но уже не фонтан.

--- Продолжение ---

Сделал скриптом из-под Ubuntu миллион файлов. Скрипт работал примерно 40 минут, жёсткий диск при этом практически не было слышно. Вроде бы -- та же папка, та же файловая система... Интересно, почему так? Возможно, Ubuntu просто пишет на диск не после каждого файла, а раз в несколько секунд?

После перезагрузки обратно в Windows начал тестировать производительность. Она, сразу скажу, меня не очень порадовала (время newLISP выдаёт в миллисекундах):

> (time (directory "."))
13906,25
> (time (directory "."))
2343,75
> (time (directory "." "7.*"))
2187,5
> (length (directory "." "7.*"))
468559
> (length (directory "." "777.*"))
1999
> (time (map read-file (directory "." "777.*")))
36406,25
> (time (read-file "123_456.txt"))
31,25
> (time (read-file "123_456.txt"))
0
> (time (read-file "234_567.txt"))
15,625
> (time (read-file "345_678.txt"))
15,625


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

36 секунд на прочесть 2 000 файлов -- тоже, в общем, сойдёт. Получается всего лишь 18 миллисекунд на файл.

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

Linux: максимальное количество файлов в папке

Цель эксперимента: определить, насколько комфортно будет ворочаться мой Linux в папке с большим количеством файлов.

Компьютер: Intel(R) Core(TM)2 Duo CPU E7300 @ 2.66GHz
Жёсткий диск: Seagate Barracuda ES SATA 3.0Gb/s 500Gb
Файловая система: wubi/ntfs/ext3 (использует раздел Widnows)
Операционная система: Ubuntu 8.10

Фаза 1. Создание файлов

Так как свободных инодов было всего 640 тысяч, я принял решение сгенерить полмиллиона файлов. Размер файлов был выбран в 2 килобайта.

Вот скрипт (newLISP):

(module "crypto.lsp")
(make-dir "/home/hilo/testdir")
(change-dir "/home/hilo/testdir")

(define (rand-2k)
(join (map (fn (x) (append (string x) ": " (join
(map string (rand 10 50))))) (sequence 0 37)) "\n"))

(time (dotimes (x 500000)
(write-file (append (crypto:md5 (string (random))) ".txt") (rand-2k))))


Время генерации файлов: 17 минут.

Фаза 2. Тест производительности

; Найти файлы по маске
(directory "." "7.*") ; 0,5с.

; Получить список вообще всех файлов
(directory ".") ; те же 0,5с.

; Отобрать 200 файлов и прочесть их из папки в память:
(map (fn (x) (read-file x)) (directory "." "6666.*")) ; Две секунды

; Прочесть один файл с известным именем:
(read-file "7777cc087494c9e6e474ac9fe6c01dfc.txt") ; 0,013 ms

; Прочесть файл из папки, в которой лежит всего несколько файлов:
(time (read-file "../ffec6ba54ea01fd951dc2433334abd0c.txt")) ; 0,014 ms

Вывод 1: скорость чтения файлов никак не зависит от того, сколько у них соседей в папке.
Вывод 2: файлы читаются мгновенно.
Вывод 3: непонятно, почему чтение 200 предварительно отобранных файлов занимает две секунды -- должно занимать полсекунды. Надо полагать, если сделать отбор имён, подождать две секунды, а потом прочесть их -- чтение займёт не полторы секунды, а несколько миллисекунд.

Фаза 3. Работа со встроенными командами Linux

3.1. Поиск через grep
grep -l "777" 5555* # 3 cекунды

3.2. ls
ls testdir > test.txt # 3 секунды (создался файл на 18 Мb)

3.3. Подсказка имени. Нажатие "Tab" для подсказки подвесило систему примерно на одну минуту, потом всё отвисло.

3.4. rm
rm * вылетел через 40 секунд с ошибкой:
"bash: /bin/rm: Слишком длинный список аргументов."

Выводы. Встроенные средства справляются с 500 тысячами файлов херовато. Самое разумное: сделать сначала ls в файл, а потом работать уже с этим файлом.

Фаза 4. Удаление файлов

(map delete-file (directory ".")) ; две минуты, под бодрое шуршание диска.

На начало эксперимента было свободно 641373 инодов из 856800. После окончания эксперимента свободных инодов стало 641279. Куда зажевались примерно 100 инодов -- неясно. Вероятно, на временные файлы операционной системы и временные файлы newLISP.

Общий вывод. Для серьёзной нагрузки 500 000 файлов -- это довольно натужно. Я бы предпочёл иметь запас по скорости раз в десять от демонстрируемого.

Впрочем, сначала я собираюсь обновить систему до 9.10 и посмотреть, насколько бодро пойдёт работа со встроенной файловой системой.

--- Продолжение ---

Проапгрейдился до 9.10. Файловая система ext4, созданная непосредственно на диске, без всяких промежуточных ntfs. Также немного упростил скрипт:

(make-dir "/tmp/testdir")
(change-dir "/tmp/testdir")

(define (string-append) (apply append (map string (args))))

(define (rand-2k x)
(write-file (string-append x ".txt")
(join (map (fn (x) (append (string x) ": " (join
(map string (rand 10 50))))) (sequence 0 37)) "\n")))

(define (gen-1k y)
(dotimes (x 1000) (rand-2k (string-append y "_" x)))
(println y))

(time (map gen-1k (sequence 0 999)))


Миллион файлов на этот раз создавался 32 минуты. В общем, практически столько же, сколько и в прошлый раз. А вот тайминг (время newLISP сообщает в миллисекундах):

> (time (directory "."))
563,001
> (time (directory "." "666.*"))
572,296 ; Это полсекунды
> (length (directory "."))
1000002
> (time (map read-file (directory "." "666.*")))
9913,617 ; 5 миллисекунд на файл
> (time (map read-file (directory "." "668_.*")))
1186,284 ; 1 миллисекунда на файл


Как видно, скорость поиска в директории увеличилась по сравнению с ntfs в два раза, а вот скорость чтения файлов почему-то сильно скачет. В дальнейших экспериментах скорость чтения одного файла составляла 10-50 миллисекунд на первый заход и примерно 0,02 миллисекунды на чтение кэшированного файла. Файлы, напомню, имеют размер в два килобайта.

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

Удаление заняло примерно полчаса:
(map delete-file (directory "."))


Затем тем же скриптом я успешно нагенерил минут за 40 миллион файлов для Windows:
http://hilocomod.blogspot.com/2010/03/windows.html

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

Javascript rollover

А вот тот самый маленький ролловер, ради которого и затевался робот на Script-Fu

<!-- Кнопки -->
<style type="text/css">
/* Общие для всех кнопок */
.bcm a { display: block; height: 52px; float: left; position: absolute; top: 0px; }
.bcm a:hover { background-position: 0px -52px; }
a:focus { outline:0; }

/* По каждой кнопочке отдельно */
.b_about a { background-image: url('about.png'); width: 97px; left: 0px; }
.b_reg a { background-image: url('reg.png'); width: 190px; left: 103px; }
</style>

<div style="width: 1024; margin-bottom: 10; position: relative;">
<div class='bcm b_about'><a href='http://test.none' target="_blank"> </a></div>
<div class='bcm b_reg'><a href="javascript:Test('test');"> </a></div>
</div>


Примечания.

1. Вставлять ширину каждой кнопки в style не получилось: важно, что это именно ссылка и после .b_reg идёт a.

2. a:focus { outline:0; } нужен, чтобы после нажатия на див не оставалось мерзкой красной рамочки.

3. Сделав кнопки первый раз я обнаружил, что высота в 54 выбрана мной опрометчиво: надо 52. Тут-то я и порадовался, что смастерил себе робота, а не начал ресайзить/клеить дедовским способом, руками.

Узнать размеры PNG-файла

Функция на newLISP.

; Возвращает размеры png-файла (ширину и высоту)
(define (get-png-size file-name)
(map eval-string (map (curry append "0x")
(select (regex {49484452....(....)....(....)}
(join (map (curry format "%02X")
(unpack (dup "b" 100) (read-file file-name))))) 3 6))))

Script-Fu: resize, combine, vectors, regexp

Текущая библиотека разных моих мелких функций на Скрипт-Фу. Очень жаль, что Блогспот не даёт прикладывать файлы в виде файлов целиком.

;
; Расширения Script-Fu
;

; Возвращает список файлов из dir
; Пример: (hl-file-list "/tmp")
(define (hl-file-list dir)
(cadr (file-glob (string-append dir "/*") 0)))

; Загрузить файл jpg
; Пример: (hl-jpeg-load "/tmp/butt/video1.jpg")
; Возвращает: image
(define (hl-jpeg-load filename)
(car (file-jpeg-load RUN-NONINTERACTIVE filename filename)))

; Склеить вертикально два одинаковых по размеру файла jpg в один
(define (hl-jpeg-vcombine file1 file2 file3)
(let* (
(img1 (hl-jpeg-load file1))
(img2 (hl-jpeg-load file2))
(h-height (car (gimp-image-height img1)))
(f-width (car (gimp-image-width img1)))
; Создадим "подложку"
(img (car (gimp-image-new f-width (* h-height 2) RGB)))
; Обозначим локальные переменные
(dra) (dra-copy))
; Снимем копию дравабле первого файла и добавим её
; в верхнюю часть подложки
(set! dra-copy (car (gimp-image-get-active-layer img1)))
(set! dra-copy (car (gimp-layer-new-from-drawable dra-copy img)))
(gimp-image-add-layer img dra-copy -1)
; Добавим копию второго имаге вниз подложки
(set! dra-copy (car (gimp-image-get-active-layer img2)))
(set! dra-copy (car (gimp-layer-new-from-drawable dra-copy img)))
(gimp-image-add-layer img dra-copy -1)
(gimp-layer-set-offsets dra-copy 0 h-height)
; Сохраним изображение в file3
(set! dra (car (gimp-image-merge-visible-layers img 0)))
(file-png-save 1 img dra file3 file3 0 9 0 0 0 1 1)
; Подчистим за собой мусор
(hl-delete-images (list img img1 img2))))

; Удаляет загруженные изображения из памяти
(define (hl-delete-images image-list)
(map (lambda (x) (gimp-image-delete x)) image-list))

; Уменьшить размеры jpg. new-len: новая длина стороны
; flag: "w" или "h", смотря по какой стороне ориентироваться с размерами
(define (hl-resize-jpg file-in file-out new-len flag)
(let* (
(img (car (file-jpeg-load 1 file-in file-in)))
(dra (car (gimp-image-get-active-layer img)))
(old-h (car (gimp-image-height img)))
(old-w (car (gimp-image-width img)))
(new-h) (new-w))
; Вычислим новые размеры картинки
(if (string=? flag "w")
(begin
(set! new-w new-len)
(set! new-h (* old-h (/ new-w old-w))))
(begin
(set! new-h new-len)
(set! new-w (* old-w (/ new-h old-h)))))
; Изменим размеры jpg
(gimp-drawable-transform-scale dra 0 0 new-w new-h 0 3 FALSE 3 2)
(gimp-image-crop img new-w new-h 0 0)
; Сохраним картинку
(file-jpeg-save 1 img dra file-out file-out .95 0 0 0 " " 0 1 0 1)
; Очистим мусор
(gimp-image-delete img)))

; Создать картинку размерами x y
(define (hl-make-image x y)
(let* (
(img (car (gimp-image-new x y RGB)))
(dra (car (gimp-layer-new img x y RGB-IMAGE "Background" 100 NORMAL-MODE))))
(gimp-image-add-layer img dra -1)
(gimp-edit-clear dra)
(list img dra)))

; Выбрать область с закруглёнными краями
(define (hl-select-round img x y ox oy)
(gimp-rect-select img x y ox oy REPLACE FALSE 0)
(script-fu-selection-rounded-rectangle img FALSE 50 FALSE))

;
; Расширения TinyScheme
;

; Отрезает последние num символов от строки
(define (chop str num)
(substring str 0 (- (string-length str) num)))

; Проверяет, принадлежит ли значение списку
; Пример: (in-list? 3 '(1 2 3 4 5 6))
(define (in-list? itm lst)
(let* (
(found 0))
(for-each (lambda (x)
(if ((if (string? x) string=? =) x itm) (set! found (+ found 1))))
lst)
(if (> found 0) #t #f)))

; Возвращает уникальные значения списка
(define (unique lst)
(let* (
(out-list '()))
(for-each (lambda (x)
(if (in-list? x out-list) #f (set! out-list (append out-list (list x)))))
lst)
out-list))

; Вырезать имя файла из полного пути
; (hl-filename "/tmp/test.jpg") -> "test.jpg"
(define (hl-filename path)
(let* (
(f-len (string-length path))
(s-len 1))
(while (and
(<= s-len f-len)
(not (re-match "/" (substring path (- f-len s-len)))))
(set! s-len (+ s-len 1)))
(substring path (+ (- f-len s-len) 1))))

;
; Рабочие процедуры
;

; Загрузить кучу кнопок вида "test1.jpg" и "test2.jpg", изменить их
; размер до 54 по вертикали, склеить их попарно в файлы для rollover
; кнопок на сайте.
(define (hl-resize-buttons)
(let* (
(in-dir "/home/oleg/gimp/indir")
(out-dir "/home/oleg/gimp/outdir")
; В button-list лежит список файлов типа '("test", "about", "home")
(button-list (unique (map hl-filename (map
(lambda (x) (chop x 5)) (hl-file-list in-dir))))))
(for-each (lambda (x)
; Делаем ресайз кнопок
(hl-resize-jpg (string-append in-dir "/" x "1.jpg")
(string-append out-dir "/" x "1.jpg") 54 "h")
(hl-resize-jpg (string-append in-dir "/" x "2.jpg")
(string-append out-dir "/" x "2.jpg") 54 "h")
(hl-jpeg-vcombine
(string-append out-dir "/" x "1.jpg")
(string-append out-dir "/" x "2.jpg")
(string-append out-dir "/" x ".png")))
button-list)))

; Сделать кнопку в стиле веб-два-ноль с заданным текстом
; (hl-button "Нажмите здесь!" "Courier" "sample.png" '(224 224 255) '(80 80 255) '(255 255 255))
; Цвета (1-2-3): фон, цвет кнопки, цвет текста
(define (hl-button text font name color-1 color-2 color-3)
(let* (
(tx (car (gimp-text-get-extents-fontname text 26 0 font)))
(ty (cadr (gimp-text-get-extents-fontname text 26 0 font)))
(image (nl-make-image (+ tx 44) 44)))

; Очищаем фон, ставим бэкграунд
(gimp-context-set-foreground color-1)
(gimp-edit-fill (cadr image) 0)
(gimp-context-set-background color-1)

; Заполняем большой прямоугольник
(hl-select-round (car image) 4 4 (+ tx 30) ty)
(gimp-context-set-foreground color-2)
(gimp-edit-fill (cadr image) 0)

; Отбрасываем тень
(script-fu-drop-shadow (car image) (cadr image) 4 4 6 color-2 80 0)

; Заполняем малый прямоугольник градиентом
(hl-select-round (car image) 6 6 (+ tx 26) (- ty 12))
(gimp-blend (cadr image) FG-BG-RGB-MODE NORMAL-MODE GRADIENT-LINEAR
100 0 REPEAT-NONE FALSE FALSE 0 0 FALSE 0 (- ty 12) 0 -26)

; Добавляем текст
(gimp-context-set-foreground color-3)
(gimp-text-fontname (car image) (cadr image) 19 4 text 0 TRUE 26 1 font)

; Сохраняем файл
(gimp-image-merge-visible-layers (car image) 0)
(file-png-save 1
(car image)
(car (gimp-image-get-active-layer (car image)))
name
name
0 9 0 0 0 1 1)

; Удаляем изображение из памяти
(gimp-image-delete (car image))))

; Нарисовать карандаш 24х24 пиксела
; Работаем с векторами и кривыми Безье. Сделав всё в
; векторном виде, закрашиваем и изменяем размер с 240х240 до 24х24
; Получается а-ля-иконка
(define (hl-pencil)
(let* (
(img (hl-make-image 240 240))
(image (car img))
(layer (cadr img))
(pbody 0)
(stro 0)
(nose 0))

; Очищаем фон
(gimp-context-set-foreground '(244 244 255))
(gimp-edit-fill layer 0)

; Задаём цвет карандаша
(gimp-context-set-foreground '(0 0 255))

; Новые вектора
(set! pbody (car (gimp-vectors-new image "pencil")))
(gimp-image-add-vectors image pbody -1)

; Рисуем туловище карандаша
(set! stro (car (gimp-vectors-bezier-stroke-new-moveto pbody 180 20)))
(gimp-vectors-bezier-stroke-lineto pbody stro 70 130)
(gimp-vectors-bezier-stroke-conicto pbody stro 105 135 110 170)
(gimp-vectors-bezier-stroke-lineto pbody stro 220 60)
(gimp-vectors-bezier-stroke-conicto pbody stro 215 25 180 20)

; Закрашиваем туловище
(gimp-vectors-to-selection pbody 0 TRUE FALSE 0 0)
(gimp-blend layer FG-BG-RGB-MODE NORMAL-MODE GRADIENT-LINEAR
100 0 REPEAT-NONE FALSE FALSE 0 0 FALSE 125 75 240 240)
(gimp-selection-none image)

; Рисуем носик карандаша
(set! nose (car (gimp-vectors-new image "pencil")))
(gimp-image-add-vectors image nose -1)
(set! stro (car (gimp-vectors-bezier-stroke-new-moveto nose 70 130)))
(gimp-vectors-bezier-stroke-lineto nose stro 20 220)
(gimp-vectors-bezier-stroke-lineto nose stro 110 170)
(gimp-vectors-bezier-stroke-conicto nose stro 105 135 70 130)

; Закрашиваем носик
(gimp-vectors-to-selection nose 0 TRUE FALSE 0 0)
(gimp-blend layer FG-BG-RGB-MODE NORMAL-MODE GRADIENT-LINEAR
100 0 REPEAT-NONE FALSE FALSE 0 0 FALSE -30 30 170 230)
(gimp-selection-none image)

; Изменяем размер
(gimp-image-scale image 24 24)

(gimp-display-new image)))

;
; Отладочные участки
;

; Показываем изображение
; (gimp-display-new img)

; Список шрифтов (можно напамить на него hl-button)
; (gimp-fonts-get-list "")

; Отштриховываем вектора
; (gimp-context-set-brush "Circle (03)")
; (gimp-context-set-foreground '(0 0 255))
; (gimp-edit-stroke-vectors layer nose)

Grep: найти в файлах

Найти строку в нескольких файлах:
grep -lir "TODO" request*

-l: выводить только имена файлов с совпадениями
-r: искать рекурсивно
-i: игнорировать case

Найти строку в одном файле:
grep "video1.jpg" filelist.txt

четверг, 18 марта 2010 г.

Команды/константы Script-Fu

Более-менее полный список команд найти несложно, если погуглить что-нибудь типа "file-bmp-load" "file-bz2-load".

Ещё несколько забавных команд лежит в файле .gimprc (не пользовательском, а в основном, в /etc).

Польза от все этого непотребства, впрочем, сомнительна.

Единственное, что я пока нашёл ценного, две константы:

RUN-INTERACTIVE (0)
RUN-NONINTERACTIVE (1)

Кое-что любопытное (скрипт массовой обработки фотографий):
http://svvord.livejournal.com/882.html

Надо бы, наверное, сказать тому товарищу, что вместо (define) лучше назначать переменные через let*...

dir или ls в Script-Fu

Сбылась мечта бесчисленных поколений. Я разобрался как делать "ls" в Script-Fu.

> (file-glob "c:\\tmp\\Тест\\*" 0)
(7 ("c:\\tmp\\Тест\\test.bmp" "c:\\tmp\\Тест\\test.dds" "c:\\tmp\\Тест\\test.png" "c:\\tmp\\Тест\\test1.bmp" "c:\\tmp\\Тест\\test1.gif" "c:\\tmp\\Тест\\test2.bmp" "c:\\tmp\\Тест\\tmp.sql"))

> (nth 2 (cadr (file-glob "c:\\tmp\\Тест\\*" 0)))
"c:\\tmp\\Тест\\test.png"

> (file-glob "c:\\tmp\\Тест\\*.bmp" 0)
(3 ("c:\\tmp\\Тест\\test.bmp" "c:\\tmp\\Тест\\test1.bmp" "c:\\tmp\\Тест\\test2.bmp"))


Второй параметр у file-glob -- кодировка возвращаемых имён. В Windows, вроде как, ни на что не влияет.

Отступы в коде внутри html

Вот здесь лежит любопытный обзор "indenting with html":
http://www.blooberry.com/indexdot/html/topics/indent.htm

Похоже, проще всего форматировать текст через тэг <pre>. Внутри тэга все пробелы и переводы строк сохраняются, а вот "<>" нужно заэкранивать через "&lt;" и "&gt;".

Кстати, "lt" расшифровывается как "less than", a gt как "greater than".

(Ещё, конечно, всегда можно настроить скрипт, который будет делать для нас форматирование через CSS).

TinyScheme Manual

Взят из исходников GIMP 2.67

                       TinySCHEME Version 1.38

"Safe if used as prescribed"
-- Philip K. Dick, "Ubik"

This software is open source, covered by a BSD-style license.
Please read accompanying file COPYING.
-------------------------------------------------------------------------------

This Scheme interpreter is based on MiniSCHEME version 0.85k4
(see miniscm.tar.gz in the Scheme Repository)
Original credits in file MiniSCHEMETribute.txt.

D. Souflis (dsouflis@acm.org)

-------------------------------------------------------------------------------
What is TinyScheme?
-------------------

TinyScheme is a lightweight Scheme interpreter that implements as large
a subset of R5RS as was possible without getting very large and
complicated. It is meant to be used as an embedded scripting interpreter
for other programs. As such, it does not offer IDEs or extensive toolkits
although it does sport a small top-level loop, included conditionally.
A lot of functionality in TinyScheme is included conditionally, to allow
developers freedom in balancing features and footprint.

As an embedded interpreter, it allows multiple interpreter states to
coexist in the same program, without any interference between them.
Programmatically, foreign functions in C can be added and values
can be defined in the Scheme environment. Being a quite small program,
it is easy to comprehend, get to grips with, and use.

Known bugs
----------

TinyScheme is known to misbehave when memory is exhausted.


Things that keep missing, or that need fixing
---------------------------------------------

There are no hygienic macros. No rational or
complex numbers. No unwind-protect and call-with-values.

Maybe (a subset of) SLIB will work with TinySCHEME...

Decent debugging facilities are missing. Only tracing is supported
natively.


Scheme Reference
----------------

If something seems to be missing, please refer to the code and
"init.scm", since some are library functions. Refer to the MiniSCHEME
readme as a last resort.

Environments
(interaction-environment)
See R5RS. In TinySCHEME, immutable list of association lists.

(current-environment)
The environment in effect at the time of the call. An example of its
use and its utility can be found in the sample code that implements
packages in "init.scm":

(macro (package form)
`(apply (lambda ()
,@(cdr form)
(current-environment))))

The environment containing the (local) definitions inside the closure
is returned as an immutable value.

(defined? <symbol>) (defined? <symbol> <environment>)
Checks whether the given symbol is defined in the current (or given)
environment.

Symbols
(gensym)
Returns a new interned symbol each time. Will probably move to the
library when string->symbol is implemented.

Directives
(gc)
Performs garbage collection immediatelly.

(gcverbose) (gcverbose <bool>)
The argument (defaulting to #t) controls whether GC produces
visible outcome.

(quit) (quit <num>)
Stops the interpreter and sets the 'retcode' internal field (defaults
to 0). When standalone, 'retcode' is returned as exit code to the OS.

(tracing <num>)
1, turns on tracing. 0 turns it off. (Only when USE_TRACING is 1).

Mathematical functions
Since rationals and complexes are absent, the respective functions
are also missing.
Supported: exp, log, sin, cos, tan, asin, acos, atan, floor, ceiling,
trunc, round and also sqrt and expt when USE_MATH=1.
Number-theoretical quotient, remainder and modulo, gcd, lcm.
Library: exact?, inexact?, odd?, even?, zero?, positive?, negative?,
exact->inexact. inexact->exact is a core function.

Type predicates
boolean?,eof-object?,symbol?,number?,string?,integer?,real?,list?,null?,
char?,port?,input-port?,output-port?,procedure?,pair?,environment?',
vector?. Also closure?, macro?.

Types
Types supported:

Numbers (integers and reals)
Symbols
Pairs
Strings
Characters
Ports
Eof object
Environments
Vectors

Literals
String literals can contain escaped quotes \" as usual, but also
\n, \r, \t, \xDD (hex representations) and \DDD (octal representations).
Note also that it is possible to include literal newlines in string
literals, e.g.

(define s "String with newline here
and here
that can function like a HERE-string")

Character literals contain #\space and #\newline and are supplemented
with #\return and #\tab, with obvious meanings. Hex character
representations are allowed (e.g. #\x20 is #\space).
When USE_ASCII_NAMES is defined, various control characters can be
refered to by their ASCII name.
0 #\nul 17 #\dc1
1 #\soh 18 #\dc2
2 #\stx 19 #\dc3
3 #\etx 20 #\dc4
4 #\eot 21 #\nak
5 #\enq 22 #\syn
6 #\ack 23 #\etv
7 #\bel 24 #\can
8 #\bs 25 #\em
9 #\ht 26 #\sub
10 #\lf 27 #\esc
11 #\vt 28 #\fs
12 #\ff 29 #\gs
13 #\cr 30 #\rs
14 #\so 31 #\us
15 #\si
16 #\dle 127 #\del

Numeric literals support #x #o #b and #d. Flonums are currently read only
in decimal notation. Full grammar will be supported soon.

Quote, quasiquote etc.
As usual.

Immutable values
Immutable pairs cannot be modified by set-car! and set-cdr!.
Immutable strings cannot be modified via string-set!

I/O
As per R5RS, plus String Ports (see below).
current-input-port, current-output-port,
close-input-port, close-output-port, input-port?, output-port?,
open-input-file, open-output-file.
read, write, display, newline, write-char, read-char, peek-char.
char-ready? returns #t only for string ports, because there is no
portable way in stdio to determine if a character is available.
Also open-input-output-file, set-input-port, set-output-port (not R5RS)
Library: call-with-input-file, call-with-output-file,
with-input-from-file, with-output-from-file and
with-input-output-from-to-files, close-port and input-output-port?
(not R5RS).
String Ports: open-input-string, open-output-string, get-output-string,
open-input-output-string. Strings can be used with I/O routines.

Vectors
make-vector, vector, vector-length, vector-ref, vector-set!, list->vector,
vector-fill!, vector->list, vector-equal? (auxiliary function, not R5RS)

Strings
string, make-string, list->string, string-length, string-ref, string-set!,
substring, string->list, string-fill!, string-append, string-copy.
string=?, string<?, string>?, string>?, string<=?, string>=?.
(No string-ci*? yet). string->number, number->string. Also atom->string,
string->atom (not R5RS).

Symbols
symbol->string, string->symbol

Characters
integer->char, char->integer.
char=?, char<?, char>?, char<=?, char>=?.
(No char-ci*?)

Pairs & Lists
cons, car, cdr, list, length, map, for-each, foldr, list-tail,
list-ref, last-pair, reverse, append.
Also member, memq, memv, based on generic-member, assoc, assq, assv
based on generic-assoc.

Streams
head, tail, cons-stream

Control features
Apart from procedure?, also macro? and closure?
map, for-each, force, delay, call-with-current-continuation (or call/cc),
eval, apply. 'Forcing' a value that is not a promise produces the value.
There is no call-with-values, values, nor dynamic-wind. Dynamic-wind in
the presence of continuations would require support from the abstract
machine itself.

Property lists
TinyScheme inherited from MiniScheme property lists for symbols.
put, get.

Dynamically-loaded extensions
(load-extension <filename without extension>)
Loads a DLL declaring foreign procedures.

Esoteric procedures
(oblist)
Returns the oblist, an immutable list of all the symbols.

(macro-expand <form>)
Returns the expanded form of the macro call denoted by the argument

(define-with-return (<procname> <args>...) <body>)
Like plain 'define', but makes the continuation available as 'return'
inside the procedure. Handy for imperative programs.

(new-segment <num>)
Allocates more memory segments.

defined?
See "Environments"

(get-closure-code <closure>)
Gets the code as scheme data.

(make-closure <code> <environment>)
Makes a new closure in the given environment.

Obsolete procedures
(print-width <object>)

Programmer's Reference
----------------------

The interpreter state is initialized with "scheme_init".
Custom memory allocation routines can be installed with an alternate
initialization function: "scheme_init_custom_alloc".
Files can be loaded with "scheme_load_file". Strings containing Scheme
code can be loaded with "scheme_load_string". It is a good idea to
"scheme_load" init.scm before anything else.

External data for keeping external state (of use to foreign functions)
can be installed with "scheme_set_external_data".
Foreign functions are installed with "assign_foreign". Additional
definitions can be added to the interpreter state, with "scheme_define"
(this is the way HTTP header data and HTML form data are passed to the
Scheme script in the Altera SQL Server). If you wish to define the
foreign function in a specific environment (to enhance modularity),
use "assign_foreign_env".

The procedure "scheme_apply0" has been added with persistent scripts in
mind. Persistent scripts are loaded once, and every time they are needed
to produce HTTP output, appropriate data are passed through global
definitions and function "main" is called to do the job. One could
add easily "scheme_apply1" etc.

The interpreter state should be deinitialized with "scheme_deinit".

DLLs containing foreign functions should define a function named
init_<base-name>. E.g. foo.dll should define init_foo, and bar.so
should define init_bar. This function should assign_foreign any foreign
function contained in the DLL.

The first dynamically loaded extension available for TinyScheme is
a regular expression library. Although it's by no means an
established standard, this library is supposed to be installed in
a directory mirroring its name under the TinyScheme location.


Foreign Functions
-----------------

The user can add foreign functions in C. For example, a function
that squares its argument:

pointer square(scheme *sc, pointer args) {
if(args!=sc->NIL) {
if(sc->isnumber(sc->pair_car(args))) {
double v=sc->rvalue(sc->pair_car(args));
return sc->mk_real(sc,v*v);
}
}
return sc->NIL;
}

Foreign functions are now defined as closures:

sc->interface->scheme_define(
sc,
sc->global_env,
sc->interface->mk_symbol(sc,"square"),
sc->interface->mk_foreign_func(sc, square));


Foreign functions can use the external data in the "scheme" struct
to implement any kind of external state.

External data are set with the following function:
void scheme_set_external_data(scheme *sc, void *p);

As of v.1.17, the canonical way for a foreign function in a DLL to
manipulate Scheme data is using the function pointers in sc->interface.

Standalone
----------

Usage: tinyscheme -?
or: tinyscheme [<file1> <file2> ...]
followed by
-1 <file> [<arg1> <arg2> ...]
-c <Scheme commands> [<arg1> <arg2> ...]
assuming that the executable is named tinyscheme.

Use - in the place of a filename to denote stdin.
The -1 flag is meant for #! usage in shell scripts. If you specify
#! /somewhere/tinyscheme -1
then tinyscheme will be called to process the file. For example, the
following script echoes the Scheme list of its arguments.

#! /somewhere/tinyscheme -1
(display *args*)

The -c flag permits execution of arbitrary Scheme code.


Error Handling
--------------

Errors are recovered from without damage. The user can install his
own handler for system errors, by defining *error-hook*. Defining
to '() gives the default behavior, which is equivalent to "error".
USE_ERROR_HOOK must be defined.

A simple exception handling mechanism can be found in "init.scm".
A new syntactic form is introduced:

(catch <expr returned exceptionally>
<expr1> <expr2> ... <exprN>)

"Catch" establishes a scope spanning multiple call-frames
until another "catch" is encountered.

Exceptions are thrown with:

(throw "message")

If used outside a (catch ...), reverts to (error "message").

Example of use:

(define (foo x) (write x) (newline) (/ x 0))

(catch (begin (display "Error!\n") 0)
(write "Before foo ... ")
(foo 5)
(write "After foo"))

The exception mechanism can be used even by system errors, by

(define *error-hook* throw)

which makes use of the error hook described above.

If necessary, the user can devise his own exception mechanism with
tagged exceptions etc.


Reader extensions
-----------------

When encountering an unknown character after '#', the user-specified
procedure *sharp-hook* (if any), is called to read the expression.
This can be used to extend the reader to handle user-defined constants
or whatever. It should be a procedure without arguments, reading from
the current input port (which will be the load-port).


Colon Qualifiers - Packages
---------------------------

When USE_COLON_HOOK=1:
The lexer now recognizes the construction <qualifier>::<symbol> and
transforms it in the following manner (T is the transformation function):

T(<qualifier>::<symbol>) = (*colon-hook* 'T(<symbol>) <qualifier>)

where <qualifier> is a symbol not containing any double-colons.

As the definition is recursive, qualifiers can be nested.
The user can define his own *colon-hook*, to handle qualified names.
By default, "init.scm" defines *colon-hook* as EVAL. Consequently,
the qualifier must denote a Scheme environment, such as one returned
by (interaction-environment). "Init.scm" defines a new syntantic form,
PACKAGE, as a simple example. It is used like this:

(define toto
(package
(define foo 1)
(define bar +)))

foo ==> Error, "foo" undefined
(eval 'foo) ==> Error, "foo" undefined
(eval 'foo toto) ==> 1
toto::foo ==> 1
((eval 'bar toto) 2 (eval 'foo toto)) ==> 3
(toto::bar 2 toto::foo) ==> 3
(eval (bar 2 foo) toto) ==> 3

If the user installs another package infrastructure, he must define
a new 'package' procedure or macro to retain compatibility with supplied
code.

Note: Older versions used ':' as a qualifier. Unfortunately, the use
of ':' as a pseudo-qualifier in existing code (i.e. SLIB) essentially
precludes its use as a real qualifier.

Архив блога