Это руководство доступно также на следующих языках: English

# Написание скриптов драйверов gnetlist на Scheme

Автор: John Doty

(первоначально это было отправлено в список рассылки gEDA-user в июле 2009 г.)

Не паникуй!

Если ты никогда не писал программы на Lisp, это выглядит страшновато. Но это намного легче, чем кажется. Добавь в Lisp чуть-чуть синтаксического сахара26) и он превращается в Logo, который могут изучить даже дети из начальной школы.

И просто для объяснения значения некоторых из этих странных слов: Lisp — компьютерный язык, Scheme — диалект Lisp'а, и Guile — реализация Scheme. Guile в gEDA используется для написания скриптов. В частности, оболочка gnetlist, написанная на C, выделяет из схем информацию о топологии и атрибутах, а затем отдаёт данные низкоуровневым скриптам (драйверам) на Guile для обработки и вывода.

Это руководство именно по программированию драйверов gnetlist на Scheme. Если ты ещё не знаешь Scheme, тебе, наверно, стоит взглянуть и на другие материалы, такие как:

http://www.ccs.neu.edu/home/dorai/t-y-scheme/t-y-scheme.html

Или поищи «Учебник по Scheme» в своём любимом поисковике: их много.

Также может пригодиться справочный документ по адресу:

http://www.gnu.org/software/guile/manual/html_node/index.html

Итак, начнём. Вот очень простой драйвер:

    ;; gnetlist development playground

    (use-modules (ice-9 readline))
    (activate-readline)

    (define (devel output-filename)
    	(scm-style-repl)
    )

Чтобы это применить, сохрани всё в файле `gnet-devel.scm`. Скопируй этот файл туда, где в твоей системе хранятся файлы Scheme. На машине, на которой я сейчас работаю, команда такова:

    $ sudo cp gnet-devel.scm /sw/share/gEDA/scheme/

`/sw/` для многих устанавливаемых в Linux пакетов надо заменить на `/usr/`, может быть на `/usr/local`, или — при установке из tar-архива — на `~/mygeda/`. Это нужно выяснить. Если ты можешь записывать в целевой каталог без прав суперпользователя, `sudo` не нужно.

Теперь, изменив нужным образом `/sw/`, набери:

    $ gnetlist -g devel /sw/share/gEDA/examples/lightning_detector/lightning.sch

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

    guile>

Попробуй:

    guile> packages

Ты должен увидеть:

    ("Q3" "R5" "Q2" "R4" "Q1" "C6" "R3" "L2" "A1" "bat(+3v)" "lamp(1)" "R2" "C5" "L1" "R1" "C4" "lamp(2)" "C3" "C2" "C1" "D1" "bat(0v)" "R7" "Q4" "R6")

`packages` — удобная переменная, содержащая список всех уникальных значений атрибутов `refdes=`. Набрав её, ты скормил её «REPL» — циклу чтения, вычисления, вывода (Read, Evaluate, Print Loop). Итак, REPL считал её, вычислил (получив список) и вывел.

Теперь попробуй:

    guile> (length packages)
    25

Что здесь произошло? Здесь REPL вычислил список.

    (length packages)

В большинстве языков программирования ты бы написал это выражение в более традиционной функциональной записи: `length(packages)`. `length` — это функция, которая сообщит тебе длину списка.

Такая же запись используется для арифметических вычислений. Например, «2+3» вычисляется так:

    guile> (+ 2 3)
    5

Учти, что процедура "+" может использоваться для сложения любого количества величин, в том числе и совсем ни одной:

    guile> (+)
    0
    guile> (+ 1 2 3)
    6

Это мы используем позже.

Строки про `readline` в нашем драйвере `gnet-devel.scm` позволят тебе пользоваться стрелками на клавиатуре для перемещения по истории и для редактирования вводимых строк. Очень удобно в интерактивном режиме. Попробуй.

Другая полезная переменная, определённая в gnetlist, это `all-unique-nets` (набери это). Точно так же как `(length packages)` говорит тебе, сколько у тебя компонентов, `(length all-unique-nets)` подскажет, сколько у тебя соединений.

Ещё есть `all-pins`:

    guile> all-pins
    (("1" "2" "3") ("2" "3" "1") ("2" "1") ("1" "2") ("1" "2") ("1" "2") ("1" "2") ("1" "2") ("1" "2") ("2" "1") ("2" "1") ("2" "1") ("1" "2") ("2" "1") ("1") ("1") ("2" "1") ("2" "3" "1") ("2" "3" "1") ("1") ("2" "1") ("2" "3" "1") ("1" "2") ("1") ("1"))

