  3.8. Сопрограммы

     Представление  задачи в виде взаимодействующих сопрограмм (процессов) в
  теории  программирования  стало  уже  традиционным [15,16]. Но на практике
  этот метод используется редко, так как большинство распространенных языков
  высокого  уровня  не  имеют необходимых конструкций. В стандарт языка Форт
  также  не  включены  средства  для  сопрограммной  работы.  Вызвано  это в
  основном  тем, что такие средства обычно зависят от конкретного приложения
  и  могут  варьироваться  очень  сильно.  Тем  не  менее,  используя только
  стандартные  для  большинства  систем слова, можно реализовать необходимые
  конструкции.

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

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

     Будем считать, что адрес области памяти текущей сопрограммы находится в
  переменной  Т-С.  Используя  ее,  введем  слова  для  доступа к полям этой
  области.  Для  простоты  в  данной  реализации  отводится  участок  памяти
  размером  128  байт.  В  его  начале  находятся: Т-ПРОГ - указатель начала
  шитого  кода,  соответствующего  исполняемой программе, Т-СТЕК - указатель
  вершины  стека  данных на момент последней приостановки данной сопрограммы
  (указатель вершины стека возвратов сохраняется на стеке данных) и Т-ВЫЗВ -
  адрес  аналогичной  области для сопрограммы, вызвавшей данную. В остальной
  памяти участка размещаются стек данных и стек возвратов по 60 байт каждый.

     Внешняя  программа,  в  рамках  которой  действует описываемый механизм
  сопрограмм,  представлена  6-байтной  областью ВНЕШ, в которой размещаются
  перечисленные  выше  значения.  В  качестве  своих  стеков эта сопрограмма
  использует исходные стеки форт-системы.

     Перед  началом  работы  все сопрограммы должны быть проинициализированы
  словом  START (старт), завершение работы сопрограммного механизма вызывает
  слово STOP (стоп).

     Сопрограмма  определяется  через  слово  СОПРОГРАММА, которое по своему
  употреблению  аналогично  двоеточию.  Вслед  за ним идет имя сопрограммы и
  форт-текст,  задающий  требуемую  программу.  Обычно она состоит из цикла,
  внутри  которого  используется слово RESUME (возобновить) для приостановки
  данной  сопрограммы  и  возобновления  сопрограммы, ее вызвавшей. Выход по
  EXIT  или  ; приводит к завершению работы всего сопрограммного механизма и
  возобновлению текстовой интерпретации.

          QUAN Т-С   ( АДРЕС ОБЛАСТИ ТЕКУЩЕЙ СОПРОГРАММЫ)
          : Т-ПРОГ ( ->A:АДРЕС НАЧАЛА ПРОГРАММЫ)  Т-С ;
          : Т-СТЕК ( ->A:АДРЕС УКАЗАТЕЛИ СТЕКИ )  Т-С 2+ ;
          : Т-ВЫЗВ ( ->A:АДРЕС ВЫЗВАВШЕЙ СОПРОГРАММЫ) Т-С 4 + ;
          CREATE ВНЕШ 6 ALLOT ( ОБЛАСТЬ ДЛЯ ВНЕШНЕЙ ПРОГРАММЫ)
          : RESUME ( -> ОСТАНОВИТЬ ТЕКУЩ.И ВОЗОБН. ВЫЗВАВШУЮ)
           RP@  SP@  Т-СТЕК ! ( СОХРАНИТЬ СОСТОЯНИЕ ТЕКаВЕИ)
           Т-ВЫЗВ @  TO Т-С  ( ПЕРЕКЛЮЧИТЬСЯ НА ВЫЗВАВШУЮ ДАННУЮ)
           Т-СТЕК @  SP!  RP!  ; ( ВОЗОБНОВИТЬ ПРИОСТАНОВЛЕННУЮ)
          : STOP ( -> ЗАВЕРШЕНИЕ ВСЕХ СОПРОГРАММ) QUIT ;
          : (START) ( A:АДРЕС ОБЛАСТИ СОПРОГРАММЫ-> ИНИЦИАЛИЗ)
            DUP TO Т-С  Т-ПРОГ @ OVER 126 + !  DUP 126 +
            OVER 66 + !  66 + Т-СТЕК !  ВНЕШ TO Т-С  ;
          : СОПРОГРАММА ( -> ОПРЕДЕЛЕНИЕ СОПРОГРАММЫ) CREATE HERE
             DUP 132 + , 126 ALLOT HERE 2+ , ['] STOP , (START) ]
             DOES> ( PFA:ОБЛАСТЬ СОПРОГРАММЫ->) RP@* SWAP >R
             SP> Т-СТЕК !  Т-С R> TO Т-С Т-ВЫЗВ !
             Т-СТЕК @ SP! RP!  ;
          : START ( -> ИНИЦИАЛИЗИРОВАТЬ СОПРОГРАММУ)
              ' >BODY [COMPILE] LITERAL   STATE @
                IF COMPILE (START) ELSE (START) THEN ;  IMMEDIATE

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

     Применяя  описанный  механизм,  определим две сопрограммы ВВОД и ВЫВОД,
  которые  обмениваются между собой через однобайтный буфер ТЕК-СИМ, и слово
  ЗАДАЧА, которое выполняет требуемую перепись данных:

          QUAN ТЕК-СИМ   ( ОЧЕРЕДНОЙ ОБМЕНИВАЕМЫЙ СИМВОЛ)
          CREATE ВХОД-БУФ 80 ALLOT  ( БУФЕР ВВОДА)
          CREATE ВЫХ-БУФ  64 ALLOT  ( БУФЕР ВЫВОДА)
          СОПРОГРАММА ВВОД
              ОТКРЫТЬ-ВВОД  BEGIN  ВХОД-БУФ ЧИТАТЬ WHILE
              ВХОД-БУФ 80 + ВХОД-БУФ DO  I С@ TO ТЕК-СИМ
                              RESUME LOOP
              REPEAT  ЗАКРЫТЬ-ВВОД  ЗАКРЫТЬ-ВЫВОД ;
          СОПРОГРАММА ВЫВОД
              ОТКРЫТЬ-ВЫВОД BEGIN  ВЫХ-БУФ 64 + ВЫХ-БУФ DO
                 ТЕК-СИМ I С!  RESUME  LOOP
              ВЫХ-БУФ ПИСАТЬ  AGAIN ;
          : ЗАДАЧА
              START ВВОД  START ВЫВОД
              BEGIN ВВОД ВЫВОД AGAIN ;

     Два  ряда  слов  ОТКРЫТЬ-ВВОД,  ЧИТАТЬ,  ЗАКРЫТЬ-ВВОД  и ОТКРЫТЬ-ВЫВОД,
  ПИСАТЬ,  ЗАКРЫТЬ-ВЫВОД  обеспечивают  взаимодействие  с входным и выходным
  файлами.  Слова  ЧИТАТЬ и ПИСАТЬ требуют в качестве параметра адрес буфера
  (ВХОД-БУФ  для  ввода  и ВЫХ-БУФ для вывода), а слово ЧИТАТЬ , кроме того,
  возвращает логический результат - признак успешного завершения чтения.

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

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

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

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

     4.  Можно  предусмотреть  вызов асинхронного выхода "Окончание задачи".
  Идентификация этого выхода может быть как статической, так и динамической.

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

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

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