Delta-Pascal 2.0

                      (14.02.94)


     В настоящем документе приведено описание языка Паскаль,
реализованного в версии 2.0. В приложении приведены особенности
данной реализации.

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


               Как запустить транслятор


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

     Другой способ запуска - указать имя файла в командной
строке: PAS <имя>. В этом случае по умолчанию листинг
программы не выводится на экран, и имеется возможность 
с помощью ключей задавать некоторые режимы.
     Ключ /S - включает выдачу листинга на экран
     Ключ /N - подавляет запись выполняемого файла
               после успешной трансляции
     Ключ /G - вызывает запуск программы после успешного
               завершения трансляции

     В обоих случаях расширение .PAS в имени файла можно
опускать. Например:

   PAS/NG MUAR   - не будет записывать файл на диск, но
                   запустит программу

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

     Трансляцию можно прервать, нажав кнопку "1" на
функциональной клавиатуре.

     По окончании работы транслятора в переменной ДОСа #Q 
находится код возврата, который равен нулю в случае
трансляции без ошибок.

     В текущем каталоге при трансляции должны находиться
файлы DPERRORS.MSG и DPERRORS2.MSG - в них содержатся
сообщения об ошибках (иначе Вы полуите только код ошибки).



                     Описание входного языка


     Общая структура программы не отличается от стандартной и
имеет вид:

     PROGRAM <имя программы>;
     USES  <имена библиотек>
     LABEL <описание меток>
     CONST <описание констант>
     TYPE  <описание типов>
     VAR   <описание переменных>
     <описание процедур>
     BEGIN
       <операторы>
     END.

     Между заголовком (программы или процедуры/функции) и ее
телом разделы описаний могут идти в любом порядке и повторяться.
Имя программы, указанное в заголовке, ни на что не влияет,
однако рекомендуется, чтоб оно совпадало с именем файла.
Программа обязательно заканчивается точкой.

     Теперь о каждом разделе поподробнее.


              Раздел подключения библиотек

     USES <имя модуля> [, <имя модуля> ];

     В данной версии <имя модуля> - это текстовое имя файла
библиотеки. Библиотека - это файл с расширением .REL,
порожденный ассемблером в системе "Спрайт". Если библиотека
находится не в текущем каталоге, то надо указывать путь.
Например:

     USES "GR256.REL";

     или

     USES "\GR\GR256.REL";


                     Раздел меток


     LABEL <имя метки> [, <имя метки> ] ;

     Именем метки, в отличие от стандарта, может быть только
идентификатор (а не целое число). Указанные имена резервируются
под метки и не могут (!) использоваться по другому (в качестве
переменной, типа, или имени процедуры). Метки, описанные в
процедуре, локальны внутри нее. Метки, определенные в блоке
программы относятся только к телу главной программы и не могут
использоваться в процедурах. Поэтому рекомендуется раздел LABEL
ставить между последним описанием процедуры и телом программы.
В программе метка может стоять перед любым оператором:
<метка> : <оператор>;
а переход на нее производится оператором GOTO <метка>;
             

                    Раздел констант

     CONST
           <имя> = <константа>;
                ...
           <имя> = <константа>;

     В данном разделе определяются значения констант. Имя -
любой идентификатор, константа - число. После этого определения
имя константы можно использовать везде, где может быть число.
К сожалению, в качестве константы нельзя писать выражение.


                    Раздел определения новых типов


     TYPE 
          <имя> = <тип>;
               ...
          <имя> = <тип>;

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

     Стандартными простыми типами являются:

     INTEGER - целое число со знаком ( -32768 .. 32767 )
     REAL    - вещественный тип
     CHAR    - символьный тип ( один символ )
     BOOLEAN - логический тип ( TRUE или FALSE )

     А также:

     BYTE    - короткое целое число без знака ( 0..255 )
     WORD    - целое число без знака ( 0 .. 65635 )
     LONG    - длинное целое без знака (4 байта)

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

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


     Для описания типа массива используется конструктор вида

     ARRAY [ <число> .. <число> ] OF <тип>

Компонентом массива может быть либо любой простой (стандартный)
тип, либо любой уже определенный (в т.ч. массив или запись). 


     Для описания типа запись используется конструктор вида

     RECORD
       <поле> : <тип>;
             ...
       <поле> : <тип>
     END


     Для описания типа указатель используется конструктор вида

     ^ <тип>


                    Раздел описания переменных


     VAR
        <имя> : <тип>;
        ...
        <имя> : <тип>;

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


                 Тело программы или процедуры

     Тело программы или процедуры состоит из операторов,
