Перейти на главную страничку сайта (список статей, файлы для скачивания)

ФОРУМ (здесь можно обсудить эту статью, а также любые проблемы программирования на различных макроязыках и в скриптовых средах)

Скриптовый язык newLISP

Оглавление:
Основные свойства и возможности
Hello, World!
Особенности синтаксиса
Контексты и модульное программирование
Сборка исполняемых файлов

Основные свойства и возможности

LISP — это целое семейство языков, наиболее известными и признанными из которых являются Common LISP и Scheme. Название LISP расшифровывается как "LISt Processor" (обработчик списков). Создатель семейства языков LISP — американец John McCarthy, автор термина "искусственный интеллект". История LISP ведёт отсчёт примерно с 1958 года.

Первые области применения LISP были связаны с символьной обработкой данных и проблемами искусственного интеллекта. Сегодня LISP является универсальным языком программирования и используется в самых разных проектах. Одно из направлений использования языков LISP — это применение в качестве встроенного языка в ряде очень популярных прикладных программ (AutoCAD, Emacs, GIMP).

Полезные русскоязычные ресурсы по LISP:


NewLISP — это свободный (лицензия GPL) скриптовый язык общего назначения для задач, обычно решающихся с помощью интерпретируемых языков, таких, как программирование для Интернет, системное администрирование, обработка текста, соединение различных программ и модулей в единое приложение и т.д. NewLISP реализует быстрый и компактный интерпретатор языка LISP.

Автор newLISP — Lutz Mueller. Интерпретатор newLISP написан на чистом Си, с применением только стандартной библиотеки. Такая реализация сделала newLISP кросс-платформенным языком, доступным для Linux, Windows, MacOS X, OS/2. Дистрибутив для Windows имеет размер порядка 1 Мб и представляет из себя удобный инсталлятор.

Основные возможности newLISP:

NewLISP достаточно сильно отличается от Common LISP или Scheme, и разработчики NewLISP не советуют изучать книги по этим языкам в целях освоения newLISP — newLISP гораздо более современен и многие проблемы решает совсем по-другому. Подробную документацию и введение в newLISP (англ.) можно найти здесь, здесь и здесь. Также есть подробный сборник образцов кодирования, примеры и форум (англ.). Дополнительные модули, не вошедшие в поставку, можно найти здесь.

После установки newLISP из дистрибутива под Windows вы получаете интерактивный интерпретатор newlisp.exe, подробную документацию, установленную локально, и графическую IDE (среду разработки) newLISP-GS. Можно прописать путь к интерпретатору в переменную среды PATH. Ша-банг для скриптов newLISP под Linux может выглядеть так:

#!/usr/bin/env newlisp

Hello, World!

Интерпретатор newlisp.exe является интерактивным, и после его запуска вы получите консоль с приглашением для ввода команд на языке LISP. Для ввода многострочных скриптов используйте обрамляющие теги [cmd] и [/cmd]. Для завершения сессии работы интерпретатора введите команду (exit).

Кроме того, есть интерактивная IDE (среда разработки) newLISP-GS, для вызова которой при установке под Windows создаётся специальный ярлык. В newLISP-GS вы получаете графический редактор с подсветкой синтаксиса и встроенной консолью вывода. Можно также запустить newLISP-GS и командой наподобие следующей:

newlisp "C:\Program Files\newlisp\newlisp-edit.lsp"

Скрипт "Hello, world!" будет выглядеть следующим образом:

(print "Hello, world!")
(exit) ; нужно для завершения работы интерпретатора

Сохраните текст скрипта в файле с расширением .lsp и запустите его командой наподобие следующей:

newlisp test.lsp

Чуть более развёрнутый пример — удаление всех пустых файлов (нулевого размера) в текущем каталоге:

(dolist (zfile (directory "."))
    (if (= (file-info zfile 0) 0) ; если нулевой размер
        (if-not (directory? zfile) (begin ; если это не каталог
            (delete-file zfile)
            (println "deleted: " zfile)
    )))
)
(exit)

