Глава 1. Данные.

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

    Идеология типизации данных в KLisp перенесена из языка Си.  Но  в
отличии от него имеет несколько иное  функциональное  значение.

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

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

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

               1.1. Типы данных. Массивы. Инициализация.

      В KLisp используется стандартный набор численных данных:
           - (char), (int), (long), (folat );
специализированные типы указателя и функции:
           - (addr ), (func );
и объединяющая конструкция данных:
           - (struct ).
Кроме того могут вводиться другие типы данных. Идея состоит не в коли-
честве байт выделяемых в памяти, а в определении функциональной  нап-
равленности этих данных.  Hапример,  если к численным данным возможно
применение функций сложения и вычитания, то по отношению к  указателю
эти действия теряют смысл.

      Перед использованием переменной в программе ее нужно  объявить,
то есть указать ее тип и оригинальное имя. Все программные  конструк-
ции KLisp, в том числе и объявление переменных, заключаются в круглые
скобки. Например, для того чтобы указать что переменная А будет иметь
тип int необходимо сделать следующую запись:

     (int A)

     Имя переменной может быть произвольной длины, но значащими явля-
ются только первые 9 знаков. В имени  допускается использовать  боль-
шие и маленькие английские буквы,  цифры (только не первым знаком)  и
знак '_'.

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

      (int A B C )

      Данные могут объединяться в массивы. Если  необходимо  объявить
массив переменных, то после имени следует указать его размер  в квад-
ратных скобках:

    (int A[4])

      В  KLisp  не  допускается использование  многомерных  массивов,
однако этот недостаток с успехом компенсируется использованием струк-
тур данных.

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

      В этом пункте мы приведем сводное описание основных типов пере-
менных. Некоторые из них будут подробнее рассмотрены ниже.

      Транслятор KLisp предоставляет  возможность  инициализации  пе-
ременных во время объявления. Инициализация состоит в том, что  пере-
менной присваивается определенное начальное значение на  этапе  тран-
сляции. Не все типы переменных можно инициализировать при объявлении.
Об особенностях инициализации каждого типа будет рассказано ниже.

      Общая схема инициализации выглядит следующим образом:

      ((<тип>) (<имя1> <значение>) <имя2> ....)

      Например, запись  ((int) (A 1) B) означает, что объявляются две
переменные: переменная А - инициализируется значением 1; переменная В
неинициализируется, ее значение будет равно 0.

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

      Если объявляется массив переменных,то допускается инициализация
элементов этого массива, начиная с нулевого.  Для  этого все значения
пишутся внутри скобок через пробел. Например, в результате  инициали-
зации:

      ((int) (A[2] 1 2))

получим: A[0] равно 1, A[1] равно 2.

      Можно инициализировать не все элементы массива, а только первые.
Тогда остальные будут иметь нулевые значения.



      Если при инициализации массива не указан его  размер,  он будет
установлен равным числу синициализированных элементов. Например,

      ((int) (A[] 1 2))

длина массива А будет равна 2.

    Есть еще один вид инициализации в теле программы. Когда в качест-
ве параметров оператора Вы указываете конкретные значения. Например,

      (gotoxy 1 1)(msg "***" "***")

выводит сообщение на экран с 1 позиции  первой  строки. Однако  такая
инициализация считается в  KLisp  плохим  тоном,  поскольку это  при-
водит к неэкономному расходу памяти. Желательно многократно встречаю-
щиеся параметры задавать  через  переменную  синициализированную  при
объявлении. Например, объявлять

      ((int)  (A 1))
      ((char) (S[] "***"))

а в программе писать

      (gotoxy A A)(msg S S)  .

      Итак, приступим к описанию типов:

      (char ) : Беззнаковое целое.
      -------   Длина: 1 байт.
                Предел значений: от 0 до 255.
                Возврат по (type ): '*' (42).
                Инициализация: да.

     Переменные типа (char ) могут инициализироваться числами от 0 до
255 или знаками типа 'a'.

    ((char) (С 'a') (M 27))

      Массивы этого типа  можно   инициализировать  строками  знаков.
Число знаков в строке не должно превышать указанного размера массива.
Например,

      ((char) (S[6] "******"))

      Если размер массива не указан, то он  будет определяться длиной