заключенных между операторными скобками BEGIN и END. Операторы
разделяются точками с запятой. После последнего ENDа программы
ставится точка, а после ENDа процедуры ставится точка с
запятой.

     Ниже приводятся описания операторов языка Паскаль,
реализованных в данной версии.

     Составной оператор:

     BEGIN
        <оператор>;
        ...
        <оператор>
     END

используется в тех случаях, когда требуется поместить несколько
операторов там, где синтаксисом разрешен только один. Вся эта
конструкция вместе с операторами, находящимися между BEGINом и
ENDом, воспринимается как один оператор.

     Оператор присваивания:

     <имя> := <выражение>

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

     Условный оператор:

     IF <условие> THEN <оператор>

     или

     IF <условие> THEN <оператор> ELSE <оператор>

Здесь и далее <условие> - это выражение, результат которого
имеет тип BOOLEAN. Если условие истинно (TRUE), то выполняется
оператор, следующий за THEN. Если же условие ложно, то
выполняется оператор после ELSE (а в случае отсутствия ELSE -
ничего не выполняется). В случае, когда после THEN стоит
условный оператор, может возникнуть неоднозначность с
принадлежностью ELSE, в этом случае ELSE относится к
ближайшему (вложенному) условному оператору. Например:

     IF X<>0 THEN IF X<0 THEN X:=-1 ELSE X:=1;

В этом примере ELSE относится к IF X<0 THEN.

     Операторы цикла:

     WHILE <условие> DO <оператор>
     REPEAT <операторы> UNTIL <условие>
     FOR <имя>:=<выражение> TO     <выражение> DO <оператор>
     FOR <имя>:=<выражение> DOWNTO <выражение> DO <оператор>

Оператор в цикле WHILE повторяется пока истинно условие. Оно
проверяется перед каждым выполнением оператора, а если условие
было ложно еще до начала выполнения цикла, то оператор не
выполняется ни разу.

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

   В цикле FOR переменная изменяется от начального значения до
конечного (включая оба эти значения). Переменная цикла при
каждом повторении увеличивается на единицу, если использовалось
слово TO, и уменьшается, если стоит DOWNTO. В случае, если
начальное значение больше конечного, тело цикла не выполняется
ни разу. Начальное значение может отстуствовать (вместе со
знаком присваиванич), тогда оно принимается равным единице.

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

   Для досрочного возврата из процедуры/функции используется
оператор RETURN. Если возврат происходит из функции, то
результат уже должен быть присвоет имени функции, иначе
будет возвращен ноль (на это свойство, однако, я бы не
рекомендовал закладываться).

   Для внезапного завершения выполнения программы может
использоваться оператор EXIT или HALT - они равнозначны.
В операторе можно указать код возврата, который попадет
в переменую ДОСа #Q и будет доступна для проверки. Например:
HALT(10) или EXIT(10) - вернут 10, а без параметра - 0
(что обычно означает успешное завершение).

   Имеются стандартные функции:
      KEY()    - ждет нажатия клавиши и выдает символ
      CHR( X ) - результатом является символ с кодом X
      ORD( C ) - результатом является код символа C
      SQRT( X )  - результатом является квадратный корень X
      TRUNC( X ) - результатом является целая часть X

   Для преобразования типов используется запись вида:
      <тип>( <выражение> )

   Например: BYTE(X)+1     REAL(X)/8

   Для ввода с клавиатуры и вывода на экран используются
операторы
     WRITE   ( <выражение> [, <выражение> ] );
     WRITELN ( <выражение> [, <выражение> ] );
     READE   ( <переменная> [, <переменная> ] );
     READLN  ( <переменная> [, <переменная> ] );



Расширения языка:

   - реализовано присваивание с увеличением (уменьшением):

        А += 2;  эквивалентно А := А+2;
        А -= 2;  эквивалентно А := А-2;

   - имеется арифметический условный оператор:

        A := IF X >= 10 THEN 1 ELSE 2; 

в результате его выполнения переменная A получит значение
либо 1, либо 2 - в зависимости от значения переменной X.

   - реализован оператор перебора SELECT, который равносилен
последовательности условных операторов:

        SELECT
          <условие> : <оператор>
          <условие> : <оператор>
             . . . . . . . . 
          <условие> : <оператор>
          <условие> : <оператор>
        ELSE          <оператор>

