Сага о CLSN Pascal.
Часть 2.
В первой части я обещал рассказать о CLSN поподробнее...
Уверяю, ЕСТЬ о чём.
Во первых, это среда быстрого программирования со своим Редактором, Компилятором, но без Линкера, так как язык компилирует коды из памяти на диск в машкоды, сразу же получая исполняемую программу ДОСа. Отсутствие же линкера позволяет запускать куски кода прямо из CLSN, что удобно при отладке.
Насколько я понимаю, CLSN является вторым, после BasicXE языком, полностью использующим расширенную память XL/XE. Причём, переключение банков памяти полностью автоматическое и память можно рассматривать как динамическую. В Паскале полноценно реализована работа с Кучей, и кучей является почти вся расширенная память - 57kb. Кроме этого хорошо реализована работа с записями, что в атарьских языках большая редкость. Если что ещё вспомню, скажу.
А, да, из предыдущего примера видно, что общение CLSN с Ассемблером похоже на общение с Ассемблером Action! Не удивлюсь, если можно подставлять коды для Action!!!
Ну, в общем, в процессе изучения CLSN, мне в голову пришла идея IMHO элегантного простого текстового меню, управляемого стрелками, которого так не хватает для Атари.
Идеи за этим стояли следующие:
1. Пусть текст меню задаётся строкой. Это - экономно и может раздаваться различными способами.
2. Строка автоматически разделяется пробелами на слова, являющиеся пунктами меню, кроме этого, открыто или скрыто каждому пункту присваивается индекс, по которому он может быть однозначно определен.
3. Я совершенно не хочу заботиться ни о длине пункта, ни о выделении памяти.
То есть программировать буду динамически то есть на Куче.
4. в качестве носителя меню хочу использовать динамический двусвязный список, так как он позволяет простейшим способом траверсировать цепочку как вперёд, так и назад вдоль списка.
5. Скорость вычислений при движении по меню совершенно не имеет значения, так как движения пальцев всё равно медленнее.
В общем, сначала начнём с ДДС - Динамического Двусвязного Списка - Doubly Linked List. Заодно вспомним и Паскаль.
- Идеологически список является единым мультиконтейнером, все элементы которого связаны между собой по Вашему выбору: - передней, задней или двойной сцепкой. Это - прямой намёк на некий тип самоадресации звеньеа списка.
- Соответственно, можно различить контейнерную (информационную) часть элемента списка и одну или несколько связных частей.
- Доступ к списку как к целому осуществляется указателями.
Если Вы помните, указатели в PL65 являются нетипизированными (void).
Типизированными их делуют используемые совместно простые переменные типа BASED.
Код:
BYTE bVar BASED myPtr INT iVar BASED myPtr
Однако Паскаль имеет возможнолсть работы с большим количеством сложных программных элементов и вполне достоин простого и эффективного их описания и простых и эффективных методов работы с ними.
Обычно указатели не только содержат адрес, но, в качестве метаданных и размер переменной для эффективного вычисления шага индексации вдоль... ну, массива, например.
В Паскале указатели тоже содержат метаданные об адресации, однако задаётся это всё описанием типов пользователя и они могут быть весьма сложными, хотя описываются они просто. Заодно, данная ситуация намекает насколько пользовательские типы данных необходимы и популярны в Паскале. Строгая типизация языка делает возможность взаимодействовать со сложными типами данных как с простыми - прозрачно. Иными словами, делай правильную работу правильными инструментами!
Если две переменных описаны одним и тем же типом, то присваивание
Код:
complicated1 := complicated2;
здесь означает полное копирование, со всеми метаданными, независимое от сложности. Также, полностью копируются сложные указательные типы.
Это в частности значит, что указатели имеют особое значение для списков. Они и обеспечивают ту самую самоадресацию узлов. Соответственно, плохо типизированные указатели не в состоянии справиться с этой функцией.
Итак, название -
Пользовательские типы -
Код:
type
(* Информационный тип, являющийся записью, в которой хранится поле счетчика типа целое. Эта запись
будет помещена в каждый узел списка. *)
infoT = record (* большая T = Type *)
cnt: integer;
end;
(* Тип связь, являющийся каким-то указателем на узел, пока ещё не описанный типом. Это - единственное
исключение в Паскале. Всё остальное требует строгой типизации. *)
linkT = ^nodeT;
(* Сложный тип узла, являющийся записью, в которой хранится вышеописанный информационный тип и две
вышеописанных связи, указывающие на узлы (полностью со всеми метаданными) следующий и предыдущий *)
nodeT = record
info: infoT;
next: linkT;
prev: linkT;
end;
Здесь следует отметить, что после полного описания связи - linkT и её подсистемы - узла nodeT, мы уже не нуждаемся в nodeT, так как он сам используется старшим описанием. Нужно пользоваться только описанием связи - linkT.
Код:
(* Тип список, описывающий dll как целое и являющийся записью, содержащей связи как с первым,
так и с последним элементом списка *)
listT = record
first: linkT;
last: linkT;
end;
Кроме обычных задач списка нам существенно также отделить работу со списком от работ с самими программными данными, то есть написать несколько подпрограмм, являющихся фактически универсальными библиотеками взаимодействия со списками. Именно поэтому захотелось максимально абстрагировать списки от данных. Именно поэтому был введён тип listT. Реально нам никто не мешал пользоваться только Указателем старта списка, или Указателем его финиша, или обоими. Однако обобщение сокращает нам запоминание лишних слов.
Тип listT, содержащий необходимые связи, является определяющим для самого смысла двусвязного списка. Если мы теряем связь со списком, - фактически его у нас нет.
Существует ситуация в которой мы НЕ ТЕРЯЛИ связи со списком, однако элементов в списке нет. То есть теряется смысл списка, как самоуказателя. Выходит и в такой ситуации списка нет? Вовсе неверно.
- Начиная с Лиспа для этого был придуман вырожденный список NULL, а для Паскаля nil, грубо говоря, признак того, что эта СИСТЕМНАЯ фикция и есть - список. То есть, приравнивая значения связных полей к вырожденным спискам, мы говорим компилятору, что несмотря на некоторые недоразумения, он имеет дело, всё же, со списком. Чтобы создать список нам достаточно прировнять связные поля вырожденным спискам. Void не говорит ни слова из метаданных, nil уже говорит, что наш объект - список и может иметь ВСЕ надлежащие типу поля!
Первой программной процедурой у нас будет создание списка, но с проверкой на то, не забыли ли мы удалить список с подобным названием. Это - облегчает жизнь. Мы не хотим получить НЕ ТЕ данные и хотим вообще абстрагироваться от данных списка!
L - параметрическая переменная, передаваемая со всеми пирогами по ссылке (так как указатель)!
Вот процедура:
Код:
(* Переменная, в качестве параметра, нужна, так как список ТАК ИЛИ ИНАЧЕ изменяется
(Здесь - создаётся) *)
procedure initList(var L: listT); (*название процедуры*)
var
(* Роль nxtP - всегда в цикле переходить своей связью next на следующий узел. Роль delP сначала
перенять связь с узла nextP, а затем пропустив его вперёд, стереть на Куче память узла, на который
указывает, т.е. самоуничтожившись. Стирание идёт с первого узла. В цикле.*)
nxtP, delP: linkT;
begin
(* Да, это список, но он пустой. От nil данных не добыть. Складываем ручки. *)
if L.first = nil then Exit;
(* если не пустой *)
nxtP := L.first; (*Нашли следующий. Он - наш первый!*)
(* Будем занудно искать все следующие, пока какой-нибудь из них не покажет нам nil *)
while nxtP <> nil do begin (* не последний, определённо... *)
delP := nxtP; (* delP становиться на nextP (текущий) *)
nxtP := nxtP^.next; (* передвинем next на next+1 *)
Dispose(delP); (* а вот с delP разберёмся... *)
end; (*кружимся в цикле while!!!*)
(* Стерев память предыдущего списка, возрождаем/создаём список! *)
L.first := nil;
L.last := nil;
end;
Не забывайте, что Список = большее, чем Узел!
zen