Тайники ZX Spectrum 1969 г.

Basic - компьютерная арифметика. Использование памяти.


                  4. Компьютерная арифметика
     Единичная ячейка памяти  компьютеров, сделанных на  процессоре
Z80,  размещает  в  себе  одно  слово  (байт), состоящий из 8 цифр,
каждая из которых может быть  либо 0, либо 1. переход  от двоичного
представления к десятичному очень прост.  Выпишем в ряд по  очереди
числа 2**7, 2**6,...,  2**1, 2**0, затем  под каждым из  этих чисел
выпишем одну цифру десятичного представления. Складывая между собой
только  те  числа  верхнего  ряда,  под которыми оказались единицы,
получим десятичное значение. Например, двоичное число 01001101:
         128  64  32  16  8  4  2  1
           0   1   0   0  1  1  0  1
         ---------------------------
              64          8  4     1 = 77
     Содержимым  ячейки  памяти  (байта)  может  быть положительное
число в диапазоне 0...255.
     Иногда  выгоднее  рассматривать  эти  числа  как  значения  со
знаком. Самый старший разряд  (самый левый бит) определяет  знак (0
если  +,  1  если  -).  При  такой  интерпретации  мы имеем числа в
диапазоне -128...127.  Однако компьютер  всегда выполняет  действия
лишь над натуральными числами без знака. Поэтому в ZX Spectrum  для
представления  чисел  применяется  код  дополнения  до  двух. Числа
меньше  нуля  записываются  в  нем  как  256+K.  Например, число -7
представляется  как  256-7=249.  В  этой  системе самый старший бит
интерпретируется  как  знак  числа.   Поэтому  проблем, связанных с
арифметикой,  не  существует.   Основные  арифметические   операции
(сложение и т.п.) ведут  к верным результатам независимо  от выбора
способа  записи  чисел.  Например,  246+1=247  является   очевидным
равенством  для  чисел   без  знака,  но   также  является   верным
равенством, если  считать 246  как -7,  так как  тогда 247=256+(-6)
обозначает -6.
     Использование  таких  маленьких  чисел  недостаточно,  поэтому
большие  числа  необходимо  хранить  в нескольких байтах, следующих
один за другим. В ZX  Spectrum важную роль играют 16-значные  числа
(16 бит).  Их цифры  делятся на  две группы  по 8  бит. Левый  байт
называется  старшим,  а  правый  -  младшим.  Во  всех компьютерах,
организованных  на  Z80,  принято  правило,  что если два очередных
байта с адресами X, X+1 содержат какое-либо число, то под адресом X
размещается младший байт,  а под X+1  - старший. Чтобы  определить,
какое число содержат эти  ячейки, необходимо старший байт  умножить
на  256  и  прибавить  младший.  Если это должно быть отрицательное
число,  представленное  в  коде  дополнения  до  2,  то еще отнимем
2**16=65536.  Эти  16-битовые  числа  позволяют  хранить  числа   в
диапазоне 0...65535 (беззнаковые) или - 32728...32727 (знаковые).
     При  программировании   в  машинном   коде  часто    возникает
необходимость   в   выполнении   разных   операций   над    битами.
Использование десятичного представления при этом затруднительно,  а
двоичного  слишком  громоздко.  Поэтому  наиболее  удобным является
16-чная запись. Для записи 16-чных чисел используются цифры от 0 до
9 и буквы от A до F.
     Пересчет 16-чного представления  в 10-чное нетруден.  Правило:
умножать очередную цифру на очередную степень числа 16 и складывать
произведение.  Например, 16-чное  число FFFF представляется в  виде
15*16**3+15*16**2+15*16**1+15*16**0   и  равно
15*4096+15*256+15*16+15.
     Переход  от  двоичной  формы  к  16-чной  (и обратно) особенно
прост. Делим  цифры бинарного  числа на  группы по  4 бита и каждую
четверку представляем  16-ричной цифрой.  Например, 1001111110=0010
0111 1110=27E.
     Как  видно,  главным  преимуществом  16-ричных  чисел является
простота  перехода  к  двоичному  представлению  и  наоборот,   при
одновременно высокой плотности  записи. Далее 16-чным  числам будет
предшествовать знак #.
     Способы кодирования больших чисел и чисел с плавающей  запятой
