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

Длинный комментарий в начале написан не по данному случаю, а просто так, в порядке размышлений.

## Код:
```
(**
   Буфер (Carrier) это динамический массив из байт. Специфика его в том, что он позволяет работать с собой как с линейным буфером, хотя на самом деле представляет из себя односвязанный список. В каждом элементе этого списка содержится очередная порция данных. Такая организация позволяет быстро добавлять и удалять сегменты, что очень полезно при работе с потоками информации неравномерной длины (например, обмен данными в сети). Преимущества по сравнению с традиционным монолитным буфером таковы: (1) размер буфера может динамически меняться, нет нужны угадывать "максимальный" размер для статичного буфера; (2) буфер может состоять из "кусочков", каждый из которых сформирован в момент получения информации. Для монолитного буфера пополнение означает выделение блока памяти бОльшего, чем прежний, размера, копирование прежней информации на новое место, добавление новой информации в конец нового блока, освобождение прежнего блока. Для кусочного буфера пополнение - это только выделение блока, необходимого для очередной порции данных, копирование только новых данных в выделенную область и установление связи с последним блоком буфера. При этом (а) не требуется единый блок памяти все бОльшего размера и (б) не образуются все бОльшие "дыры", которые не подойдут для очередного прироста буфера.
   Пример. Пусть мы из сети получаем три пакета по одному байту длиной. Мы хотим их все сложить в буфер для дальнейшей обработки. Как организовать буфер?
   Если мы создаем монолитный статичный буфер, то мы просто указываем в качестве размера буфера 3 байта, выделяем память заранее и последовательно ее заполняем по мере получения данных. Проблемы начнутся, если мы вместо 3 байт получим 4 или более. Традиционное решение - увеличить размер буфера - требует перекомпиляции программы. Кроме того, вряд ли возможно предусмотреть такой буфер, который бы сгодился на все случаи жизни, а если бы это и было возможно, то он скорее всего был бы слишком велик для повседневного использования, то есть неоправданно расходовал память.
   Если мы создаем монолитный динамический буфер, то ситуация развивалась бы следующим образом:
   Мы получаем первый байт. В начале памяти выделяется блок размером один байт. Данные копируются в память.
   Мы получаем второй байт. Буфер заполнен, его необходимо увеличить. В памяти ищется блок размером в два байта, он выделяется под буфер. Из старого буфера копируется прежняя информация, после чего на оставшееся место дописывается только что полученный второй байт. Прежний буфер освобождается, оставляя "дырку" размером в один байт.
   Мы получаем третий байт. Буфер заполнен, его необходимо увеличить. На месте первоначального буфера имеется область размером только в один байт, следовательно надо искать новую свободную область. В памяти ищется блок размером в три байта, он выделяется под буфер. Из старого буфера копируется прежняя информация, после чего на оставшееся место дописывается только что полученный третий байт. Прежний буфер освобождается, оставляя новую "дырку" размером в два байта.
   Таким образом, мы используем три байта под буфер, и еще три байта памяти мы уже не сможем использовать под буфер в дальнейшем. Если нам понадобится получить четвертый байт, мы выведем из употребления уже 6 байт. Конечно, эта память будет доступна для других операций, не требующих блоков большей длины, но все же довольно быстро растет вероятность того, что для очередного увеличения буфера монолитного блока памяти уже не будет.
   Наконец, если мы создаем динамический связанный буфер, мы каждый раз будем выделять только по одному байту - ровно столько, сколько необходимо для размещения очередной порции данных. Фрагментация памяти при этом минимальна. Недостатком данного способа является необходимость хранения дополнительных данных, описывающих каждый из блоков. Как минимум это указатель на следующий блок в связанном списке.

   Замечания по реализации:
   Следует доработать функцию Carrier.Delete, чтобы в ней не производилось копирование буфера. Это копирование только замедляет работу и ведет к использованию лишней памяти. После удаления копирования нужно будет доработать райдеры, чтобы они при создании или сбросе указывали не на начало первого буфера (значение -1), а на байт, предшествующий первому в Carrier.
*)

MODULE CommBuffer;
(** Copyright (c) Alexander Iljin, 2006. **)

TYPE
   (** Rider предназначен для чтения данных из Carrier. *)
   Rider* = POINTER TO LIMITED RECORD
      buff: Carrier;   (* буфер, который мы просматриваем *)
      pos-: INTEGER;   (* положение относительно начала буфера *)
      curr: CarrierData;   (* этот блок мы сейчас просматриваем *)
      currPos: INTEGER;   (* положение в блоке curr *)
      closed-: BOOLEAN;   (* если TRUE, райдер больше не работает *)
      next: Rider   (* следующий райдер в списке *)
   END;

   (** Logger - "журналист", который можно прикрепить к Carrier с помощью Carrier.SetLogger. Этот объект получает копию всех данных, добавленных в буфер с помощью Carrier.Append. Может использоваться для низкоуровневого слежения за обменом данными при отладке. *)
   Logger* = POINTER TO ABSTRACT RECORD
   END;

   CarrierData = POINTER TO RECORD
      next: CarrierData;
      data: POINTER TO ARRAY OF BYTE
   END;

   Carrier* = POINTER TO RECORD
      root: CarrierData;   (* первый блок данных буфера *)
      riders: Rider;   (* райдеры, читающие данный буфер *)
      logger-: Logger;   (* прикрепленный журналист *)
      total-: LONGINT   (* суммарное количество записанных в буфер байт *)
   END;


(* Logger *)

PROCEDURE (log: Logger) Appended- (IN buff: ARRAY OF BYTE), NEW, EMPTY;
(** Данная процедура вызывается для каждого блока данных, добавленного в Carrier, с которым связан данный Logger. *)


(* CarrierData *)

PROCEDURE NewCarrierData (len: INTEGER): CarrierData;
VAR bd: CarrierData;
BEGIN
(*   StdLog.String("LEN = "); StdLog.Int(len); StdLog.Ln; *)
   IF len <= 0 THEN RETURN NIL
   ELSE
      NEW(bd);
      NEW(bd.data, len);
      RETURN bd
   END
END NewCarrierData;


(* Rider *)

PROCEDURE (r: Rider) Init, NEW;
BEGIN
   r.pos := -1;
   r.closed := FALSE
END Init;


PROCEDURE (r: Rider) Eof* (): BOOLEAN, NEW;
BEGIN
   IF r.closed OR (r.buff.root = NIL) THEN
      RETURN TRUE
   ELSE
      IF r.pos = -1 THEN
         RETURN FALSE
      ELSE
         IF r.currPos < LEN(r.curr.data)-1 THEN
            RETURN FALSE
         ELSE
            RETURN r.curr.next = NIL
         END
      END
   END
END Eof;


PROCEDURE (r: Rider) Next* (): SHORTCHAR, NEW;
(* Если Eof() = FALSE, возвращаем очередной символ, иначе возвращаем 0X. *)
BEGIN
   IF r.Eof() THEN
      RETURN 0X
   END;
   
   IF r.pos = -1 THEN
      r.curr := r.buff.root;
      IF r.curr = NIL THEN
         RETURN 0X
      ELSE
         r.currPos := 0
      END
   ELSE
      IF r.currPos < LEN(r.curr.data)-1 THEN
         INC(r.currPos)
      ELSE
         r.curr := r.curr.next;
         r.currPos := 0
      END
   END;
   INC(r.pos);
   RETURN SHORT(CHR(r.curr.data[r.currPos]))
END Next;


PROCEDURE (r: Rider) ReadBytes* (VAR x: ARRAY OF BYTE; beg, len: INTEGER; OUT read: INTEGER), NEW;
(* Читаем len байт и помещаем их в массив x, начиная с позиции beg. В read возвращаем число прочитанных байт (read <= len). *)
BEGIN
   ASSERT(beg >= 0, 20);
   ASSERT(len >= 0, 21);
   ASSERT(beg+len <= LEN(x), 22);
   
   read := 0;
   WHILE (len > 0) & (~r.Eof()) DO
      x[beg] := SHORT(ORD(r.Next()));
      DEC(len);
      INC(beg);
      INC(read)
   END
END ReadBytes;


PROCEDURE (r: Rider) Deleted (n: INTEGER), NEW;
(* Из буфера r.buff только что было удалено n первых байт (n >= 0). *)
VAR i : INTEGER;
BEGIN
   IF (r.buff.root = NIL) OR (n > r.pos) THEN
      r.pos := -1
   ELSE
      DEC(r.pos, n)
   END;
   IF r.pos > -1 THEN
      r.curr := r.buff.root;
      i := LEN(r.curr.data);
      WHILE i <= r.pos DO
         r.curr := r.curr.next;
         INC(i, LEN(r.curr.data))
      END;
      r.currPos := r.pos-(i-LEN(r.curr.data))
   END
END Deleted;


PROCEDURE (r: Rider) Close*, NEW;
(** Удаляем данный райдер из списка обновляемых. После этого использовать его невозможно, зато он будет удален сборщиком мусора. *)
VAR rd : Rider;
BEGIN
   IF (r.buff # NIL) & (r.buff.riders # NIL) & ~r.closed THEN
      r.closed := TRUE;
      IF r.buff.riders = r THEN
         r.buff.riders := r.next
      ELSE
         rd := r.buff.riders;
         WHILE rd.next # NIL DO
            IF rd.next = r THEN 
               rd.next := r.next;
               RETURN
            ELSE
               rd := rd.next
            END
         END
      END
   END
END Close;


(* Carrier *)

PROCEDURE (c: Carrier) NewRider* (old: Rider): Rider, NEW;
VAR r: Rider;
BEGIN
   IF (old # NIL) & (old.buff = c) & ~old.closed THEN
      r := old
   ELSE
      NEW(r);
      r.buff := c;
      r.next := c.riders;
      c.riders := r
   END;
   r.Init;
   RETURN r
END NewRider;


PROCEDURE (c: Carrier) SetLogger* (log: Logger), NEW;
BEGIN
   c.logger := log
END SetLogger;


PROCEDURE (c: Carrier) Append* (IN buff: ARRAY OF BYTE; beg, len: INTEGER), NEW;
VAR
   i : INTEGER;
   bd, bdPtr : CarrierData;
BEGIN
   ASSERT(beg >= 0, 20);
   ASSERT(len >= 0, 21);
   ASSERT(LEN(buff) >= beg+len, 22);
   
   bd := NewCarrierData(len);
   i := 0;
   WHILE len > 0 DO
      bd.data[i] := buff[beg];
      INC(i);
      INC(beg);
      DEC(len)
   END;
   INC(c.total, i);

   IF c.root = NIL THEN c.root := bd
   ELSE
      bdPtr := c.root;
      WHILE bdPtr.next # NIL DO
         bdPtr := bdPtr.next
      END;
      bdPtr.next := bd
   END;
   IF (c.logger # NIL) & (bd # NIL) THEN
      c.logger.Appended(bd.data^)
   END
END Append;


PROCEDURE (c: Carrier) Delete* (n: INTEGER), NEW;
(** Удаляем из буфера первые n байт. **)
VAR
   bd, newbd : CarrierData;
   i, cnt, deleted : INTEGER;
   r: Rider;
BEGIN
   IF n = 0 THEN RETURN END;

   ASSERT(n > 0, 20);

   bd := c.root;
   deleted := n;
   WHILE (n > 0) & (bd # NIL) DO
      cnt := LEN(bd.data);
      IF n >= cnt THEN
         bd := bd.next;
         DEC(n, cnt)
      ELSE
         newbd := NewCarrierData(cnt-n);
         i := 0;
         WHILE n < cnt DO
            newbd.data[i] := bd.data[n];
            INC(i);
            INC(n)
         END;
         n := 0;
         newbd.next := bd.next;
         bd := newbd
      END
   END;
   c.root := bd;

   (* обновим текущую позицию райдеров *)
   r := c.riders;
   WHILE r # NIL DO
      r.Deleted(deleted);
      r := r.next
   END
END Delete;


PROCEDURE (c: Carrier) IsEmpty* (): BOOLEAN, NEW;
BEGIN
   RETURN c.root = NIL
END IsEmpty;

END CommBuffer.
```