инициализирующей строки плюс 1 (в конце строки автоматически добавля-
ется знак 0). Например, при записи

      ((char) (S[] "******"))

длина массива S будет равна 7.

      Допускается инициализация в несколько строк. Например,

      ((char) (S[] "******" "=====" ))

      Имеется несколько специальных знаков,которые особым образом ин-
терпретируются при инициализации строк. Они задаются с помощью '\'  -
обратной косой черты. К ним относятся:


   _""""""""""""'"""""""'""""""""""""""""""""ї
   _обозначение _  код  _   Примечание       _
   _"""""""""""":""""""":""""""""""""""""""""_
   _  \r        _  10   _  Возврат каретки.  _
   _  \n        _  13   _  Перевод строки.   _
   _  \"        _  "    _  Двойные кавычки.  _
   _  \\        _   \   _  Обратная косая    _
   _            _       _       черта.       _
   _""""""""""""_"""""""_""""""""""""""""""""T

    (int  ) : Знаковое целое.
    -------   Длина: 2 байта.
              Предел значений: от -32768 до 32767.
              Возврат по (type ): '&' (38).
              Инициализация: да.

    Переменные типа (int ) могут инициализироваться числом со знаком.
Например,
    ((int) (C 2) (D -25))

    (long ) : Знаковое длинное целое.
    -------   Длина: 4 байта.
              Предел значений:
              Возврат по (type ): '%' (37).
              Инициализация: да.

    Переменные типа (long ) могут инициализироваться числом  со  зна-
ком, в конце числа ставиться буква 'l'.
Например,
    ((long) (C 2l) (D -25l))

    (float ): Знаковое с плавающей точкой.
    -------   Длина: 4 байта.
              Предел значений: от 3.4Е-38 до 3.4Е+38
              Возврат по (type ): '$' (36).
              Инициализация: да.

    Переменные типа (float ) могут инициализироваться числом  со зна-
ком, в конце числа ставиться буква 'f'.
Например,
    ((float ) (C 2f) (D -2.5f))

    Можно опускать знак 'f' в конце числа, если используется знак '.'
(для отделения целой части от дробной) или  'E' (для указания  степе-
ни). Например,

    ((float ) (C 2.5) (D 2E-2))


    (addr ):  Указатель на переменную.
    -------   Длина: 4 байта.
              Предел значений: --
              Возврат по (type ): '[' (91).
              Инициализация: нет.

    Подробно об идеологии использования указателей в KLisp  рассказы-
вается в пункте 1.3.


    (func ):  Указатель на функцию.
    -------   Длина: 2 байта.
              Предел значений: --
              Возврат по (type ): ']' (93).
              Инициализация: нет.

    Указатель адреса программы в процессе. Он  используется в  опера-
торах (call ) и (gofunc ) вместо имени функции для ускорения  доступа
к подпрограммам. Инициализируется  при исполнения программы строковой
переменной - именем  функции (длина имени  до 9  знаков),  с  помощью
оператора (setq ). Например,

    (func Fnc)
    (setq Fnc "aaaa")

    (struct ):  Тип, объединяющий сложные конструкции данных.
    -------   Длина: зависит от описания.
              Предел значений:  --
              Возврат по (type ): '.' (46).
              Инициализация: нет.

    Описанию возможностей языка KLisp по работе со сложными конструк-
циями данных мы посвятим следующий пункт данной главы.

              1.2.  Структуры данных.

    Одним из требований, предъявляемых к языку KLisp, как Вы помните,
было требование предоставления возможности обработки сложных структур
данных. Если конкретизировать поставленную задачу, то она будет сос-
тоять в следующем:

    1. Объединение в структурах произвольного количества любых  типов
данных и их массивов.
    2. Возможность построения вложенных структур неограниченной  глу-
бины.
    3. Предоставление  удобной  системы адресации и доступа к сложным
конструкциям данных.

    Идеология использования структур  в  KLisp  параллельна идеологии
объектов С++. Каждый  объект обладает набором свойств  как  численных
так и функциональных. Совокупность всех этих  свойств  объединяется в
обособленный блок данных, который можно целиком передавать  на  обра-
ботку той или иной программе.


               1.2.1. Описание конструкции структур.

    Как и в любом другом языке, объявлению структуры предшествует  ее