Особенности синтаксиса

Эта часть статьи рассчитана на тех, кто уже немного знаком с каким-либо языком программирования, и здесь мы выборочно приведём лишь некоторые особенности, которые позволяют познакомиться с уникальным лицом языка. Приводимые ниже фрагменты кода пробуйте в интерактивной среде newLISP-GS из поставки. Вообще, здесь и далее в статье мы приведём только некоторые характерные приёмы программирования, которые позволят почувствовать "вкус" языка, но не превратят статью в руководство или справочник.

Как следует из самого названия языка, список в LISP — это основной тип данных. Элементами списка могут быть константы, имена переменных и функций (в терминологии LISP — символы) и другие списки:

(1 2 "abc" var (34 "z"))

Немного о вызове функций. То, что математик записал бы как f1(x, f2(y, z)), на LISP будет выглядеть как (f1 x (f2 y z)). Математическая запись 1 + 2 + 3 на LISP будет выглядеть как (+ 1 2 3). Таким образом, "+" — это обычная функция, которая получает аргументы. Например, setq — функция присваивания, которая также возвращает значение последнего аргумента. Пример присвоит переменной b значение 5 (квадратный корень из 25) и вернёт это значение:

(setq b (sqrt 25))

Апостроф — специальный символ, предотвращающий вычисление выражения, в результате чего получается обычный список. Апостроф — это просто короткая запись для функции quote. Следующие записи эквивалентны (переменной b присваивается список, а не квадратный корень из 25):