для различных компьютеров различны. В ZX-BASIC все числа хранятся в
5   последовательных   байтах.   Целые   числа   из   диапазона  от
-65536...65536 кодируются  иначе, чем  остальные. Первый  байт в их
представлении всегда равен  0, последний байт  положительного числа
равен 0, а отрицательного - 256. В третьем и четвертом  разместятся
соответственно  младший  и  старший  байты  данного  числа,  причем
отрицательные значения хранятся в  коде дополнения до 2.  Например,
число 38 выглядит так: 0 0 0 38 0, а число 743=2*256+231 имеет  вид
0 0 231 2 0. В свою очередь   число  -1231  запишется  как  0   255
251  49  0,  так как 256*251+49=65536-1231.
     С  остальными  числами  все  запутано еще больше. Используется
факт, что каждое число можно однозначно представить в виде: 2**N*M,
где M - число в диапазоне [1/2,1]. В первом байте  Spectrum  хранит
значение N+128. В  остальных четырех размещено  число M в  двоичной
форме, а  точнее M*2**32,  с тем,  что самый  старший бит,  который
всегда  должен  быть  равен  1,  служит  для  хранения знака числа.
Единица означает минус, а ноль - плюс.
     Допустим, что 5  последовательных ячеек памяти  содержат числа
130   212   16   34    178.   Они   интерпретируются   как    число
-2**(130-128)*(212/2**8+16/2**16+34/2**24+178/2**32),     а   байты
106      112      4       231      78      представляют       число
2**(106-128)*((128+112)/2**8+4/2**16+231/2**24+78/2**32).
     Достоинством  языков  высокого  уровня  (в том числе ZX-BASIC)
является  то,  что  программист  освобожден от записи представлений
различных чисел в память.

                    5. Использование памяти
   После  включения  компьютера  в  сеть  автоматически запускается
программа,  размещенная  в  ROM  по  адресу  0. Она проверяет объем
допустимой  памяти,  разделяет  ее  на  различные  блоки,  а  также
присваивает системным переменным начальные значения.
   Диаграмма, приведенная ниже, описывает раздел памяти. Объем от 0
до  16383 размещается в ROM, остальные - в RAM. Только часть блоков
имеет  свое  постоянное  положение,  остальные могут перемещаться в
памяти  и  их  текущие  адреса хранятся в соответствующих системных
переменных.  Числа  в конце некоторых областей обозначают указатели
конца области и всегда присутствуют там.
---------------------------------------------------------------
|   Адрес      |           Область               |   Конец    |
|-------------------------------------------------------------|
|#0000 (0)     | Системные процедуры             |            |
|#3000 (15616) | Прообразы симв. с кодами 32..127|            |
|#4000 (16384) | Экран                           |            |
|#5800 (22528) | Атрибуты                        |            |
|#5B00 (23296) | Буфер принтера                  |            |
|#5C00 (23552) | Системные переменные            |            |
|#5CB6 (23734) | Карта микродрайва               |            |
| CHANS        | Информация о каналах            |#80 (128)   |
|-------------------------------------------------------------|
| PROG         | BASIC                           |            |
|-------------------------------------------------------------|
| VARS         | Переменные ZX-BASIC             |#80 (128)   |
|-------------------------------------------------------------|
| E_LINE       | Буфер редактора                 |#80 (128)   |
|-------------------------------------------------------------|
| WORKSP       | Буфер инструкции INPUT          |#0D (13)    |
|              | Рабочая область ZX-BASIC        |            |
|-------------------------------------------------------------|
| STKBOT       | Стек калькулятора               |            |
| STKEND       |                                 |            |
|-------------------------------------------------------------|
| SP           | Свободная обл. памяти ZX-BASIC  |            |
|-------------------------------------------------------------|
| ERR_SP       | Машинный стек                   |            |
|-------------------------------------------------------------|
| RAMTOP       | Стек адресов возврата GOSUB     |#3E (62)    |
|-------------------------------------------------------------|
|              | Свободная область памяти        |            |
|-------------------------------------------------------------|
| UDG          | Прообразы символов, определенных|            |
| P_RAMT       |            пользователем        |            |
---------------------------------------------------------------
                    5.1 Прообразы символов
   Формы  печатных символов с кодами от 32 до 127 (8 байт на каждый
знак)  заполняют 768 последних ячеек ROM. Они закодированы в той же
форме,  как  и символы, определяемые пользователем. Знаки мозаичной
графики  (коды  от  128  до  143)  конструируются каждый раз. Адрес
начала этой области хранится в системной переменной CHARS.
                           5.2 Экран
   Эта область предназначена для хранения информации о каждой точке
