Лицензия Creative Commons
Содержимое блога доступно по лицензии Creative Commons Атрибуция — С сохранением условий
(Attribution-ShareAlike) 3.0 Unported
, если не указано иное.

суббота, 10 января 2015 г.

Создание расширенной версии оператора case с помощью макросов Lisp

Доброго времени суток, случайные и не случайные читатели.

Сегодня хочу рассказать о модификации конструкции case в Scheme для того, чтобы она использовала переданный пользователем предикат для сравнения. Решение основано на макросах... но обо всём по-порядку.

Описание работы case

Во-первых, как работает case ? Несложно догадаться, case является одной из управляющих конструкций, которые выполняют работу по принятию решений (англ. case analysis), выбирая "путь", по которому должна пойти программа, в зависимости от переданного им выражения.

В общем виде, синтаксис case выглядит так:


case ключевое-выражение условие-1 условие-2 ...

Каждое из условий должно содержать список из одного или нескольких элементов данных, за которым должно быть одно или несколько выражений для исполнения:


(case ключевое-выражение
  ((элемент-данных ...) выражение-1 выражение-2 ...)
  ...)

Используя специальный оператор =>, можно вызвать выражение (процедуру) в условии со значением ключевого выражения:


(case ключевое-выражение
  ((элемент-данных ...) => выражение))

В этом случае, значение ключевого выражения будет передано в выражение в качестве параметра:


((lambda (параметр)
   ...)
 значение-ключевого-выражения)

Так же case позволяет использовать else в конце списка условий, для случаев, когда ни одно из условий не было удовлетворено:


(else выражение-1 выражение-2 ...)

Конструкция с => также допустима:


(else => выражение)

Пример использования case:


(case (+ 2 3)
  ((5)
   (display "Привет мир!")
   (newline))
  (else
   (display "С миром что-то пошло не так.")
   (newline)))

Как всё это работает? Сначала case вычисляет первый аргумент (выражение; в данном случае, (+ 2 3)), после этого сравнивает полученный результат с каждым условием. Условие состоит из списка значений для сравнения и выражения (последовательности выражений) к выполнению при совпадении результата с одним из элементов данных. Сравнение производится через предикат eqv?.

Попробуем выполнить выражение выше (-| показывает побочный эффект от вычисления выражения, в данном случае, печать строки):


(case (+ 2 3)
  ((5)
   (display "Привет мир!")
   (newline))
  (else
   (display "С миром что-то пошло не так.")
   (newline)))
-| Привет мир!

Как и ожидалось, выводится строка "Привет мир!". Но что делать, если нам нужно сравнить результат вычисления ключевого выражения со, скажем, списоком строк? Попробуем это сделать обычным case.

Использование case для перебора строк

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


(case *цвет*
  (("оранжевый")
   (display "апельсин")
   (newline))
  (("красный" "зелёный")
   (display "яблоко")
   (newline))
  (else
   (display "банан")
   (newline)))

Разумеется, компьютеры (пока) не умеют удивляться, когда им подсовывают для исполнения подобный код, однако в остальном мы уловили суть. Переменная *цвет* может быть задана до выполнения case следующим образом:

(define *цвет* "оранжевый")

Но возникает проблема -- мы всегда получаем "банан", поскольку предикат eqv? возвращает "неожиданный" результат:


(eqv? "яблоко" "яблоко")
=> #f