Заметь, это немного сложнее, чем в предыдущих примерах: это список списков, а не просто список строк. Каждый из списков соответствует выводам компонента. Есть одна штука, которую мы могли бы вытащить отсюда, — это подсчёт количества выводов. Мы не можем просто взять длину `all-pins`, чтобы получить его: это даст нам только количество списков, содержащихся там, равное количеству компонентов:

    guile> (length all-pins)
    25

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

    guile> (map length all-pins)
    (3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 1 2 3 3 1 2 3 2 1 1)

Это один из простых способов сделать «цикл» на Scheme; `(map p x)` выдаёт список результатов вызываемой процедуры `p` отдельно для каждого элемента из `x`. Затем мы можем их сложить с помощью «цикла» несколько иного типа:

    guile> (apply + (map length all-pins))
    50

`(apply p x)` вызывает процедуру `p` один раз, с аргументами из всех элементов из `x`. Поэтому вышеуказанное выражение в конце концов посчитает следующее:

    (+ 3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 1 2 3 3 1 2 3 2 1 1)

До сих пор мы использовали предопределённые переменные и процедуры. Но мы бы хотели иметь возможность определять свои. Это просто:

    guile> (define the-answer 42)
    guile> the-answer
    42

Это определяет переменную `the-answer` и задаёт ей значение 42.

Можно также определять процедуры:

    guile> (define add1 (lambda (x) (+ x 1)))
    guile> (add1 100)
    101

Когда видишь `lambda`, думай — «процедура». Сразу следом за `lambda` идёт первый элемент (технический термин — «выражение»27)) — список аргументов процедуры, в данном случае `(x)`. Когда вызывается процедура, Guile вычисляет оставшиеся выражения, в данном случае только одно, `(+ x 1)`, с подстановкой текущих аргументов. Результат процедуры — это результат вычисления последнего выражения. Так, `(add1 100)` становится `(+ 100 1)`, что даёт 101.

Теперь мы можем объединить наш сбор статистики в драйвер. Сначала определим процедуру для записи выходной строки:

    (define format-line
         (lambda (name value)
             (display name)
             (display value)
             (newline)
         )
    )

Здесь мы используем две новых встроенных процедуры, `display` и `newline`, названия которых говорят сами за себя. Теперь:

    (define display-stats
         (lambda ()                  ; без аргументов
             (format-line "pins:     " (apply + (map length all-pins)))
             (format-line "packages: " (length packages))
             (format-line "nets:     " (length all-unique-nets))
         )
    )

    guile> (display-stats)
    pins:     50
    packages: 25
    nets:     13

Чтобы завершить драйвер, нам нужна «основная программа». По соглашению она называется так же, как и сам драйвер. Также она отвечает за открывание выходного файла. Итак, целиком файл драйвера сбора статистики «stats» будет выглядеть примерно так:

    ;; драйвер gnetlist для получения статистики по проекту
    ;;
    ;; Стандартный текст лицензии, как положено

    (define stats
         (lambda (filename)
             (set-current-output-port (open-output-file filename))
    	(display-stats)
         )
    )

    ;; Сбор и вывод статистики

    (define display-stats
         (lambda ()                  ; без аргументов
             (format-line "pins:     " (apply + (map length all-pins)))
             (format-line "packages: " (length packages))
             (format-line "nets:     " (length all-unique-nets))
         )
    )

    ;; Простой формат вывода

    (define format-line
         (lambda (name value)
             (display name)
             (display value)
             (newline)
         )
    )

Сохрани это в файле с именем `gnet-stats.scm`, скопируй его в надлежащее место, например так:

    $ sudo cp gnet-stats.scm /sw/share/gEDA/scheme/

и затем `gnetlist -g stats` с другими обычными аргументами и именами схем выдаст статистику твоего проекта в выходной файл (по умолчанию `output.net`).

Довольно просто, а? А также полезно. Недавно я проектировал системы, состоящие из множества плат: статистика, подобная этой, помогает мне выяснить, какие подсистемы лучше скомбинировать на каждой из плат.

26)
«Синтаксический сахар» — конструкция языка программирования, полностью эквивалентная другой его конструкции, но имеющая более естественную запись (Компьютерный словарь). — Прим. перев.
27)
Англоязычный термин — «form». — Прим. перев.