Первая программа на PicoLisp
Перевод. Оригинал здесь
Это 11-я статья из цикла Введение в PicoLisp.
В ней мы обсудим соглашения об именовании и создадим, наконец, нашу первую программу на PicoLisp.
В PicoLisp есть небольшое количество соглашений об именовании различных объектов, призванных избежать конфликтов имён и сделать код более читаемым. Вот некоторые из наиболее важных:
- Имена глобальных переменных начинаются со звездочки
*
. - Имена глобальных констант записываются прописными.
- Функции и другие глобальные символы начинаются со строчной буквы.
- Локальные символы начинаются с прописной буквы (например, локальные переменные в функциях).
Этот список не полон, с полным списком соглашений по написанию кода вы можете ознакомиться здесь.
Давайте возьмём для примера функцию PicoLisp car
. Поскольку данные и
код в PicoLisp - одно и то же, локальная переменная с именем "car"
может переопределить встроенную функцию car
:
: (de max-speed (car)
(.. (get car 'speeds) ..) )
-> max-speed
Внутри функции max-speed
встроенная в язык функция car
переопределена, что безусловно приведёт к сбою при выполнении многих
команд, например (car Lst)
. Поэтому мы должны начинать имена локальных
переменных с прописной буквы:
: (de max-speed (Car) # 'Car' начинается с прописной буквы
(.. (get Car 'speeds) ..) )
-> max-speed
Другим примером могут быть глобальные символы T
и NIL
, которые можно
переопределить по ошибке:
(de foo (R S T)
...
Пользоваться при написани кода только REPL - занятие не из лёгких. Сейчас мы создадим небольшую программу, которая выполняет всего одну функцию - запрашивает имя пользователя и выводит его на экран. В этом примере мы покажем, как должна выглядеть типичная программа на PicoLisp.
Для лучшего усвоения материала пишите код прямо во время чтения статьи. Если такой возможности у вас нет - в конце вы найдете ссылку на файл с исходным кодом.
Как говорилось в серии статей "60 функций PicoLisp", для того, чтобы
прочитать строку из входного потока, можно воспользоваться функцией
line
. Давайте присвоим этой строке имя Name. Воспользуемся REPL для
проверки нашей идеи:
: (setq Name (line))
Mia
-> ("M" "i" "a")
Давайте проверим, что имя присвоилось:
: (prinl "Hello " Name)
Hello Mia
Кажется, всё работает, как предполагалось. Давайте теперь создадим текстовый файл с названием greeting.l (расширение .l используется для файлов с программами на PicoLisp) и вставим туда следующий текст:
# greeting.l
(prinl "Hello! Who are you?")
(setq Name (line))
(prinl "Hello " Name "!")
Сохраните файл. Теперь вы можете запустить программу командой
pil greeting.l
из консоли. К сожалению, результат выглядит несколько
странным:
$ pil greeting.l
Hello! Who are you?
Hello !
(знак $
- это приглашение командной оболочки, его вводить не надо!)
Кажется, команда (line)
пропускается. Почему так происходит?
Согласно документации, функция
line
считывает строку из текущего входного потока. Нам нужно сделать
так, чтобы строка считывалась из консоли, потому что во время запуска
файла входным потоком является сам файл с программой.
После поиска в документации вы выясните, что для чтения из консоли нужно
использовать функцию in
, передавая ей первым параметром NIL
вместо
имени файла. Давайте изменим текст нашей программы, чтобы он выглядел
так:
# greeting.l
(prinl "Hello! Who are you?")
(setq name (in NIL (line)))
(prinl "Hello " name "!")
Раотает! Однако после выполнения кода не происходит выход из программы.
Чтобы программа завершалась после выполнения инструкций, добавьте в
конец строку (bye)
.
Давайте теперь, в демонстрационных целях, вынесем код, печатающий приветствие, в отдельную функцию:
(de greeting (Name)
(prinl "Hello " Name "!") )
Обратите внимание, что по соглашению о написании кода "Name" написана
с прописной буквы. Также обратите внимание, что в конце второй строки
стоит пробел между скобками: ) )
. Он показывает, что последняя скобка
закрывает выражение, которое началось не на текущей строке, а на
предыдущей. Это стандартный подход в языках семейства Lisp.
Конечно же, определение функции greeting
должно происходить раньше её
использования, чтобы интерпретатор знал, о какой функции идёт речь.
Текст программы после изменения выглядит так:
# запустите эту программу командой "pil greeting.l"
(de greeting (Name)
(prinl "Hello " Name "!") )
(prinl "Hello! Who are you?")
(setq Name (in NIL (line)))
(greeting Name)
(bye)
Готовый файл с программо вы можете скачать отсюда.
Использовать каждый раз интерпретатор, чтобы запускать программу, не слишком удобно. К счастью, в Linux есть способ сделать любой текстовый файл исполняемым. Вот инструкция для этого:
Используется механизм так называемых "интерпретируемых файлов". Если
первые два символа файла - #!
, то операционная система откроет файл с
помощью программы-интерпретатора, путь к которой указан в первой строке.
Опционально можно передать интерпретатору аргументы.
Если вы устанавливали PicoLisp глобально, например, используя
менеджер пакетов вашего дистрибутива, то почти наверняка исполняемый
файл picolisp
находится в каталоге /usr/bin
. Убедиться в этом можно,
выполнив команду which
:
$ which picolisp
/usr/bin/picolisp
Если вы выполняли локальную установку, то файл инрерпретатора picolisp
находится в подкаталоге bin/
того каталога, куда вы устанавливали
PicoLisp: <Path to Folder>
{=html}/pil21/bin
. Найдите полный путь до
файла с помощью команды locate
:
$ locate "bin/picolisp"
/home/user/pil21/bin/picolisp
Если команда locate
выдаёт ошибку, значит вам нужно установить пакет
mlocate
или plocate
через ваш пакетный менеджер и запустить
обновление базы данных для этой команды: $ sudo updatedb
.
Одного интерпретатора недостаточно. Нам понадобится ещё библиотечный
файл, который называется lib.l
. При глобальной установке этот файл
обычно находится в каталоге /usr/lib/picolisp
:
$ locate "picolisp/lib.l"
/usr/lib/picolisp/lib.l
При локальной установке этот файл находится в корне с папкой, куда установлен picolisp:
$ locate "lib.l"
/home/user/pil21/lib.l
Отлично! Теперь вернёмся к файлу с нашей программой.
Первая строка вашего файла должна выглядеть так:
#! /usr/bin/picolisp /usr/lib/picolisp/lib.l
Замените путь к интерпретатору и библиотеке, если они у вас отличаются. Как вариант, если у вас только локальная установка, вы можете создать ссылки на интерпретатор и библиотеку в стандартных расположениях:
$ ln -s /home/foo/pil21 /usr/lib/picolisp
$ ln -s /usr/lib/picolisp/bin/picolisp /usr/bin
Последний шаг, который нам остался - сделать файл с программой
исполняемым. Это делается командой chmod +x greeting.l
. Теперь вы
можете запускать программу командой ./greeting.l
.
$ ./greeting.l
Hello World! Who are you?
Mia
Hello Mia!
Теперь всё должно работать. Готовый файл с программой вы можете загрузить здесь.
Иногда требуется запустить программу с определёнными параметрами, вместо того, чтобы вводить их после запуска.
Давайте изменим нашу функцию greeting
так, чтобы она получала имя из
глобальной переменной:
(de greeting ()
(prinl "Hello " *Name "!"))
В PicoLisp есть встроенная функция opt
, которая принимает аргументы из
командной строки. Присвоим переменной *Name
значение из командной
строки следующим кодом:
(setq *Name (opt))
Ваша программа должна выглядеть вот так:
#! /usr/bin/picolisp /usr/lib/picolisp/lib.l
(de greeting ()
(prinl "Hello " *Name "!"))
(setq *Name (opt))
(greeting)
(bye)
Теперь мы можем передавать имя программе прямо через вызов в командной строке следующим образом:
$ ./greetings.l Mia
Hello Mia!
Работает! Но что будет, если мы вызовем программу без аргументов?
$ ./greetings.l
Hello !
Было бы неплохо, если бы программа проверяла, передано ли ей имя через
аргумент командной строки и предлагала ввести его, если это забыли
сделать. Используем для этого управляющую конструкцию ifn
("если
не"):
(ifn *Name
(prinl "Пожалуйста, введите своё имя!")
(greeting) )
Давайте проверим, как работает программа теперь:
$ ./greeting-with-args.l
Please specify a name!
$ ./greeting-with-args.l Mia
Hello Mia!
Полный код программы вы можете скачать по ссылке.
В следующих двух статьях мы поговорим о работе с документацией и отладчиком функций PicoLisp.