экрана, а точнее о том, должна ли она быть в цвете чернил или фона.
6144  байта  этого блока позволяют адресовать 49152 различных точек
(один  байт  описывает  8 точек). Они организованы в таблицу из 192
строк  и  256  столбцов.  Для графических инструкций BASIC доступны
только  176 верхних строк. Каждый графический знак выводится в поле
8*8  точек. Это позволяет выводить тексты в формате 24 строки по 32
позиции.  Две нижние текстовые строки зарезервированы для системных
сообщений и рабочей области редактора.
   Интересен  способ,  которым  эта область переносится на экран. К
сожалению,  последовательные  32 байта не описывают последовательно
графическую  строку  на  экране,  а  256  последовательных  байт не
описывают  оператор  текста.  Размещение графической строки (линии)
абсолютно  противоположно,  а  с  точки  зрения  ZX-BASIC похоже на
изобретение  сумасшедшего.  Легче  всего  это можно увидеть во время
считывания  экрана  с ленты. Здесь мы ограничимся только примерами,
позволяющими  пересчитывать  текстовые  и графические координаты на
экранные адреса.
   Байт,  содержащий  графическую точку с координатами (K,M) (точка
(0,0)  лежит в верхнем левом углу экрана выше двух низших текстовых
операторов) на экране имеет адрес:
16384+32*(INT((175-M)/8-INT((175-M)/64*8+8*(175-M-INT((175-M)/8)*8+
+64*INT((175-M)/64)+INT(K/8)
   Положение бита в байте рассчитывается как:
  8-K+INT(K/8)*8
   В свою очередь, адреса текстовых знаков рассчитываем по образцу:
  16384+2048*INT(K/8)+32*(K-8*INT(K/8))+256*M+N
где    K    обозначает  номер оператора (0..23), N является номером
колонки  (0..31),  в  свою  очередь  M является номером графической
строки  (линии), формирующей данный знак (0..7). За 0-ю принимается
лежащая   по   верху   блока  знака  линия.  Начало  координат  для
инструкций, пишущих знаками, помещено в левом верхнем углу экрана.
                 5.3 Атрибуты (22528...23295)
   В  этой  области  хранится  информация  о  размещении  цветов на
экране.   Наименьшей   единицей  поверхности,  цвет  которой  можно
изменять,  является  поле  единичного знака размером 8*8 точек. Для
такого поля можно определить цвет фона, чернил и мерцания. На выбор
имеются 8 цветов :
                   0 - черный
                   1 - фиолетовый
                   2 - красный
                   3 - синий
                   4 - зеленый
                   5 - голубой
                   6 - желтый
                   7 - белый
   Каждый   цвет   может  выступать  в  двух  оттенках:  обычном  и
интенсивном (BRIGHT 0,1).
   Все сведения, касающиеся единичного знакового поля, закодированы
в одном байте:
         ----------------------------------------
         | Биты |          Значение             |
         |--------------------------------------|
         |  7   |  Мерцание                     |
         |--------------------------------------|
         |  6   |  Интенсивность фона           |
         |--------------------------------------|
         |  5   |                               |
         |  4   |  Цвет фона                    |
         |  3   |                               |
         |--------------------------------------|
         |  2   |                               |
         |  1   |  Цвет чернил                  |
         |  0   |                               |
         ----------------------------------------
   Чтобы  установить  желаемые  атрибуты  поля  и определить, какое
значение   следует   поместить   в   соответствующий   байт,  лучше
пользоваться образцом:
                   128*F+64*8+32*P+I,
           где     F определяет мерцание      (FLASH 0,1)
                   B определяет оттенок фона  (BRIGHT 0,1)
                   P является номером цвета фона
                   I является цветом чернил
   Определение адреса байта, описываемого атрибутами знакового поля
с координатами K,M, осуществляется по формуле:
                   22528+32*K+M
              5.4 Буфер принтера (23296...23551)
   В  этой  области  собираются  данные,  которые  будут посланы на
принтер.  Печать, фактически, следует после заполнения этого буфера
(256 байт или 32 знака), или после символа "конец строки" (CHR$13).
Если  принтер  не  подключен,  то  область  не будет использоваться
системой и может использоваться для других целей.
                     5.5 Карта микродрайва
   Эта  область используется только в случае подключения к Spectrum
ZX  интерфейса  1.  В  "голом"  компьютере  эта карта не существует
физически и CHANS=23734.
            5.6 Программа ZX-BASIC (PROG до VARS-1)
   После инициализации системы, без подключенных внешних устройств,
этот  блок  начинается с адреса #5CC8 (23755). В нем хранится текст
программы  на  BASIC.  Отдельные  строки  в памяти компьютера имеют
следующую форму:
         ---------------------------------------------------
         |  2 байта  |  2 байта  | Длина текста |  1 байт  |
         |-------------------------------------------------|
         |  Номер    |   Длина   |    Текст     | #0D (13) |
         |  строки   |  текста+1 |              |Код"ENTER"|
         ---------------------------------------------------
   В  случае  номера  строки сделано отступление от правил и первым
здесь  выступает старший байт, а потом младший. Все ключевые слова,
появляющиеся в тексте, кодируются единичными символами.
   Интересным является способ хранения чисел в тексте программы. За
последовательностью  знаков, представляющих последовательные цифры,
всегда размещается управляющий символ CHR$14, а за ним пять байтов,
содержащих  двоичное  представление  этого  числа.  Во время вывода
программы  на  экран  эти  шесть  добавочных  байт  опускаются. Это
объясняет то, почему некоторые программисты предпочитают запись VAL
"17"  вместо просто 17. Первая из них фактически занимает 5 байтов,
а  вторая  -  8.  Речь идет об экономии памяти. Во время выполнения
программы  в  свою  очередь пропускаются символы, которыми записано
число   и   используется  5-байтовое  двоичное  представление.  Это
позволяет прятать правильные значения определенных чисел (например,
стартовых адресов процедур в машинном коде) от "любопытных". В этом
случае   необходимо   определить  адрес  внутреннего  представления
данного  числа  и  инструкцией POKE присвоить ей желаемое значение.
При выводе текста на экран и в дальнейшем будет фигурировать старое
представление  числа, уже не связанное с записанным за ним двоичным
числом!  Применяя  эту  технику,  необходимо  помнить,  что  каждое
извлечение  такой  строки  в  нижнюю  часть  экрана и возврат ее на
место,  даже  без  модификации,  изменяет  внутреннее представление
каждого числа в тексте, присваивая им значения, видимые на экране.
   Из  других  любопытных  трюков  приведем  еще два. Если в первой
строке программы в ячейках, содержащих ее номер, поместить число 0,
то  такой  оператор  невозможно  будет  скопировать  в нижнюю часть
экрана  в область редактора, а затем модифицировать. В другом конце
программы  возможна  другая  штучка.  Допишем там строку 9999 REM и
после  установления  ее адреса в обоих байтах, содержащих ее номер,
разместим значение 255. Первый результат этого проявится при выводе
программы  на  экран.  Модифицированная  нами  строка  не появится.
Второй   эффект,   гораздо  более  ценный,  проявится  при  попытке
считывания  такой  программы  с кассеты инструкцией MERGE. Spectrum
"обижается"  на  пользователя  и  перестает  реагировать  на  любые
клавиши.
          5.7 Переменные ZX-BASIC (VARS до E_LINE-2)
   В этой области система хранит все переменные, инициализированные
как  программой,  так  и  пользователем  с клавиатуры. Появляющиеся
переменные  последовательно  дописываются в конце этой области (все
последующие  блоки  автоматически  сдвигаются) и остаются там, пока
область  не  будет  очищена. Исключением являются простые текстовые
переменные: когда какая-нибудь из них модифицируется, ее предыдущая
версия  убирается  (что  сопровождается  перемещением других блоков
памяти), а новая дописывается в конце этой области.То же происходит
при новом объявлении массивов.
   Способ  хранения  переменных  зависит  от  их  типов. В ZX-BASIC
существует  шесть  различных видов переменных, обозначаемых числами
от 2 до 7:
         2 (010) - простые текстовые переменные;
         3 (011) - простые числовые переменные с однолитерными
                   именами;
         4 (100) - числовые массивы;
         5 (101) - простые числовые переменные с многолитерными
                   именами;
         6 (110) - знаковые массивы;
         7 (111) - переменные, управляющие циклами FOR...NEXT.
   Во всех случаях первый байт описания переменной содержит ее тип,
а также номер первой литеры имени. Метод размещения этих сведений в
одном слове следующий:
                   ----------------------------------------
                   |      |    Тип   |  Номер  литеры     |
                   |--------------------------------------|
                   | Биты |  7 6 5   |   4 3 2 1 0        |
                   ----------------------------------------
   Обратим  внимание,  что  биты  4...0  содержат  порядковый номер
литеры  в  алфавите,  а  не ее код. Соответственно код мы получаем,
складывая  с  числом  #60  (96).  Храня  имена переменных, Spectrum
автоматически  заменяет  большие  литеры  малыми  и  пропускает все
пробелы. Содержимое последующих байт зависит от типа переменной.
     Простая текстовая переменная. Длина описания N+3 байта:
         ---------------------------------------------------
         | Тип и литера | N=длина текста (2 байта) | Текст |
         ---------------------------------------------------
     Простая числовая переменная с однолитерным именем.
     Длина описания 6 байт:
         --------------------------------------
         | Тип и литера | Содержимое (5 байт) |
         --------------------------------------
     Числовой массив. Длина описания N+3 байта:
   ------------------------------------------------------------
   | Тип и  | N=длина    | k=число  |  1-й...k-й  | Элементы  |
   | литера | описания -3| индексов |индекс индекс|(по 5 байт)|
   |        | (2 байта)  |          | (по 2 байта)|           |
   ------------------------------------------------------------
     Простая числовая переменная с многолитерным именем.
     Длина описания k+5 байт:
         ---------------------------------------------------
         | Тип и литера | 2-й знак ... k-й знак | Значение |
         |              | имени        имени    | (5 байт) |
         ---------------------------------------------------
     Второй   и   последующие   символы  имени  хранятся  как  коды
соответствующих  знаков.  Последний  байт имени имеет самый старший
бит всегда установленным в 1, что позволяет всегда распознать его.
     Знаковый массив. Длина описания N+3 байта:
   ------------------------------------------------------------
   | Тип и  | N=длина    | k=число  |  1-й...k-й  | Элементы  |
   | литера | описания -3| индексов |индекс индекс|(по 1 байт)|
   |        | (2 байта)  |          | (по 2 байта)|           |
   ------------------------------------------------------------
     Управляющая циклом переменная. Длина описания 19 байт.
----------------------------------------------------------------
| Тип и | Значение  | Значение  |  Шаг=z  | k=номер |Номер FOR |
| литера|текущее = x|конечное =y|(5 байт) | строки  |в строке+1|
|       |  (5 байт) |  (5 байт) |         | (2 байт)| (1 байт) |
----------------------------------------------------------------
   Последний  байт области переменных как указатель всегда содержит
значение #80 (128).
           5.8 Буфер редактора (E_LINE до WORKSP-1)
   В    этой    области    размещается    строка    программы   или
последовательность  директив  для  непосредственного  выполнения во
время  их  ввода с клавиатуры. Вводимые символы копируются в нижнюю
часть  экрана.  После  нажатия "Enter" компьютер проверяет верность
вводимого текста и предпринимает соответствующие действия.
        5.9 Буфер инструкции INPUT (WORKSP до STKBOT-1)
   В этот буфер первоначально вводятся данные, считываемые командой
INPUT  и  только после нажатия "Enter" следует их преобразование. В
этой области Spectrum также выполняет некоторые операции, требующие
рабочей памяти (например, сцепление символьных переменных).
          5.10 Стек калькулятора (STKBOT до STKEND-1)
   Рабочая  область  системных подпрограмм, выполняющих большинство
арифметических операций.
    5.11 Свободная область системы BASIC (STEKEND+1 до SP)
   Этот  блок  памяти  свободен,  но  предназначен для потребностей
системы   BASIC.   Рабочие  области  с  обоих  концов  этого  блока
пополняются   за   его   счет.  Данные,  размещенные  здесь,  могут
подвергнуться   уничтожению   без   предупреждения.  Перед  началом
действий,  требующих  увеличения  какого-нибудь  из рабочих блоков,
Spectrum  проверяет,  остается ли в этом блоке хотя бы 80 свободных
байт   на  возможные  потребности  машинного  стека.  Отрицательный
результат  проверки  сигнализируется сообщением 4. Адрес конца этой
области  хранится  в  регистре процессора Z80,называемом указателем
стека (SP) и недоступен из системы BASIC.
               5.12 Стек машинный (SP до ERR_SP)
   Стек,   используемый   процессором  Z80  для  текущего  хранения
различных  данных,  адресов и т.п. Эта область абсолютно бесполезна
для программирования на BASIC.
     5.13 Стек адресов возврата GOSUB (ERR_SP+1 до RAMTOP)
   Очередной  стек, на этот раз предназначенный для хранения номера
строки  и  положения  в ней инструкции GOSUB. Эти данные необходимы
для   команды   RETURN  при  неаварийном  выходе  из  подпрограммы.
Многоразовое  выполнение  GOSUB или RETURN вызывает сдвиг машинного
стека,  адресуемого  через  ERR_SP  на  три  байта  вниз или вверх.
Поэтому при модификации блока требуется особая осторожность.
      5.14 Область свободной памяти (RAMTOP+1 до P_RAMT)
   Блок  действительно  свободной памяти и безопасной в том смысле,
что  его  содержимое  недоступно  для  системы  BASIC  и может быть
модифицировано   только   по   требованию  пользователя  с  помощью
инструкции  POKE.  Даже  директива  NEW не нарушает этой области. В
принципе она используется для хранения программ в машинном коде или
специфичных данных, для которых переменные BASIC не подходят.
      5.15 Образы символов пользователя (UDG до UDG+167)
   После  инициализации  системы  этот  блок  начинается  с ячейки,
имеющей     адрес     RAMTOP+1=#FF58=65368,     и    кончается    в
P_RAMT=#FFFF=65535.  Адрес  этого  блока  в  памяти можно получить,
выполнив  инструкцию  USR  "A".  Эта  область  служит  для хранения
очертаний   графических   символов,   определенных   пользователем.
Определение  единичного символа состоит из восьми байт, описывающих
состояние каждой точки в знаковом поле 8*8.
   Для  примера  определим  графический  символ  и  разместим его в
области  UDG так, чтобы он высвечивался после нажатия клавиши "O" в
режиме  G. Первым этапом является проектирование формы нового знака
в   квадрате   8*8.   В  поле,  предназначенном  для  закрашивания,
размещается  1,  а  в  пустом  - 0. Каждый так заполненный оператор
рассматривается как восьмицифровое бинарное число. Полученный набор
8 чисел описывает форму проектируемого символа:
                   00000100        4
                   00001000        8
                   00000000        0
                   00111100       60
                   01000010       66
                   01000010       66
                   00111100       60
                   00000000        0
Введем  его  теперь  в компьютер. Проще всего это сделать с помощью
программы:
         10 DATA 4,8,0,60,66,66,60,0
         20 FOR I=USR "O" TO USR "O"+7
         30 READ A: POKE I,A
         40 NEXT I
После  ее  выполнения клавиша "O" в режиме G выводит закодированный
символ.  В  цепочке  символов  он  будет  иметь номер 158. Данные в
строке  10 можно подать также и в двоичной форме вместо десятичной,
используя  функцию BIN. Это требует больше писанины, но значительно
облегчает все модификации определенного нами образца.
   168  байт  области UDG позволяют хранить 21 различный символ. Их
коды  размещаются  в  диапазоне  от  144  (A)  до  164  (U).  После
инициализации системы этот блок содержит образцы форм больших литер
латинского алфавита.



СОДЕРЖАНИЕ:


  Оставте Ваш отзыв:

  НИК/ИМЯ
  ПОЧТА (шифруется)
  КОД



Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Железо - SOUNDRIVE- этo музыкальная приставка.
Ansi и ASCII - анси и аски графика.
Вступление - Смелу пришли очередные номера московского журнала ZX-РЕВЮ за 1997г.
История - Продолжение этой попойки.
Реклама - 30.01.2002 года вышел очередной (10-й) номер полиграфической газеты "Абзац"!

В этот день...   21 ноября