Что же это? Результатом булева выражения оказалась "ложь" (#f), несмотря на то, что с нашей точки зрения строки идентичны! Посмотрим, в чём дело.

С точки зрения реального мира подобный ответ корректен, так как переданные eqv? яблоки (то есть, строки) являются разными объектами (хранятся в разных областях памяти), хотя и выглядят одинаково. В реальном мире два яблока, как бы они не были внешне похожи, являются разными объектами.

Если же мы объявим переменную, в которую сохраним яблоко (то есть, строку "яблоко", конечно же), и объявим ещё одну переменную, присвоив ей значение первой переменной, то при сравнении этих переменных мы получим истину (#t):


(define *фрукт1* "яблоко")
(define *фрукт2* *фрукт1*)
(eqv? *фрукт1* *фрукт2*)
=> #t

Та-да! Эти переменные равны, так как ссылаются на один и тот же фрукт (одну и ту же область памяти, которая хранит строку "яблоко").

Поиск правильного инструмента сравнения

Очевидно, что eqv? не подходит в данном случае для сравнения строк, поскольку по мнению eqv? две строки различаются, если являются разными объектами, даже если они состоят из одинакового набора символов. Мы должны использовать правильный способ сравнения объектов, чтобы получить желаемый результат. В данном случае, строки должны сравниваться лексикографическим способом, тогда две строки одинаковой длины, с одинаковым набором символов, расположенных в одинаковом порядке, будут идентичны. Это может сделать предикат string=?:


(string=? "яблоко" "яблоко")
=> #t

Как раз то, что нужно. Осталось научить case использовать string=? для сравнения. Это можно сделать, добавив в язык программирования новую конструкцию -- назовём её case-pred. Сложно, разве нет? Оказывается, что не так уж и сложно, когда знаешь лисповые макросы.

Пишем код, который пишет код, который пишет код, который ...

Для реализации case-pred можно использовать другую управляющую конструкцию, cond. То есть, мы объявим наш case-pred таким образом, что после развёртки макроса он будет превращаться в корректную конструкцию cond, которая и будет выполнять всю работу.

Конструкция cond, так же, как и case, состоит из набора условий, но не привязана к конкретному ключевому выражению. В общем виде синтаксис выглядит так:

cond условие-1 условие-2 ...

где каждое из условий представляет собой:

(тест выражение-1 выражение-2 ...)

Выражения вычисляются тогда, когда тест возвращает не-ложь (не #f). Наш "фруктовый" пример кода, переписанный вручную с использованием cond:


(cond
  ((string=? *цвет* "оранжевый")
   (display "апельсин")
   (newline))
  ((or (string=? *цвет* "красный")
       (string=? *цвет* "зелёный"))
   (display "яблоко")
   (newline))
  (else
   (display "банан")
   (newline)))

Теперь осталось написать код, который будет писать код, который будет делать то, что нам нужно.

Что мы хотим получить в итоге, так это макрос case-pred, который принимает первым аргументом предикат, который используется для сравнения результата ключевого выражения со списком условий. В остальном, макрос должен работать так же, как и обычный case.

Макросы во всей красе

Наш первый вариант макроса case-pred использует классический лисповый макрос, объявляемый через define-macro:


(define-macro (case-pred pred key . clauses)
  `(cond
    ,@(map
       (lambda (clause)
         (let ((datum (car clause))
               (exp   (cadr clause)))
           (cond
            ((and (not (list? datum)) (not (eq? datum 'else)))
             (error "Syntax error: expected a list" datum))
            ((eq? datum 'else)
             `(else ,exp))
            ((= (length datum) 1)
             `((,pred ,key ,(car datum)) ,exp))
            (else
             `((or ,@(map (lambda (o) `(,pred ,key ,o))
                          datum)) ,exp)))))
       clauses)))

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

`(cond ,@(map ...)) как раз возвращает нужный список. Как можно прочитать в документации, оператор quasiquote, в краткой форме обозначаемый знаком грависа ("`"), позволяет создать цитированный (англ. quoted) список, части которого могут быть вычислены. Те части, которые должны быть вычислены, помечаются с помощью операторов unquote (в краткой форме -- ",") и unquote-splicing (в краткой форме -- ",@"). unquote вычисляет выражение и вставляет результат в список. unquote-splicing также вычисляет выражение, но подставляет в список элементы того списка, который был получен в результате вычисления "разкавыченного" выражения. Таким образом, ,@(map ...) возвращает список, элементы которого подставляются в другой список -- как тело cond.

Данный макрос не лишён недостатков -- к примеру, не описан оператор =>. Однако в остальном данная версия case-pred работает неплохо.

Но можем ли мы сделать эту запись короче и яснее? Ответ -- можем, используя define-syntax:


(define-syntax case-pred
  (syntax-rules (else)
    ((_ pred key ((datum ...) exp) ...)
     (cond
      ((or (pred key datum) ...) exp) ...))
    ((_ pred key ((datum ...) exp) ... (else else-exp))
     (cond
      ((or (pred key datum) ...) exp) ...
      (else else-exp)))))

В этой версии используется стиль макросов, предоставляемый GNU Guile. syntax-rules создаёт преобразователь синтаксиса, основанный на сопоставлении выражения с шаблоном, и переписывании данного выражения согласно шаблону преобразования.

К примеру, шаблон


    ((_ pred key ((datum ...) exp) ...)
     (cond
      ((or (pred key datum) ...) exp) ...))

совпадает с выражением следующего вида:


(case-pred string=? *цвет*
  (("оранжевый")
   (display "апельсин")
   (newline)))  

А шаблон


    ((_ pred key ((datum ...) exp) ... (else else-exp))
     (cond
      ((or (pred key datum) ...) exp) ...
      (else else-exp)))))

совпадает с


(case-pred string=? *цвет*
  (("оранжевый")
   (display "апельсин")
   (newline))
  (else
   (display "банан")
   (newline)))

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

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


(case-pred string=? *цвет*
  (("оранжевый")
   (display "апельсин")
   (newline))
  (("красный" "зелёный")
   (display "яблоко")
   (newline))
  (else
   (display "банан")
   (newline)))

После развёртки макроса мы получим примерно следующий код (который мы уже видели ранее):


(cond
  ((string=? *цвет* "оранжевый")
   (display "апельсин")
   (newline))
  ((or (string=? *цвет* "красный")
       (string=? *цвет* "зелёный"))
   (display "яблоко")
   (newline))
  (else
   (display "банан")
   (newline)))

Думаю, данная версия макроса в дальнейшем будет претерпевать изменения -- к примеру, можно добавить поддержку => выражений. Но это уже другая история.

- Артём

воскресенье, 1 декабря 2013 г.

Монтирование разделов с IMG-образов дисков, используемых QEMU

Доброго времени суток.

Заметка о том, как монтировать разделы с "сырых" образов дисков (англ. raw images), используемых, в том числе, эмулятором QEMU.

Итак, дано: Образ Debian GNU/Hurd, взятый отсюда. Цель: подмонтировать корневой раздел с образа диска к файловой системе рабочей ОС с помощью mount.

Положим, что мы уже находимся в каталоге с образом диска, а монтировать собираемся первый раздел с него в /mnt/hurd. Имя файла образа: debian-hurd-20130504.img

Первое, что нужно сделать -- посмотреть расположение разделов в образе диска (иными словами, с какого сектора диска начинается каждый из разделов). Это можно сделать как минимум двумя способами: с помощью file и с помощью fdisk. Рассмотрим оба способа.

Утилита file, как я уже как-то раз писал, умеет показывать информацию не только о простых файлах, но и о всяких чудных штуках вроде сохранённой в файл Главной Загрузочной Записи (Master Boot Record, MBR). А сейчас эта утилита поможет нам получить информацию о разделах, запечатанных внутри образа диска:

$ file debian-hurd-20130504.img | fmt
debian-hurd-20130504.img: ; partition 1: ID=0x83, starthead 32,
startsector 2048, 5785600 sectors; partition 2: ID=0x5, starthead 99,
startsector 5789694, 354306 sectors
.

Здесь утилита fmt используется, дабы уместить вывод команды file в 75 столбцов.

Посмотрим же на вывод команды. Видно, что первый раздел (partition 1) начинается с 2048-го сектора диска (startsector 2048). Это как раз то, что нам нужно.

Попробуем теперь получить эту же информацию с помощью утилиты fdisk:

$ su -
# fdisk -l debian-hurd-20130504.img
Disk debian-hurd-20130504.img: 3146 MB, 3146776576 bytes, 6146048 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00053020

                  Устр-во Загр  Начало    Конец   Блоки  Id Система
debian-hurd-20130504.img1        2048 5787647 2892800 83 Linux
debian-hurd-20130504.img2     5789694 6143999  177153  5 Расширенный
debian-hurd-20130504.img5     5789696 6143999  177152 82 Linux своп / Solaris

Видим то же самое -- начало первого раздела находится на 2048-м секторе. Разница лишь в подробности вывода и в том, что нам нужно быть суперпользователем для использования fdisk, а утилита file может быть запущена от имени обычного пользователя.

Теперь посмотрим на то, как использовать имеющиеся данные. fdisk показывает данные в секторах, которые по-умолчанию равны 512 байтам. Соответственно, чтобы получить смещение первого раздела на диске в байтах, нужно умножить количество секторов на 512. Сделаем это:

$ echo $((2048 * 512))
1048576

Осталось только передать полученное значение команде mount:

$ su -
# mount -o loop,offset=1048576 debian-hurd-20130504.img /mnt/hurd

Ручного копирования посчитанного значения можно избежать, используя подстановку вывода команды:

# mount -o loop,offset=`echo $((2048 * 512))` \
debian-hurd-20130504.img /mnt/hurd

Полюбуемся результатом:

$ df -h | grep hurd
/dev/loop0  2,8G  804M  1,8G  31%  /mnt/hurd

И последнее: согласно документации, необходимо отмонтировать образ диска прежде, чем загружать его в QEMU -- иначе можно повредить данные на подмонтированных разделах образа диска.

Использованные источники:

- Артём

понедельник, 10 июня 2013 г.

Отладка разделяемой библиотеки в детективном жанре

Одна из проблем при разрабоке -- сделать так, чтобы программа собиралась и работала не только на машине у разработчика, но и на машине конечного пользователя. А ещё лучше -- чтобы всё работало без присутствия разработчика рядом.

Собственно, о чём речь

При работе над Guile-SSH столкнулся с интересной проблемой -- на Gentoo GNU/Linux (моей основной системе) библиотека работает без нареканий, а на Debian GNU/Linux 6.0.7 тестовая программа завершается с ошибкой.

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

Тестовая программа -- sssh, или Scheme Secure Shell -- работает, как упрощенный вариант ssh в неинтерактивном режиме. То есть, выполняет команду на хосте и возвращает результат. Авторизация на хосте происходит по открытым ключам.

Характерные признаки Bug'а

Bug проявляет себя громким шуршанием за плинтусом и чередующиемися ошибками вида Segmentation Fault (SIGSEGV) и Illegal Hardware Instruction (SIGILL):


$ ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname
libssh version:       0.5.2
libguile-ssh version: 0.2
1. ssh_new
2. ssh_options_set
3. ssh_connect
4. ssh_is_server_known
   ok
[1]    2205 segmentation fault  ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname

Инструменты сыщика

Один из способов отладки -- это добавление отладочных сообщений (или трейсов, от англ. trace, т.е. след) в код. Это на удивление эффективный способ узнать, как работает (или не работает) программа -- в том числе, он может помочь выследить даже хитрого bug'а.

С кодом на Scheme всё достаточно просто -- можно использовать, например, display и write. Примеры:


(display "debug message\n")
=> debug message

...

(write "Hello Scheme World\n")
=> "Hello Scheme World
"

...

(define value 1024)
(display (string-append "Value:" (number->string value))
=> Value: 1024

...

(define some-list '(a b c d e))
(display some-list)
=> (a b c d e)

А вот так можно добавить вывод отладочного сообщения в код на C, чтобы он печатался из Scheme:


scm_display (scm_from_locale_string ("debug message\n"),
             scm_current_output_port ());

То же самое, но в виде удобного макроса:


#define PRINT_DEBUG(data)\
  scm_display (data, scm_current_output_port ())

...

PRINT_DEBUG(scm_from_locale_string ("debug message\n"));

Сужаем круг поиска

Вернёмся к нашему логу.


...
3. ssh_connect
4. ssh_is_server_known
   ok
[1]    2205 segmentation fault  ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname

Путём добавления дополнительных отладочных сообщений после 4-го (в логе выше) можно легко понять, что sssh аварийно завершает свою работу при вызове библиотечной функции guile_ssh_public_key_to_string (ssh:public-key->string)


...
(display "4. ssh_is_server_known\n")
...
(let ((public-key (ssh:public-key->string
                    (ssh:private-key->public-key private-key))))
  ...

которая, в свою очередь, обращается к функции ssh_string_to_char из библиотеки libssh, где и происходит ошибка:


...
str_key = publickey_to_string (data->ssh_public_key);
ret = scm_from_locale_string (ssh_string_to_char (str_key));
ssh_string_free (str_key);
...

Дальнейшее исследование показало, что даннные в функцию передаются вроде бы нормальные, и всё должно работать. Тогда в чём же дело? Как оказалось, это уже...

Вопрос к линковщику

Изучение документации по созданию разделяемых библиотек (к слову, вот этот замечательный документ) дало подсказку, что нужно посмотреть, как идёт процесс динамического связывания. Как-никак, мы же отлаживаем разделяемую библиотеку, верно? Для того, чтобы увидеть этот процесс в действии, нам потребуется увеличительное стекло и одна переменная окружения: имя её LD_DEBUG. В вышеупомянутом замечательном документе пишут, что этой переменной можно присвоить несколько значений. На самом деле, вы можете присвоить ей любое значение, просто только некоторые, вполне определённые значения несут смысл.

Одно из таких сакральных слов -- ну конечно, help. Присвоим же это значение, ударим в бубен и посмотрим, что из этого получится:


$ export LD_DEBUG=help
$ ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname
Valid options for the LD_DEBUG environment variable are:

  libs        display library search paths
  reloc       display relocation processing
  files       display progress for input file
  symbols     display symbol table processing
  bindings    display information about symbol binding
  versions    display version dependencies
  all         all previous options combined
  statistics  display relocation statistics
  unused      determined unused DSOs
  help        display this help message and exit

To direct the debugging output into a file instead of standard output
a filename can be specified using the LD_DEBUG_OUTPUT environment variable.

Voilà! Вместо ошибки сегментации от нашей программы мы получили замечательный вывод справки от линковщика о доступных значениях LD_DEBUG. Наиболее интересным для нас сейчас будет значение symbols. Присвоим его переменной и попробуем запустить программу снова (надеюсь, вы не забыли надеть защитные очки?):


$ export LD_DEBUG=symbols
$ ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname
...
      2487: symbol=ssh_string_to_char;  lookup in file=/usr/lib/i686/cmov/libcrypto.so.0.9.8 [0]
      2487: symbol=ssh_string_to_char;  lookup in file=/lib/i686/cmov/libpthread.so.0 [0]
      2487: /usr/local/lib/libguile-ssh.so.0: error: symbol lookup error: undefined symbol: ssh_string_to_char (fatal)
[2]    2487 segmentation fault  ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname

Итак, мы получили ОЧЕНЬ много информации на выходе (я пропустил здесь эту часть), но нам важны последние несколько строчек. Вот оно! Линковщик не может найти символ ssh_string_to_char. Если посмотреть лог выше, то видно, что он просматривает, в том числе, и libssh:


...
 2505: symbol=ssh_string_to_char;  lookup in file=/usr/lib/libssh.so.4 [0]
...

Но ведь эта библиотека как раз и должна содержать в себе определение данной функции!

Идём по следу

Посмотрим-ка на версию библитеки в Debian GNU/Linux. Но прежде выключим подробный вывод сообщений от линковщика, присвоив LD_DEBUG пустую строку:


$ export LD_DEBUG=""
$ aptitude search libssh
...
i A libssh-4            - tiny C SSH library
...
$ aptitude versions libssh-4
i A 0.4.5-3+squeeze1    oldstable                  500 
p A 0.5.3-1~bpo60+1     squeeze-backports          100 

То есть, на Debian GNU/Linux у меня сейчас стоит версия libssh 0.4.x, тогда как мне известно, что API библиотеки был изменён в версии 0.5.x.

Попробуем поставить libssh 0.5 и запустить программу снова. Я собрал версию 0.5.3 из исходников, make install установил её в /usr/local/lib. Укажем программе, где искать библиотеку через переменную LD_LIBRARY_PATH:


$ LD_LIBRARY_PATH=/usr/local/lib ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname
libssh version:       0.5.2
libguile-ssh version: 0.2
1. ssh_new
2. ssh_options_set
3. ssh_connect
4. ssh_is_server_known
   ok
5. ssh_userauth_pubkey
6. ssh_channel_new
7. ssh_channel_open_session
8. ssh_channel_request_exec
Linux

Наконец-то! Заработало! Последняя строчка вывода получена по протоколу SSH и является выводом команды uname.

Заключение

Получается, что баг скрывался даже не в коде (точнее, не совсем в коде), а в динамической линковке с разделяемой библиотекой. При динамическом связывании линковщику не удавалось найти функцию ssh_string_to_char из libssh, тем не менее вызов этой функции всё же происходил. Я думаю так: процессор закладывал аргументы в стэк и прыгал на начало несуществующей функции -- из-за этого-то мы и получали чередующиеся ошибки SIGSEGV/SIGILL. Элементарно, Ватсон.

Странно только, что эта ошибка так себя проявляла -- вот уж чего бы я не ожидал, так это SIGSEGV/SIGILL на этапе исполнения программы из-за того, что линковщик не смог найти искомую функцию при динамическом связывании.

Одно из возможных решений проблемы -- это привязка библиотеки Guile-SSH к конкретной стабильной версии libssh. Это не самый лучший вариант, так как не во всех дистрибутивах используется новая версия библитеки (примером тому могут служить Debian GNU/Linux и старые версии Ubuntu GNU/Linux). Однако это решение позволит избежать проблем с поддержкой совместимости Guile-SSH с libssh 0.4.x.

На этом у меня всё. Надеюсь, что это расследование вам было интересно.

- Артём

суббота, 8 июня 2013 г.

Guile-SSH

Доброго времени суток, случайные и не случайные читатели этого блога.

Занимаюсь сейчас разработкой библиотеки Guile-SSH, которая призвана обеспечить возможность работы с протоколом SSH из программ, написанных на языке Scheme (с использованием интерпретатора GNU Guile).

Презентация по проекту: odp pdf (CC-BY-SA 3.0)

Guile-SSH является обёрткой над libssh и находится на начальной стадии разработки. На данный момент библиотека предоставляет API для создания простого SSH-клиента (пример клиента можно посмотреть здесь). API для создания SSH-сервера планируется.

Для сборки текущей версии библиотеки вам нужны GNU Guile 1.8 и libssh 0.5.3 или новее. Инструкции по сборке и установке можно найти здесь. Последняя версия Guile-SSH на данный момент -- 0.2, но если надумаете собирать, то лучше берите последний коммит с master'а. Код относительно стабилен (по крайней мере, стараюсь ничего не ломать в коммитах).

- Артём

воскресенье, 14 апреля 2013 г.

Макрос define-method* для использования ключевых слов совместно с GOOPS

Сегодня загрузил в репозиторий LazyCat коммит, который добавляет макрос, расширяющий стандартный набор функций GOOPS для создания методов. Макрос используется для реализации метода host-list-add-host в классе <host-list> и позволяет использовать ключевые слова (англ. keywords) для задания аргументов, передаваемых в метод. То есть, вместо создания методов с большим количеством параметров, или выдёргивания аргументов из списка по индексу, можно просто указывать каждый из аргументов по ключевому слову.

Текущая версия макроса выглядит так:

;; Needed modules
(use-modules (ice-9 optargs)
             (ice-9 syncase)
             (oop goops))

;; Macro definition
(define-syntax define-method*
  (syntax-rules ()
    ((_ (m (o <class>) (var defval) ...) body ...)
     (define-method (m (o <class>) . args)
       (let-keywords args #t ((var defval) ...)
                     body ...)))))

Пример использования макроса:

(define-class <talking-machine> ())

(define-method* (hey (obj <talking-machine>)
                     (say  "Hello, ") 
                     (name "J. Random Programmer"))
  (display (string-append say name "!\n")))

(define talking-machine (make <talking-machine>))

Результат вызова метода hey с разными аргументами:

(hey talking-machine)
=> Hello, J. Random Programmer!

(hey talking-machine #:say "Goodbye, ")
=> Goodbye, J. Random Programmer!

(hey talking-machine #:name "John Doe")
=> Hello, John Doe!

Данный макрос написан в процессе изучения макросов в GNU Guile -- может быть, данную задачу можно решить гораздо более простым и элегантным способом?

- Артём

суббота, 9 марта 2013 г.

Show bandwidth usage in GNU Screen status line

Написал простой скрипт для показа скорости загрузки/отдачи в статусной строке GNU Screen. Данный скрипт основан на скрипте для tmux, однако я его основательно переписал, убрав вызов sleep, из-за которого Screen блокировался при обновлении статусной строки.

- Артём

вторник, 29 января 2013 г.

Переворот строки c помощью указателей

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

Вот мой вариант ответа:


#include <stdio.h>

void reverse(char *str)
{
 char *p = str;

 while (*p && *(p + 1) && *p++)
  ;

 while ((p > str) && (*str += *p) && (*p = *str - *p) && (*str++ -= *p--))
  ;
}

int main(int argc, char **argv)
{
 char *str = argv[1];
 reverse(str);
 printf("desrever -- %s\n", str);
 return 0;
}

И, разумеется, "hello world":


$ gcc reverse.c -o reverse
$ ./reverse "hello world"
desrever -- dlrow olleh

- Артём