описание. Описание состоит в том чтобы указать  тип  и последователь-
ность расположения данных в структуре, а также размер вложенных  мас-
сивов.

    Описание структуры имеет такой вид:

    ((struct <имя описываемой структуры>)
  <произвольный набор данных объединенных структурой>
                ........
    )

Например, описание
    ((struct Gt)
     (int A B)
     (char Str[24] k)
    )
соответствует тому что в структуре с именем Gt содержаться данные A и
B типа (int), массив Str в 24 байта и переменная k типа (char ).

    Условимся A, B, Str, k, - называть  элементами  структуры,  а  их
имена - именами смещений в структуре. Понятие  смещения  вытекает  из
физического смысла расположения данных в памяти в виде последователь-
ности байт - это расстояние от начала структуры до ее элемента с дан-
ным именем.

    Для объявления переменной такого типа, например, в описании внеш-
них переменных программы следует сделать запись

    ((extern)
    ((struct Gt) A B[4])
    )
Тогда A будет  рассматриваться системой  как отдельный блок данных, а
B[4] как массив из четырех структур. Условимся называть их экземпляра-
ми структур.

    Если внутри структуры необходимо объявить  другую  структуру,  то
она должна быть описана предварительно. Например,

    ((struct Gt)
     (int A B)
     (char Str[24] k)
    )
    ((struct At)
     (int M N)
     ((struct Gt) A B[4])
    )

    Заметим, что при описании структур имена входящих в нее  перемен-
ных совпадать не должны. Однако  во вложенных структурах это совпаде-
ние допускается (переменные A и B в структурах Gt и  At).  Транслятор
не пропустит также конструкцию вложения структуры самой в себя.

    Допускается инициализация переменных во время описания  структур.
Эти значения запоминаются,  и, в дальнейшем,при объявлении переменных
такой  структуры  инициализированные значения будут сохранены во всех
экземплярах этой конструкции. Например,

    ((struct Gt)
     ((int) (A 1) B)
     ((char) (Str[] "******") )
    )
    ((extern)
    ((struct Gt) M N[4])
    )

При этом значение переменной A структуры Gt и в  экземпляре  M  и  во
всех элементах массива структур N будут  равны  1.  Тоже  касается  и
переменной Str.

                 1.2.2. Обращение к элементам структур.

    Обращение к элементу структуры в программе  осуществляется  путем
описания  последовательности  имен  смещений в структурах разделенных
точкой  - '.'.

   Приведем примеры допустимых написаний имен переменных.

   M.A , M.B , M.Str, M.Str[2] - обращение к элементам структуры M.

   N.A , N[2].A  - обращение к переменной A соответственно нулевого и
второго элемента массива структур N.

   Записи N.A и N[0].A - эквивалентны.

   Во вложенных  конструкциях  при обращении  к элементам  внутренних
структур запись смещений также пишется через точку.

   Например, для структуры At при объявлении
    ((extern)
    ((struct At) K H[4])
    )

    K.M - обращение к элементу M экземпляра K структуры At;
    K.A.A - обращение к переменной A структуры Gt вложенной в At  как
элемент A;
    K.A.Str[2], K.B.A , K.B[2].Str[20], H[2].B[1].Str[1] и т.д.

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

    K.A.Str[  2 ] или с переносом строки H[
2 ].B[1 ].Str[1]
    Но нельзя -  K. B.A

    Если  в конструкции структуры нет массива структур, а Вам необхо-
димо сделать  разрыв  или перенос на другую строку, то это можно сде-
лать в любом месте после имени элемента, вставив скобки пустого
массива.
    Например,

    K.A[  // Здесь можно написать комментарии
].A

    Внутри скобок [] (описания номера  в  массиве)  транслятор  KLisp
допускает написание произвольной программной конструкции. Причем, ес-
ли внутренний оператор возвращает число (int ) или (char  ),  то  оно
будет рассматриваться как указание номера элемента массива. Например,

   K.B[ (op1 (op2 ...) ...) ].Str


       1.3.  Идеология использования указателей в KLisp.

    Понятие  указатель  в KLisp  заимствовано  из  языка Си и  подра-