(setq b (quote (sqrt 25)))
(setq b '(sqrt 25))

Получение элемента списка по индексу, синтаксис "implicit indexing":

(setq b '(("first" 1) ("second" 2) ("third" 3) ("fourth" 4)))
(b 1) ; выведет ("second" 2), первый элемент имеет индекс 0
(1 b) ; срез, с первого элемента до конца: (("second" 2) ("third" 3) ("fourth" 4))
(1 2 b) ; срез, два элемента, начиная с первого: (("second" 2) ("third" 3))

Ассоциативный список — это список списков, в которых первый элемент используется как ключ для поиска:

((ключ1 значение1) (ключ2 значение2) ...)

Для поиска в ассоциативных списках используются функции assoc и lookup. Можно иметь несколько элементов с одинаковым ключом, а порядок следования записей всегда фиксирован. Пример поиска в ассоциативных списках:

(setq b '(("first" 1) ("second" 2) ("third" 3) ("fourth" 4)))
(assoc "second" b) ; выведет весь подсписок ("second" 2)
(lookup "second" b) ; выведет последний элемент подсписка - 2
(lookup "second" b 0) ; выведет указанный элемент подсписка - "second"
(lookup "second" b 1) ; выведет указанный элемент подсписка - 2

Цикл со счётчиком на пять итераций:

(dotimes (i 5)
    (println i " привет") ; печать строки
)

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

Объявление и вызов функции:

(define (summa arg1 arg2) (+ arg1 arg2))
(summa 25 2) ; выведет 27

В некотором смысле объявленная выше пользовательская функция "summa" — это всего лишь переменная, которой присвоен в качестве значения специальный список, представляющий из себя безымянную функцию (lambda). Пример, равносильный предыдущему, который демонстрирует сказанное:

(setq summa '(lambda (arg1 arg2) (+ arg1 arg2)))
(summa 25 2) ; выведет 27

Функцию можно вызвать и "по имени", передав ей список аргументов, с помощью функции apply:

(define (summa arg1 arg2) (+ arg1 arg2))
(setq oops '(summa +)) ; список oops, состоящий из двух функций - "summa" и "+"

; вызов функции summa тремя способами:
(summa 1 2)
(apply summa '(1 2))
(apply summa (list 1 2))

; вызов первой функции из списка функций "oops":
(apply (oops 0) '(1 2 3)) ; выведет 3, последний аргумент отбрасывается

; вызов второй функции из списка функций "oops":
(apply (oops 1) (list 1 2 3)) ; выведет 6

В отличие от обычных функций, аргументы которых автоматически вычисляются перед вызовом, при вызове макро-функций используется так называемый ленивый (lazy) порядок вычисления, при котором аргументы не вычисляются:

(define (test1 arg) arg) ; просто возвращаем переданный аргумент
(test1 (+ 1 2)) ; выведет 3
(define-macro (test2 arg) arg)
(test2 (+ 1 2)) ; выведет (+ 1 2)

Вышеприведённая функция test2 является по сути аналогом встроенной функции quote. В макро-функции можно вычислить переданное выражение принудительно, с помощью функции eval:

(define-macro (test3 arg) (eval arg)) ; вычисляем переданное выражение
(test3 (+ 1 2)) ; выведет 3

В newLISP в качестве значения "ложь" используется символ nil. Все остальные значения в newLISP трактуются как "истинные", что обозначается символом true (в т.ч. 0, пустая строка и пустой список). Функция условного ветвления выглядит так:

(setq x 0 y 1)
(if (> x y) (println "больше")
    (= x y) (println "равно")
            (println "другое")
)

Или так:

(setq x 0 y 1)
(cond ((> x y) (println "больше"))
      ((= x y) (println "равно"))
              ((println "другое"))
)

Если по условию необходимо выполнить больше одного выражения, их нужно "обернуть" функцией begin:

(setq x 2 y 1)
(if (> x y) (begin (print "больше") (println "!"))
    (= x y) (println "равно")
            (println "другое")
)

В варианте с cond функция begin не нужна, т.к. есть дополнительные скобки.

Функция выбора case:

(define (translate n)
    (case n
        (1 "one")
        (2 "two")          
        (3 "three")
        (true "Can't translate this")))

(println (translate 1))

В некотором смысле в LISP существует всего два типа данных — атомы (числа и символы) и списки, с помощью которых можно построить сколь угодно сложные структуры данных. Благодаря его специфическому синтаксису LISP часто называют мета-языком: одним из рекомендуемых методов программирования на LISP является создание собственного языка с синтаксисом, удобным для решения поставленной задачи, а затем использование этого языка для получения конечного результата. Например, вот так можно определить собственный оператор условного ветвления:

(define-macro (new-if condition res-true res-false)
    (let (t (eval condition)) ; временный символ t (будет равен nil или true)
         (and t (eval res-true))
         (or t (eval res-false))
    )
)
;===========================================
(new-if (= 1 1) (println "yes") (println "no")) ; yes
(new-if (= 1 2) (println "yes") (println "no")) ; no

В идентификаторах можно свободно использовать кириллицу:

(define (квадрат arg) (* arg arg))
;===========================================
(define (гипотенуза leg-1 leg-2)
    (sqrt
        (+ (квадрат leg-1) (квадрат leg-2))
    )
)
;===========================================
(setq катет-1 12 катет-2 5)
(setq результат (гипотенуза катет-1 катет-2))
(println результат)

Пример выше вычислит гипотенузу прямоугольного треугольника с катетами 12 и 5.

Контексты и модульное программирование

Понятие контекста (context) в newLISP обеспечивает именованный "контейнер" для символов. У символов в различных контекстах может быть одинаковое название без конфликта имён. Если вы явно не создавали и/или не переключали контексты, вы работаете в контексте MAIN.

Функция context может использоваться для создания контекста, переключения между контекстами, получения значения существующего символа в контексте, выяснения имени текущего контекста, создания нового символа в контексте.

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

(dolist (s (symbols MAIN))
    (println s)) ; можно использовать (eval s) для получения значений

Можно автоматически создать контекст при определении функции:

(define (D:greeting) ; будет создан контекст "D"
    (println "greetings from context " (context)))
(D:greeting)

Обычное использование контекста — создание словаря (упорядоченной последовательности пар "ключ/значение"):

; пустой словарь (формально - пустая функция, название которой
; совпадает с названием её контекста, т.е. функция "по умолчанию"):
(define Dict:Dict)
; синтаксис работы со словарём:
; (Dict key value) - установить значение ключу
; (Dict key) – получить значение ключа
; (Dict key nil) – удалить ключ
(Dict "one" "first")
(Dict "two" "second")
(println (Dict "one")) ; выведет first
; другой способ инициализации словаря:
(Dict '(("one" 1) ("two" 2)))
(println (Dict "two")) ; выведет 2
(exit)

Созданный контекст (словарь) можно сохранить в файл:

(save "C:/Temp/test.txt" 'Dict)

Сохранённый контекст (словарь) можно загрузить из файла, чтобы им воспользоваться:

(load "C:/Temp/test.txt")
(context 'Dict)
(println (Dict "two"))
(exit)

Контексты используются как контейнеры для программных модулей, потому что они обеспечивают лексически отделённые пространства имен. Обычно модули, включённые в поставку newLISP, определяют контекст, который содержит ряд функций, решающих задачи в определенной области. Загрузка модуля и переключение в его контекст производятся так же, как показано в примере выше, с помощью функции load, а затем context. После этого можно вызывать функции модуля точно так же, как "обычные" функции. Можно не переключаться в загруженный контекст, а вызывать его функции с полными именами (контекст:функция).

В FOOP (Functional Object-Oriented Programming) методы и свойства класса, т.е. функции и символы, которые относятся к каждому объекту этого класса, хранятся в контексте. Каждый объект хранится как список, где первый элемент — символ, идентифицирующий класс объекта, а оставшиеся элементы — значения, которые описывают свойства объекта. Таким образом, все объекты класса используют те же самые свойства, но у этих свойств могут быть различные значения.

Например, момент времени представляется как объект Time. Объект содержит два значения: число секунд, прошедших с начала 1970 года, и смещение часового пояса, в минутах к западу от Гринвича. Таким образом, список, который представляет типичный объект Time, выглядит следующим образом:

(Time 1219568914 0) ; примерно 10 утра 24 августа 2008, где-нибудь в Англии

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

(define (Time:Time (t (date-value)) (zone 0))
    (list Time t zone)) ; список с именем класса и двумя свойствами
; значения по умолчанию - текущее время и нулевое смещение

; создание объекта класса:
(set 'christmas-day (Time (date-value 2008 12 25)))

Затем можно определить метод класса:

(define (Time:show t)
    (date (t 1) (t 2)))

И вызвать этот метод:

(println (Time:show christmas-day))

Сборка исполняемых файлов

Для сборки исполняемых файлов предназначен модуль link.lsp, расположенный в каталоге util установки newLISP.

Приведём пример сборки исполняемого файла под Windows. Создайте файл test.lsp следующего содержания:

; выводим первый переданный аргумент, преобразуя его к верхнему регистру:
(println (upper-case (main-args 1)))
(exit)

Скопируйте файлы link.lsp и newlisp.exe в тот же каталог, где расположен только что созданный test.lsp. Запустите командный интерпретатор и перейдите в этот каталог. Далее выполните команду:

newlisp link.lsp

В результате вы получите сессию интерпретатора newLISP в контексте link.lsp. Наберите LISP-команду:

(link "newlisp.exe" "uppercase.exe" "test.lsp")

В результате вы получите исполняемую программу uppercase.exe размером чуть больше 230 Кб, которая выводит свой первый аргумент, преобразуя его к верхнему регистру. Запустите её примерно так:

uppercase "abcdefgh"

Вы должны получить вывод "ABCDEFGH".

Людоговский Александр, 18.02.2009г.

Перейти на главную страничку сайта (список статей, файлы для скачивания)

© 2007 http://www.script-coding.com При любом использовании материалов сайта обязательна ссылка на него как на источник информации, а также сохранение целостности и авторства материалов.