Из указанных альтернатив выполняется только одна. Условия
проверяются в порядке написания. Вместо слова ELSE может
использоваться слово OTHERWISE.

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


Особенности реализации:

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

   - в цикле FOR конечное значение вычисляется при каждом
повторении (а не один раз перед началом), что приводит к
замедлению выполнения цикла, если конечное значение - сложное
выражение (особенно, если в нем используется индексация,
умножение, деление или вызов функции);

   - не реализована возможность описания вложенных процедур
(и никогда не будет реализована), однако нет препятствий
для использования рекурсии;


ПРИЛОЖЕНИЕ. Для тех, кого интересуют низкоуровневые
            возможности языка

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

     Далее под словом будет пониматься значение двух байтов,
размещенных в памяти подряд, причем первый из них - младший,
а второй - старший. Адресом слова является адрес первого
(младшего) байта.

     При работе с адресами (и не только) бывает удобно
использовать шестнадцатеричные числа. Для обозначения их
надо перед числом поставить ¤. В этой версии при записи
шестнадцатеричных чисел можно использовать только большие
буквы (и цифры, конечно).

     Для размещения переменной по абсолютному адресу
необходимо указать этот адрес при ее описании, например:

   VAR
      KBD     AT ¤C000 : CHAR;
      STROB   AT ¤C010,
      SPEAKER AT ¤C030 : BYTE; 

   TYPE
      T_Window = RECORD
                   Left,
                   Bottom,
                   Top     : BYTE;
                 END;
   VAR
      Window AT ¤20 : T_Window;

   BEGIN
      Window.Bottom := 10;

      

     Для доступа к ячейкам памяти имеются два способа: либо
через указатели, либо с помощью псевдомассивов MEM и MEMW.

     Псевдомассивы MEM и MEMW позволяют обращаться к байтам
и словам памяти. Например:

     VAR
        A : WORD;
        i : BYTE;
     BEGIN
        ...
        A := MEMW[ ¤3000 ];
        MEMW[ ¤3000 ] := 0;

        MEM[ ¤C723 ] := 0;

        Buf := ¤6000;
        FOR i := 0 TO 10 DO MEM[ BUF+i ] := 0;
        ...

     Через указатели можно получить доступ как к байтам и
словам, так и к более сложным структурам. Сначала указателю
надо присвоить адрес объекта, после чего к нему можно будет
обращаться. Например:

     TYPE
        WWORD = RECORD
                   Lo, Hi : byte;
                END;
     VAR
        Pnt : ^WWORD;
     BEGIN
        Pnt := ¤1234;
        x := Pnt^.Lo;
        Pnt^.Lo := 1;

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

     Запустить подпрграмму по абсолютному адресу можно с
помощью оператора ASM, однако передать параметры ей или
получить результат можно только по фиксированным ячейкам:

     ASM( ¤327 );

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

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

     2) написание псевдо-перемещаемых программ на ассемблере,
независимых от места в памяти, куда их загрузят (т.е. это
накладывает сильные ограничения: нельзя использовать
индексную адресацию, подрограммы и т.д.). Описание процедур
из таких программ будет выглядеть так:

PROCEDURE P1( A : INTEGER; C : CHAR ); EXTERNAL "PROG.PRG";
PROCEDURE P2( B : INTEGER; C : BYTE ); EXTERNAL;
PROCEDURE P3( A : BYTE;    B : WORD ); EXTERNAL;
FUNCTION  P4( A : CHAR     ) : CHAR;   EXTERNAL;

     Ассемблерная программа должна начинаться с векторов на
каждую из процедур в порядке их следования. Каждый вектор
должен занимать 3 байта, но т.к. команды JMP использовать
нельзя, рекомендую использовать пары CLC BCC.

     Результат функции может быть либо байт (BYTE, CHAR,
BOOLEAN), либо слово (WORD, INTEGER, указатель). Возвращаемый
результат берется из регистров AY, причем в A - младший байт,
а в Y - старший.

     Передаются параметры блоком по два байта на каждый
параметр (если байт - то в младшем). Адрес блока находится
в ячйке ¤C0. Все подпрограммы могут использовать ячейки
нулевой страницы ¤A0-AF по своему усмотрению.

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

   MOVE( <адрес откуда>, <адрес куда>, <кол-во байт> );