зумевает некоторую переменную в которой находится адрес (место распо-
ложения  в  памяти) другой переменной. Использование указателей  зна-
чительно повышает эффективность обработки  данных, особенно в сложных
конструкциях. Хотя KLisp считается  интерпретатором, обращение к дан-
ным на этапе исполнения производится  не по имени, а по адресу распо-
ложения в памяти,  что значительно увеличивает  скорость работы прог-
раммы. Транслятор  группирует все объявленные данные программы в еди-
ный блок и при инициализации указателя  в него заносится адрес указы-
ваемой переменной в памяти. В  дальнейшем,  любой  оператор, которому
будет передан в качестве параметра  указатель, автоматически  заменит
его на указанную переменную.

    Возможность анализа конструкции данных на этапе исполнения позво-
ляет  применить  в  KLisp  систему   безтиповых  указателей.  Другими
словами, переменная  - указатель  может  содержать адрес данных любо-
го типа, в том числе и структуры, и произвольным образом менять  этот
адрес и тип указываемой переменной в процессе исполнения программы. В
этом  состоит  коренное  отличие  KLisp от своего прародителя Cи, где
тип указателя задается строго во время  его  объявления.  Хотя  такое
вольное трактование возлагает на программиста бОльшую ответственность
по контролю за содержанием указателя.

    Переменные указатели, как уже отмечалось, выделены в KLisp  в от-
дельный тип данных и описываются следующим образом:

       (addr A B[4])

    Указатель не может быть инициализирован  во время объявления. По-
скольку неизвестно, где будет располагаться переменная при  загрузке.
Инициализация осуществляется во время исполнения  оператором (Adr  ).

         1.3.1.  Доступ к данным и элементам массива.

    Указатель инициализируется адресом переменной и смещением по мас-
сиву. В этом состоит еще одно отличие понятия указателей от языка Си.
Через  указатель  KLisp  можно  иметь  произвольный  доступ  к любому
элементу массива переменных, не забывая при этом на что он указывает.
Это особенно важно при работе с массивами структур данных. Кроме того,
перемещаясь по элементам массива операторами (++ A) (-- A) вы никогда
не перейдете в другой блок данных, а  будете  ходить  по  кольцу,  но
всегда сможете определить  свое местоположение  оператором  (Sm A)  и
встать на конкретный элемент массива оператором (>< A).

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

      Например, при определении

      (Adr A X[4])  - А будет указывать на 4 элемент в массиве X.
Запись
      A.[1] будет указывать на 5 элемент,
а     A.[-1] на третий. При этом значение указателя не меняется. Знак
'.' между именем указателя и значением массива обозначает  что массив
относится к указанной переменной. Если требуется выбрать значение  из
массива указателей, следует писать так B[2].[1]


    Если указатель инициализирован адресом переменной, которая в свою
очередь является указателем,  то  иметь доступ к данным в таком двух-
уровневом указателе можно посредством оператора (Con A).

    Подробнее об операторах действий над указателями будет рассказано
ниже.

         1.3.2.  Доступ к структурам.

    Нетрудно заметить, что написание в программе имен сложных  конст-
рукций данных выглядит очень громоздко, и усложняет тем самым понима-
ние программы. БОльшие возможности  указателей по  доступу  к  данным
приводят к уменьшению количества операторов и  тем  самым  к  большей
эффективности интерпретатора.  Указатель может  быть  инициализирован
как адресом самой  структуры, так и адресами ее внутренних элементов.

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

Hапример,
    ((struct Gt)             // объявлена структура
     ((int) (A 1) (B 1))
     ((char) (Str[] "******") )
    )

   (addr A)                // указаны переменные
   ((struct Gt) S))

   (Adr A S)               // Указатель на структуру
                           // Опреаторы
   (gotoxy A.Gt.A A.Gt.B) (msg A.Gt.Str )
напечатают на экране в позиции 1,1 содержание S.Str - "******"

      Кажется, что конструкция усложнилась, но, если инициализировать
указатель элементом структуры,

   (Adr A S.Str)

доступ к нему будет проще. К любому элементу массива S.Str можно обра-
титься через A.[n] и чем выше вложенность структур, тем большая эффек-
тивность использования указателей. Кроме того, все возможности указа-
телей по доступу к массивам, полностью распространяются и на  массивы
структур и массивы элементов структур.