Глава 2. Программы.

2.1. Правила написания и интерпретации операторов.
В KLisp сохранен принцип единообразной префиксной формы запи- си операторов принятой в языке Lisp, при которой имя оператора и его аргументы записываются внутри круглых скобок с разделением параметров пробелами: (оп1 x y) На первом месте после левой круглой скобки '(' всегда должен сто- ять оператор известный интерпретатору, а за ним может находиться про- извольное число аргументов. Например, (оп1 10) -интерпретируется как вызов оператора оп1 с параметром 10, а (оп1 1 0) - как вызов оператора оп1 с двумя параметрами 1 и 0. Такой способ написания операторов очень удобен и для интерпрета- тора, поскольку исключает необходимость сложного синтаксического анализа выражения перед началом его исполнения, и для программиста, поскольку при большом количестве сложновложенных операторов позво- ляет сразу определить назначение конструкции. Очень важным преимуществом языка даже по сравнению с языком С яв- ляется возможность использования при написании программ неограничен- ной вложенности операторов. Т.е. внутри оператора может находиться произвольная конструкция из других операторов, последовательность вы- полнения которых определяется порядком закрытия правых скобок. Например, (op1 (op2 x1 (op3 x2 x3) (op4) )) последовательность выполнения: op3, op4, op2, op1. Мало того, вложенность допускается и внутри написания имени пе- ременной при определении номера элемента массива: A.B[(op1 ...)(ор2 (ор3 ) ... )].C Здесь сохраняется тот же принцип последовательности исполнения операторов: (оп1 B[(оп2 1 (оп3 А))]) Конкретный элемент массива В будет определен в ходе вычисления опера- торов 3 и 2 до выполнения оп1. Однако, следует помнить, что внутри "квадратных скобок" должна соблюдаться парность "круглых". Попробуйте написать на Си последовательность операторов внутри оп- ределения элемента массива. Вам это не удастся, а выносить их за пре- делы имени не всегда удобно,если Вы имеете дело с конструкцией данных состоящей из взаимовложенных массивов структур. Кроме того Вы можете организовать переход сюда по оператору (goto ) из любого места программы, либо до завершения описания имени перейти к обработке дру- гой части программы. Хотя транслятор KLisp не ограничивает уровень вложенности опера- торов, указывать в качестве имени оператора вложенную конструкцию нельзя. Например, запись ((оп2 0) 1 1) - ошибочна. Следует отметить, что при написании программы транслятор KLisp допускает произвольное число пробелов и переносов строк при разнесе- нии операторов и аргументов,а также ввод комментариев после двух зна- ков: // - (косая черта) или внутри блока ограниченного знаками: /* .........*/, как в Си. Наличие в программах большого количества скобок кажется на первый взгляд трудноперевариваемым, особенно для тех кто привык к процедур- ным языкам программирования. Однако по мере освоения функционального подхода к написанию программ, Вы оцените такой способ записи. Во-первых, постарайтесь выбрать удобную и понятную для вас форму написания текста программы. Обычно используют способ смещения записи, когда внутренние аргументы оператора пишутся под ним на несколько ко- лонок правее: (оп1 Х (оп2 Н) ) При таком способе легче контролировать парность скобок и разобраться в функциональном назначении операторов. Во-вторых, функциональное программирование предполагает постепен- ную детализацию исполняемых функций: сначала пишутся операторы, вы- полняющие основные функции программного блока, а затем с помощью на- ращивания вложенности, определяются способы вычисления их аргументов. В связи с этим, в языках, реализующим такой способ программирования, предъявляются повышенные требования к обработке сложновложенных опе- раторов и переходов внутри программы. KLisp, как вы убедитесь, блес- тяще решает обе эти проблемы. С целью сокращения промежуточных присваиваний из языка Си заим- ствован принцип возврата операторов. То есть после выполнения опера- тора он рассматривается интерпретатором как переменная определенного типа, которая может соответствующим образом обрабатываться. Например, если в конструкции (op1 X (op2)) оператор op2 после своего исполнения возвращает значение Y, то опера- тору op1 будет передано два параметра X и Y. Такой подход имеет целый ряд преимуществ: Во-первых,как уже отмечалось, сокращается число промежуточных присва- иваний, то есть отпадает необходимость в промежуточных переменных, что очень важно для безстекового интерпретатора. Во-вторых, сокращается и число самих операторов, что увеличивает ско- рость исполнения программы. В-третьих, упрощается структура программы. Hапример, вместо того чтобы написать программу в виде: (= X (оп1)) (= Y (оп2)) (оп3 X Y) можно написать: (оп3 (оп1) (оп2)) что наглядно показывает назначение операторов оп1 и оп2. Операторы могут возвращать численные переменные: char, int, long, float; указатели: addr; но не могут возвращать структуры, хотя это легко компенсируется возвратом указателей на структуру произвольного типа. Есть еще одно понятие возврата, это ссылка на переменную: ssyl. Ссылку обычно возвращают операторы работающие с разными типами данных, например, арифметические операторы: (+ ) (- ) (= ). Интер- претатор воспринимает ссылку как, если бы Вы написали на этом месте саму переменную, не проводя над ней никаких действий. Hапример, в эк- вивалент предыдущему примеру: (оп3 (= X (оп1)) (= Y (оп2))) оп3 в качестве аргументов будут переданы переменные X и Y, но до этого они будут синициализированы операторами оп1 и оп2. Такой способ был сделан для расширения возможностей по функциональному программиро- ванию языка KLisp. Очень важно уловить разницу между возвратом ссылки и возвратом переменной. Следующий пример наглядно демонстрирует эту разницу. Опе- ратор (+ ) возвращает ссылку, оператор (> ) возвращает (char ) - "ис- тина" или "ложь". ((int) (A 1) B) (print "A=%d% " A) // Вывод 1 (+ (+ (+ A 1) 1) 1) (print "A=%d% " A) // Вывод 4 (print "N=%d% " (+ (> A B) 1)) // Вывод 2 (print "A=%d% " A) // Вывод 4 Следующая особенность языка: многие операторы воспринимают произ- вольное количество входных аргументов и умолчания. Это также сделано для увеличения эффективности программ. Hапример, оператор (+ Х) // По умолчанию увеличит значение X на 1 (+ X Y) // Прибавит к X Y (+ X Y (оп1) (оп2) ......) // Будет прибавлять к X все вычисленные значения переменных и операторов до закрытия правой скобки оператора (+ ). Что конкретно возвращает каждый оператор, какие требует аргументы и какие возможны умолчания Вы найдете ниже, в разделе описания опера- торов.
2.2. Структура программ.
Программа KLisp оформляется в виде текстового файла, который может содержать одну или несколько подпрограмм. Подпрограмма опреде- ляется в виде поименованной функции, внутри которой записывается блок исполняемых операторов и переменные с которыми они работают. Общая структура подпрограммы выглядит следующим образом: ((defun <имя п/программы>) (<имена и типы передаваемых аргументов>) (<имена и типы внутренних переменных>) <операторное тело программы> ) Имя подпрограммы - строка длиной от 1 до 9 знаков. Допускается использовать большие и маленькие латинские буквы, цифры (только не первым знаком) и знак '_'. По этому имени к ней можно будет обращать- ся из других подпрограмм с помощью операторов (call ) и (gofunc ). Внутри следующих операторных скобок указываются имена и типы ар- гументов передаваемых при вызове подпрограммы, если они есть. Если таковых нет, то указание пустых скобок обязательно. Об особенностях передачи значений в KLisp мы поговорим при описании операторов (call) и (gofunc ). Следующий блок определяет имена и типы внутренних переменных под- программы. Если их нет, то указание пустых скобок также обязательно. Далее следует произвольная конструкция операторного тела программы. Внутренние переменные и аргументы доступны для использования только внутри подпрограммы. Кроме того, можно определить переменные доступные нескольким подпрограммам. Они называются внешними и объяв- ляются внутри блока (extern), располагаемого снаружи блока (defun). Общая схема: ((extern) <описатели переменных> ) Всем подпрограммам, расположенным ниже указанного блока доступны переменные, имена которых указаны в нем. При совпадении имен внутрен- них и внешних переменных в подпрограммах обрабатываются внутренние переменные. В программном файле может быть несколько описателей (extern ). Доступ к этим данным той или иной подпрограммы определяется их взаим- ным расположением. Описание структур данных производится вне блока defun. Объявление переменных struct может производиться как в блоке внешних данных, так и в блоке внутренних параметров подпрограммы, но только ниже их опи- сания. Как и в случае операторов структуры не могут передаваться в качестве аргументов подпрограммы, но могут быть переданы указатели на структуру, то же относится и к массивам данных. Внешние и внутренние переменные при объявлении могут быть синициа- лизированы. Аргументы подпрограмм не инициализируются, а определяются при исполнении параметрами переданными при обращении к подпрограмме через операторы (call ) или (gofunc ) при этом типы аргументов прог- раммы и операторов должны совпадать. Как и в случае с операторами, здесь применен мягкий принцип передачи аргументов, в отличии от обра- щения к подпрограммам в языке Си. Оператор (call "porg" ... ), может передать меньшее количество аргументов, чем ждет подпрограмма.Осталь- ные сохранят свои предыдущие значения как статические переменные. Hо и при этом типы передаваемых параметров, должны совпадать с типами первых аргументов подпрограммы. Аргументы воспринимаются подпрограм- мой как внутренние переменные, поэтому их имена не должны совпадать. Hикаких ограничений на их использование нет. Пример схемы расположения блоков: ((defun ccc) () () // Подпрограмма "ccc" (оп1 10) ) ((extern) // Блок внешних данных (int A) ) ((defun aaa) () () // Подпрограмма "ааа" (оп2 A 1) (call "bbb" 1) ) ((defun bbb) ((char D)) // Подпрограмме "bbb" передан ( // аргумент D ((char) (K 1)) // Объявлена внутренняя переменная К=1 ) (оп3 A D K) ) Переменная A доступна подпрограммам "aaa" и "bbb", но недоступна подпрограмме "ccc". Как и операторы, подпрограммы могут возвращать значения с помощью оператора (return Arg). Возвращаться могут любые типы переменных, кроме массивов и структур, их заменяют возвратом указателей. Причем из разных мест программы могут возвращаться разные типы переменных. Здесь интерпретатор не выставляет никаких ограничений, и оставляет на усмотрение программиста, как использовать эти возможности. Для программистов следует указать на изменение идеологии построе- ния подпрограмм в отличии от стековых языков. Объявляя в подпрограмме структуру или массив переменных в качестве внутренней переменной, Вы не теряете ее при возврате из подпрограммы, а возвращая указатель на эту структуру при выходе Вы можете передавать его в любое место Вашей системы для использования или изменения ее данных. В этом смысле для KLisp нет разницы объявлять переменные внутренними или внешними. Программа KLisp обычно строится из нескольких блоков подпрограмм, каждый из которых специализируется на обработке одной структуры дан- ных. Hа основании взаимодействия этих блоков достигается решение по- ставленной задачи. Такой подход чем то напоминает объекты в С++.