PDA

Просмотр полной версии : Паскаль о котором не знали...



ezswift
22.06.2018, 22:05
Сага о CLSN Pascal.
Часть1.

Снова мне хотелось бы представить Вам новый древний язык программирования для Атари.

Прежде всего - это Паскаль.
Затем, Паскаль ВПОЛНЕ совершенный!
И, наконец, он - Атарьский. (* Я имею ввиду, что системные вещи Атари в нём весьма заметно реализованы *).

(* Вот так в CLSN пишутся комментарии (,в отсутствии фигурных скобок...) *)

Как Вы понимаете, и CC8 и PL65 и CLSN являются ПРЯМЫМИ наследниками ПЕРВОГО языка программирования - ALGOL. Для нас значимо, что синтаксис у наследников заимствует большинство принцыпов синтаксиса родителя.
Уточню, НЕ САМОГО СИНТАКСИСА, А ПРИНЦЫПОВ!

Что же внесено нового после ALGOL'а?
IMHO:
PL65 - Эклектичность = бессистемная всеядность. (всё новое, что тогда было...)
CC8 - Приверженность к C = (максимальная безошибочность чтения C текстов, как источника логично-короткого, но полноценного синтаксиса для переносимости.)
CLSN - Приверженность к Pascal = (максимальная предобработка текстов для беспроблемной их компиляции при условии точного соблюдения правил языка.)

Откровенно, мне до лампочки на каком языке писать. Хочется найти СВОЙ!
И CLSN близок к этому. Причин необычайно много! О них я расскажу позднее.

Давайте, будучи знакомыми с моими ранее опубликованными постами об ассемблере, напишем программку для инверсии строки символов со скоростью ассемблера.
(* Кстати, в CLSN есть встроенная функция инверсии символов с клавиатуры!
Но это не то, что нам надо... *).
Мы хотим - просто взять строку (либо ATASCII, либо INTERNAL) и преобразовать её в инверсную. Здесь строка = короткая (0-255) символов.

Вот код:


(* Global *)
var
_s_temp: string; (* Temporary global *)

function invStr(s: string): string;
begin
_s_temp := s; (* Copy the local to an absolute memory location *)

inline($ac/_s_temp/ (* LDY _S_TEMP *)
$f0/$0c/ (* BEQ XIT *)
$b9/_s_temp/ (* NXT LDA _S_TEMP,Y *)
$49/$80/ (* EOR #$80 *)
$99/_s_temp/ (* STA _S_TEMP,Y *)
$88/ (* DEY *)
$d0/$f3); (* BNE NXT *)
(* XIT *)
invstr := _s_temp; (* Return the value *)
end;

(* Main Procedure *)
procedure main;
(* Local *)
var
s: string;

begin
repeat (*repeat this ...*)
writeln('Enter some string:'); write('>'); (*prompt symbol*)
readln(s); (*reads line*) writeln; (*place it on screen as input*)
writeln(invstr(s)); (*place on screen the result*) writeln; (*skip line*)
until (s=''); (*... to the end of line, looking at chars *)
end;

(* Calling *)
begin main; end.



Как Вы видете, CLSN не различает Прописных и Строчных символов.

ezswift
26.06.2018, 11:21
Сага о 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).

POINTER myPtr

Типизированными их делуют используемые совместно простые переменные типа BASED.

BYTE bVar BASED myPtr INT iVar BASED myPtr

Однако Паскаль имеет возможнолсть работы с большим количеством сложных программных элементов и вполне достоин простого и эффективного их описания и простых и эффективных методов работы с ними.

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

В Паскале указатели тоже содержат метаданные об адресации, однако задаётся это всё описанием типов пользователя и они могут быть весьма сложными, хотя описываются они просто. Заодно, данная ситуация намекает насколько пользовательские типы данных необходимы и популярны в Паскале. Строгая типизация языка делает возможность взаимодействовать со сложными типами данных как с простыми - прозрачно. Иными словами, делай правильную работу правильными инструментами!

Если две переменных описаны одним и тем же типом, то присваивание

complicated1 := complicated2; здесь означает полное копирование, со всеми метаданными, независимое от сложности. Также, полностью копируются сложные указательные типы.

Это в частности значит, что указатели имеют особое значение для списков. Они и обеспечивают ту самую самоадресацию узлов. Соответственно, плохо типизированные указатели не в состоянии справиться с этой функцией.


Итак, название -

program dll;

Пользовательские типы -

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

ezswift
27.06.2018, 08:12
Чтобы работать со списком нам всегда необходимо помнить о двух вещах:
- Связи узла(тип связь) должны указывать либо на узел, либо на nil. Больше ни на что!
- Связи списка(тип список) должны указывать либо на узлы, либо на nil. Больше ни на что!
Иначе крах!

Чтобы добавить узел к нашему списку, мы в качестве параметров сначала передадим подпрограмме
сам список, с которым мы работаем(списковый тип), затем временный узел(связной тип), созданный
в главной программе на Куче.

Алгоритм:
Предстоит разобрать несколько вариантов...
1. Не создан временный узел.
- Выходим из бесполезной подпрограммы.
2. Временный узел задан, но не инициализирован как связь.
- Инициализируем в nil и работаем дальше...
3. Если переданный в подпрограмму список задан, но пуст
- Делаем временный узел первым и его же последним элементом списка, работаем дальше...
4. Если задан, но не пуст...
а) реинициализация последнего узла
- Продлеваем связь next последнего в списке узла, ныне указывающую на nil, на наш временный узел
- Устанавливаем связь prev временного узла на бывший последний узел
(Здесь узел добавлен, но список об этом нифига не знает)
б) реинициализация всего списка
- Переносим связь last списка на временный узел
Готово!


(* Процедура добавления узла к пустому, или не пустому списку. Параметры передаются по ссылке, а не
по значению, так как список изменяется! добавлением. *)
procedure addNode(var L: listT; tmpP: linkT);
begin
if tmpP = nil then Exit;

tmpP^.next := nil;
tmpP^.prev := nil;

if L.first = nil then begin
L.first := tmpP; L.last := tmpP;

end else begin
L.last^.next := tmpP;
tmpP^.prev := L.last;
L.last := tmpP;
end;
end;

Вот тестовая программа:


program dll;
(* User types *)
type
infoT = record
cnt: integer;
end;

linkT = ^nodeT;

nodeT = record
info: infoT;
next: linkT;
prev: linkT;
end;

listT = record
first: linkT;
last: linkT;
end;

(* Procedures to work with list *)
procedure initList(var L: listT);
var
nxtP, delP: linkT;
begin
if L.first = nil then Exit;

nxtP := L.first;
while nxtP <> nil do begin
delP := nxtP;
nxtP := nxtP^.next;
Dispose(delP);
end;

L.first := nil;
L.last := nil;
end;

procedure addNode(var L: listT; tmpP: linkT);
begin
if tmpP = nil then Exit;

tmpP^.next := nil;
tmpP^.prev := nil;
if L.first = nil then begin
L.first := tmpP;
L.last := tmpP;
end else begin
L.last^.next := tmpP;
tmpP^.prev := L.last;
L.last := tmpP;
end;
end;

(* Main program *)
var
list: listT;
A: linkT;
i: Integer;
begin
list.first := nil;
list.last := nil;

for i := 1 to 5 do begin
New(A);
A^.info.cnt := i;
AddNode(list, A);
end;

Writeln('List created:');
A := list.first;
i := 0;
while A <> nil do begin
Inc(i);
Writeln(A^.info.cnt);
A := A^.next;
end;
Writeln;
end.

Вот паскаль и результат...
65628

Вот результат
65629

Удачи, zen

ezswift
28.06.2018, 10:42
Сначала хотелось бы сказать пару слов о синтаксисе Паскаля.

Как следует понимать следующую запись? nxtP^.info.cnt?

Вспомним из чего состоит узел списка. Прежде всего из информационного поля, являющегося записью.
Доступ к полям записи ведётся через оператор . - точка.
То есть info.cnt - это нахождение значения счётчика внутри информационного контейнера.
Также в узле есть два связных поля prev и next.

Однако, прежде чем доступаться к значениям, мы должны сначала доступиться к самому узлу.
Вот как раз адрес самого узла и задаётся следующим образом nxtP^ - сам узел.
Так как узел сам является записью, то доступ к его информационному полю будет таким nxtP^.info
И соответственно погружаясь далее в поле счётчика в info записи получаем nxtP^.info.cnt

В программе нам может понадобиться подпрограмма поиска по числу в счётчике пунктов.

Вот она:


(*Поиск элемента в списке по данным. Если элемент не найден, то возвращается nil.*)
function findNode(var A: listT; c: integer): linkT;
var
nxtP,resP: linkT;
begin
(* Инициализируем результат в nil *)
resP := nil;
(* Устанавливаем временный указатель на первый узел *)
nxtP := A.first;
(* Траверсируем вперёд, пока не закончился список *)
while nxtP <> nil do begin
(* Если данные счётчика достигли заданного знычения *)
if nxtP^.info.cnt = c then begin
(* Берём этот узел как результат и выходим из подпрограммы *)
resP := nxtP; exit;
end;
(* Если пока не достигли траверсируем вперёд к концу списка *)
nxtP := nxtP^.next;
end;
(* возвращаем связь узла результата *)
findNode := resP;
end;

А вот набросок программы меню:


program menu;
(* User types *)
type
infoT = record
cnt: integer;
dat: string;
end;

linkT = ^nodeT;

nodeT = record
info: infoT;
next: linkT;
prev: linkT;
end;

listT = record
first: linkT;
last: linkT;
end;

var
(* Строка меню и временная строка пункты *)
mStr,items: string;

(* Procedures to work with list *)
procedure initList(var L: listT);
var
nxtP, delP: linkT;
begin
if L.first = nil then Exit;
nxtP := L.first;
while nxtP <> nil do begin
delP := nxtP;
nxtP := nxtP^.next;
Dispose(delP);
end;
L.first := nil;
L.last := nil;
end;

procedure addNode(var L: listT; tmpP: linkT);
begin
if tmpP = nil then Exit;
tmpP^.next := nil;
tmpP^.prev := nil;
if L.first = nil then begin
L.first := tmpP;
L.last := tmpP;
end else begin
L.last^.next := tmpP;
tmpP^.prev := L.last;
L.last := tmpP;
end;
end;

(* Main program *)
var
list: listT;
A: linkT;
i: Integer;
begin
initList(list);
clrscr;
mStr := 'First Second Third Fourth Fifth';

i := 0; items := mStr+' ';
while pos(' ', items) <> 0 do begin
New(A); Inc(i);
A^.info.cnt := i;
A^.info.dat := copy(items,1,pos(' ', items)-1);
items := delete(items,1,pos(' ', items));
AddNode(list, A);
end;

WriteLn;
A := list.first;
while A <> nil do begin
Write(A^.info.cnt); Writeln('. '+A^.info.dat);
A := A^.next;
end;
Writeln;
end.

Здесь оператор pos ищет первый пробел во временной строке items.
Позиция пробела без единицы и будет длиной первого пункта меню.
Именно это количество символов и заносится в поле dat созданного оператором New узла.
После чего первый пункт стирается из строки items вместе с пробелом.
Так как эти действия зациклены просматривается вся строка items до последнего пробела, который мы программно пришили к строке items. Также в цикле мы наращиваем поле счётчика, автоматически нумеруя пункты меню.

Вот результат:
65636

Мне кажется, что алгоритм просто очарователен.
zen

ezswift
01.07.2018, 21:29
Здравствуйте, здравствуйте, здравствуйте!

Размышляя о том, что мы(знаю, знаю!..) наляпали в программировании на Паскале, я ощущал непрерывный дискомфорт.

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

То есть пользуясь тем, что CLSN позволяет компильнуть-запустить, или проверить код, мы далеко уходили от структуры как СИСТЕМЫ, в зону Бейсика := набрал-исполнил.

Ну, например, если мы зададим _infoT глобальным, мы можем уже начать создавать библиотеку Doubly-Linked Lists. Мы же честно отделили функции списков от функций данных!!!

ДА!!! У нас очень мало списковых функций, но ведь мы не этим озабочены, правда?

Я думаю, что автосоздание пунктов меню из строки требует только одной дополнительной функции - Отключения_Пункта, при показе в меню, то есть 'Серый пункт' (Я даже не знаю как его реально делают, но видел!!!) В смысле списка, нужно его сохранить, как связь (и память) в дополнительной глобальной переменной, но удалить из списка, переустановив связи.

Затем, как со всем этим работать?
Ясно, что меню нужно читать из файла на диске. (В Противном случае нужно раздавать все коды пользователю, а как он ими распорядится? - XZ)

Радует то, что изучение Паскаля, пока идёт гладко. Но индикации НАСКОЛЬКО - вовсе нет.

То есть, Я думаю, что так как текстовое меню должно быть квадратным, то всё равно мы должны отслеживать максимальную длину пункта.
Системная функция Len'ка вернёт число копируемых или инвертируемых символов, но я ненавижу ёрзанье по ширине курсора в Вертикальном меню!

Отследить - просто. Коли текущая длина меньше или равна - не заботься, а коли увеличилась - измени свою переменную... ДА! в Горизонтальном меню наш алгоритм ИСКЛЮЧИТЕЛЬНО рулит!

Это всё размышления о пост-программировании. Об облизывании сущностей...

А нужно думать о сущностях!
Например, как мне узнать, насколько двумерный массив экрана в CLSN обсчитывается быстрее, чем то, что я задавал в PL65.

То есть, стоит ли создавать библиотеку прямого вывода на экран CLSN?, когда существуют системные двумерные массивы?

Это для меня - загадка есть! Я просто не знаю Паскаля! I BEG YOUR PARDON PLEASE,SIRS!

Вот первые приближения к файловым операциям...


function fexist(s: string): boolean;
var
f: file of char;
begin
assign(f,s); (*Look up FileName*)
[I-]; (* chkoff *)
reset(f); (* Open *)
[I+]; (* chkon *)
fexist := (ioresult < 128); (* Less than 128 is success *)
c1ose(f);
end;

function freadln(f: text; s: string): string;
(* Reads a text file *)
var
s: string;
f: text;
begin
assign(f,'D:MENU.DAT'); reset(f);
while not eof(f) begin
readln(f,s); (* Get a line *)
_mStr01 := s; (* Save it *)
end;
close(f);
end.

ezswift
03.07.2018, 16:18
Здравствуйте, Дорогие Атаристы - ВосьмиБитники!
Пришло время ещё одного маленького прорыва. (Он - не маленький!)

Ситуация следующая: Я никогда в жизни не программировал на Паскале. И поэтому в принцыпе - не ведаю, что творю! Кроме этого, Мануал CLSN - не является ВООБЩЕ учебником Паскаля.

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

Ну, в общем, разобрался я ещё с одной проблемкой...

Как Вы знаете, чтобы что-то системное нарисовать, надо данные-то под этим сохранить сперва...
Так вот, средствами Паскаля мне удалось доступиться к экрану, как к видеобуферу. DMA, короче.

Честно, для меня, не знающего Паскаль было туго и непросто.

Однако я и с ЛИСПом справлялся и с Паскалем справлюсь!

Во Первых, Что нам ЭТО даст? Да, не печать через терминал CIO вывода, а занесение подготовленного (нашими предыдущими процедурками) тега байтов напрямую в меню. Или, инверсию символов, со скоростью машкодов.

А храниться сохранённое будет на Куче! То есть количество меню ограничено лишь ВСЕЙ памятью.
Пиши, не напишешь! Идём прямяком к Светлому Будущему!

Итак, Паскалевский DMA...


program scr;
type
SAVMSC = ^scrT;
scrT = array[1..24] of array[1..40] of byte;

var
screen: SAVMSC absolute $58;

begin
x := 10; y := 10;
screen^[y][x] := 128;
end.

Идея была проста.
Честно определяем SAVMSC как (не удивляйтесь!) указатель.
Затем определяем экран, как массив массивов байтов (не литер!, так как screen-bytes!).
Определяем абсолютную переменную screen ярлыком SAVMSC и...

И вот здесь я увяз. Понадобилась неделя, чтобы я понял, что мне нужно screen[y][x], заменить на screen^[y][x]. То есть, screen^ - это получение из указателя данных, являющихся адресом - разыменование!

В общем поделился с Вами большой радостью!!!
Ваш zen.

Shiny
03.07.2018, 16:41
Интересно array объявляется.

а array [0..39,0..23] of byte не прокатит?

ezswift
04.07.2018, 08:14
Здравствуйте, Шынни!

В CLSN для массивов только - array[1..24] of array[1..40] of byte;
Старый синтаксис.

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

Да, кстати, заметил некий загадочный баг в отображении экрана выполнения прогрвммы, наверное это CLSN шалит.

Вот скрин первого запуска предыдущей программы:
65664

А вот второго:
65663

Shiny
04.07.2018, 08:25
Кстати, а тип данных Record есть?

ezswift
04.07.2018, 08:29
Дык, я двусвязные списки на Record делал. Немного раньше в теме.

Вот изображение двусвязного списка:

65749

Shiny
04.07.2018, 09:16
каюсь, я списки ниасилил(

ezswift
04.07.2018, 12:17
По настоящему, там огромные неувязки с терминологией.
И очень много знаний по-умолчанию (что есть необходимость, но как их добывать?)
Это - напрочь дырявит мозг.

Например прямой перевод Звено - Link.
При описании Списков, логика ОТСУТСТВУЕТ, так как звенья в цепи связываются самостоятельно, а в списках нужны сцепки!

Но прямой перевод Сцепка - Coupler.

Вот, как раз, вагонная сцепка (prev, next) и должна называться fCoup, bCoup.

Я бы хотел описывать списки как поезд - очень подходит!
Есть универсальная тележка(node) [нужно указывать её тип, так как бывает простой вагон, а бывает пульмановский], есть Контейнер(infoItem)[указываем тип - сухогруз или цистерна], есть передняя и задняя сцепка(next, prev), есть тягач и толкач (first , last).
ВСЁ!

Это всё лучше смотреть в картинках.
Их много в Инете, но редко можно найти что-либо кузявое.
Вот одна картинка похожая на нашу структуру:
65765

Опять-таки, я только в МГУшных статьях нарыл, какие-то приемлемые сведения...

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

ВСЁ ВОЗМОЖНО ДЛЯ ТОГО, КТО ВЕРИТ!
zen

ezswift
11.07.2018, 23:29
Вообще говоря, чем дальше я забираюсь в Паскаль, тем мне больше нравится Никлаус Вирт.
Не зря он Нобелевский лауреат и автор Модулы2 и Ады, кроме Паскаля, разумеется.

Просто, прежде чем писать языки он последовательно трудился над философией языка.
ДА! Можно в какой-то степени говорить и о философии Си, но так вышло, что Ритчи и Керниган, всё же не Вирт и не швейцарцы.

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

Прежде всего мы со списками работаем только На Куче.
Однако, работа с ЛЮБОЙ динамической переменной ЕДИНООБРАЗНА для всего Паскаля!!!

1. Создаём ТИП УКАЗАТЕЛЯ, чтобы ссылаться не ещё неописанный объект.
2. Описываем ТИП объекта любой сложности, на имя которого и ссылается указатель.
3. Создаём данный объект на куче, выделяя для него память и инициализируя компоненты
4. ЗАТО обращаемся к объекту С ПОМОШЬЮ ПЕРЕМЕННОЙ, создаваемой в статической области программы и имеющей наш описанный ранее указательный тип.
5. Кроме этого мы всегда должны адресоваться к ТИПУ. Системный тип pointer не даст нам никакого доступа к зфпрашиваемому объекту.
Только к начальному адресу.

Давайте пройдёмся вдоль алгоритма:


const
maxC = 40; maxR = 24;

type
SAVMSC = ^scrT;
scrT = array[1..maxR] of array[1..maxC] of byte;

var
scr: SAVMSC absolute $58;

То есть, получается некое разделение труда.
- Сам указатель нетипизирован никак, лишь обозначен как указатель.
- Типизация объекта добавляет указателю тип этого объекта
- Создав переменную такого же типа мы из Паскаля с полным правом работаем с этой переменной как с типизированным объектом.

Из-за строгой типизации данных в Паскале совсем не тривиально создать такой объект, как байтовый буфер.

Сделаем это на Куче, и сделаем это с массивом байт.

Однако придётся вспомнить, что массив байт в Паскале всегда должен иметь РАЗМЕРНОСТЬ, как же быть, если массив наш динамический и размерности его мы не знаем?

Тогда вдумайтесь о чём я пытался сказать ранее: Мы создаём void указатель и типизируем его типом наших данных. ПРИ ЭТОМ ОБЪЕКТ НА КУЧЕ НЕ СОЗДАН! Просто компилятору уже известны НЕКОТОРЫЕ его атрибуты.

Переменная для доступа к объекту уже будет иметь эти атрибуты, а также те, которые мы дополнительно придадим при создании объекта.


const
(* Пусть это от балды будет размером нашего буфера *)
(* Или Width*Heigth без разницы это определяется динамически в рантайме*)
bytes = 960;
type
arrP = ^arrT; (* Тип Указателя на массив *)
(* Тип Статического массива из одного байта *)
arrT = array[1..1] of byte;
var
(* Переменная, пригодная для работы с одноразмерными массивами *)
(* Размерность здесь существенна, а размер в байтах нет, так как массив на Куче НЕ СОЗДАН. *)
A: arrP;
begin
(* Создаём на Куче массив байтов нужного размера *)
getmem(A, sizeof(byte)*bytes);
... (* Работаем с ним *)
(* Освобождаем память *)
freemem(A, sizeof(byte)*bytes;
end.

zen

Oleg N. Cher
12.07.2018, 06:47
Вообще говоря, чем дальше я забираюсь в Паскаль, тем мне больше нравится Никлаус Вирт.
Не зря он Нобелевский лауреат и автор Модулы2 и Ады, кроме Паскаля, разумеется.Ещё и автор языка Оберон, который породил Оберон-2 и Компонентный Паскаль, а потом Оберон-07. Тоже очень интересные языки, я сейчас с ними плотно работаю. А Модула-2 породила Модулу-3 и Modula-2 R10, а также Objective Modula-2.

Поправлю: Вирт лауреат премии Тьюринга, а не Нобелевской, что не уменьшает его заслуг. Также он не участвовал в разработке Ады (это язык, разработанный Жаном Ишбиа и большим комитетом, но без Вирта). В основу разработки Оберона лёг девиз Эйнштейна: «Делай просто, насколько возможно, но не проще этого». Ишбиа же сказал: «Вирт верит в простые решения сложных проблем. Я не верю в такие чудеса. Сложные проблемы требуют сложных решений».

ezswift
12.07.2018, 11:32
Олег,
Я, просто, столкнулся по настоящему с ТЕОРЕТИКАМИ языков программирования начиная с языка Лиспа.

МакКарти занимался ЧИСТО теоретическими проблемами и даже ПРЕДСТАВИТЬ не мог, что он на острие Оккама. Это его френд запрограммировал ЛИСП (На чём - не важно. Может на Ассемблере, может просто в машкодах)
Цитата из Оккама - "Не плоди сущности сверх необходимости". (Получше, Айнстайна, правда?)

Я почувствовал, что теория Языков важна!, но не понимал смысла...
...

И теперь я вижу, что Вы отметили существенные вещи:

- PL65 не попрёт из-за эклектичности. Нужно быть ЯВУ! Слишком низкоуровневый. Библиотеки не спасут.
- Си не догонит никогда даже ACTION! По скорости, по очевидности, для тех кто знает Атари.
- LISP не использует возможности машин, сделанных после него.

Ну, в общем, нам мало остаётся. Паскаль. Он УЧИТ учить, но САМ ЯЗЫК СЛОЖНО учить!

Так как он ОРГАНИЗОВАН Правильно для ВСЕХ машин, а не Си (Я, кстати, Си люлю!)
Си правильно ВЗАИМОДЕЙСТВУЕТ со всеми машинами. И шо це нам дало?

Всё взаимодействие идёт по законам Си.

Но, здесь о Паскале. Тем более, не об уродливом. (А их в Атарьском мире таких, даже, много!)

Я даже рад, кстати, что Вирт лауреат Тьюринговой премии. Оба думали об одном и том же...
Как организовать нам машину!

Сделали же... А УРОД Нобель, промёрзши во льдах, убил даже понятие ПРЕМИЯ!
Теперь Нобелевка - это проституирование на ... далее по списку.
zen

Shiny
12.07.2018, 11:56
Си не догонит никогда даже ACTION! По скорости, по очевидности, для тех кто знает Атари.

ACTION! по простоте гораздо удобнее. Хотя, его линковка оставляет желать лучшего.

ezswift
12.07.2018, 18:36
Шынни, Action! они рассматривали ТОЖЕ как только ПОДХОД к языку.

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

Невозможно таскать с собой рантаймы везде. От этого сдохло ещё полдесятка языков для Атари.

Они же пробовали ДАЖЕ всунуть рантаймы в ДОС. И шо?
Язык работает реально О-о-очень быстро, но ГДЕ ОН? (Это - DVC C компилятор...)
Разумеется, никакой переносимости В НАТУРЕ!
НАФИГА ЭТО НУЖНО,ТОВАРИЩИ?

Честно, кроме CLSN, я не вижу других языков. Даже бросил PL65, который разлюбил.

Просто, посмотри коды... Учти, кроме этого, что это - СТАНДАРТНЫЕ средства языка.
Никаких трюков.

Я и сам балдею от запретов жесткой типизации переменных Паскаля, но я уже понял ДЛЯ ЧЕГО она нужна.
Для ПРОГРАММИСТА, а не для программы! (И для компилятора ТОЖЕ, но это вторично.)

Я, когда-нибудь напишу что-то вроде оконной(терминал) библиотеки для Паскаля.
НО Я - НЕ ПРОГРАММИСТ.
Уже половина готова. (Я с CLSN знаком только полгода... С Паскалем не знаком. Документации по Паскалю нет...)
Нужно алгоритмы выбирать, а я не умею. Пытаюсь.

Удачи, zen.

ezswift
15.07.2018, 13:47
Я тут на две недели пропал, так как не мог с лёгкостью, присущей PL65 возиться с байтами.

Это - та самая типизация в Паскале!

Сейчас я многое узнал.
Доступ к значениям в Паскале контролируется и Вы не сможете доступиться НИ ДО ЧЕГО, кроме начального адреса при помощи void указателя.

В общем, с трудом написал блочные функции для работы с окнами.

Как Вы помните, - это сохранить блок экрана и затем восстановить его из Кучи. В pl65 не было кучи.
Каждый блок экрана занимал ОЗУ. Только CLSN даёт нам эту возможность!

Я честно пробовал CLSN средства, но это было НЕВЕРОЯТНО медленно. Побайтовый вызов и всё через стек...

Сейчас - всё равно через стек, но только по числу вызова строк. Строки прорисовываются машинными кодами встроенных программ.

Вот код:


(* Block procedures for CLSN Pascal*)
const
Описали доступные границы. Паскаль будет за ними следить и при случае даст ошибку.
maxC = 40; maxR = 24;

type
Описываем экран как массив строк, состоящих из массива колонок экранных байтов.
scrT = array[1..maxR] of array[1..maxC] of byte;

Описываем системный ярлык Атари как указатель на экран типа scrT
SAVMSC = ^scrT;

Описываем буфер как одномерный массив из одного байта.
Идея в том, что нам далее понадобится адрес начала этого массива, создаваемого на Куче.
bufT = array[1..1] of byte;

Так как мы работаем в динамической области памяти, мы можем пользоваться ТОЛЬКО указателями.

Описываем указатель на буфер как сущность способную работать с типом bufT, то есть с массивом байт.
bufP = ^bufT;

Доступ к Куче ведётся через переменные и ТОЛЬКО. Это - указательные переменные!
var
Создали указательную статическую переменную для доступа к абсолютному системному адресу - SAVMSC.
scr: SAVMSC absolute $58;

Создали указатнльную переменную доступа к массиву.
buf: bufP;

Создали глобальные
(вообще говоря, для блочных функций нужны локальные, или нужно создать хендл, передающий данные)
переменные координат блока.
x,y,w,h: byte;

Создали счётчики по координатам.
i,j,k: integer;

Создали управляющую переменную для просмотра состояний программы.
ch: char;

Предполагается, что мы в динамической области создадим место для помещения туда части экрана.
Так как переменная buf для нас постоянный указатель на буфер, мы считаем её константой и нам
нужна временная переменная b для доступа к буферу... Нельзя рубить сук, на котором сидишь!
x,y,w,h - координатно-размерные переменные

(* Save Block to Buffer *)
procedure saveBlk(var x,y,w,h: byte);
var
j,k: integer; b: bufP;
begin
getmem(b, sizeof(byte)*w*h);
k := 1; b := buf; (*начальные значения*)
for j := 0 to h-1 do begin(*по строкам*)
move(scr^[y+j][x], b^[k], w); (*копируем строку в буфер*)
fillchar(scr^[y+j][x], w, 128); (*для индексации заменяем её инверсными пробелами*)
k := k+w; (* переходим к следующей строке в буфере *)

Здесь важно понять, что доступ к объекту ведётся ТОЛЬКО в соостветствии с его типом!
То есть Паскаль не даёт нам права обращаться к массиву буфера просто указателями,
только указателями типа bufP, для буфера!!!

end;
end;

(* Restore Block from Buffer *)
procedure restBlk(var x,y,w,h: byte);
var
j,k: integer; b: bufP;
begin
k := 1; b := buf;
for j:=0 to h-1 do begin
move(b^[k], scr^[y+j][x], w); (* Взяли и поменяли источник и приёмник. Вуаля! *)
k := k+w;
end;
freemem(b, sizeof(byte)*w*h); (*будьте честными. верните арендованное!*)
end;



(* Main program *)
var
B: bufP;
begin
x:=3; y:=3; w:=12; h:=6;
ch := readkey;
saveBlk(x,y,w,h);
ch := readkey;
restBlk(x,y,w,h);
ch := readkey;
end.

Привет ВСЕМ! zen.

Shiny
15.07.2018, 13:57
ezswift, Ваше усердие достойно уважения.

ezswift
15.07.2018, 14:13
Вы тоже, Шынни заняты нужным делом.

ВСЯКОЕ! Всякое усердие достойно уважения.
Разница между попугаем и человеком в том, что человек усерден.

Попугай рыщет за жрачём, ищет за попугаехой, и... ВСЁ!

А мы пробавляемся как можем.

Нерадостно, но достойно.

ezswift
16.07.2018, 12:16
Поскажите, в чём беда...

Реальность показывает, что ВСЁ в норме!
Однако программа запускается не всегда.
WHAT TO DO, HOW TO BE?

zen

shurik-ua
16.07.2018, 12:56
Опять-таки, я только в МГУшных статьях нарыл, какие-то приемлемые сведения...
Ещё Дональда Кнута почитайте - там можно найти множество структур и алгоритмы к ним.

ezswift
17.07.2018, 14:53
Спасибо, конечно, большое. :)
Дайте пожалуйста ссылку на pdf, если есть.

Но мы здесь не из-за программирования, а из-за Атари.
Я вовсе не собираюсь заучивать алгоритмы, хотя приходилось этим заниматься для своих целей.

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

- - - Добавлено - - -

Кажется понял.
Думаю, хромает эмуляция управления памятью в эмуляторе.
В Алтирре всё запускается стабильно. Уря, товарищи!

- - - Добавлено - - -

Ну, что же?
Пора предлагать структуру хендла окна...

Это будет запись.
В записи будет храниться описание конкретного окна.

Предлагается следующее...
_с - Капча окна
_a - флаг активности
_i - флаг инверсии
_x, _y - координаты
_w, _h - размеры
_b - указатель на буфер на куче

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

Я может быть что-то упустил... подскажите, pls.
Не беда.
... Надо добавить глобальное описание рамок окна. То есть псевдографика и её инверсия.
... Зато, строку меню добавить локально в хендл.

zen

ezswift
25.07.2018, 11:31
Вот моя цитата из предыдущего поста:
"Структура оконной системы легче всего описывается стеком."

Вопрос в том, понимаете ли Вы, что Стек, Кью, Дек,Три - это РОЛИ.
А вот Актёр = Список.

Вы также можете понять, что ЛЮБАЯ роль = ОГРАНИЧЕНИЕ Актёра!!!
Он САМ - сможет сыграть ВСЁ!

Я это к тому, что ВСЕ динамические Ёрзания - это Роли!!!

Есть Главная роль = Список!
Есть Универсальная Главная роль = Многосвязный Список!

А после этого идёт сценарий...

По сценарию можно из Многосвязного списка создать ВСЕ нижеуказантые сущности...

Стек = вход и выход с одной стороны!
Кью = вход с одной, а выход с другой стороны!
Дек = Куда хочешь заходи, откуда хочешь выходи!
Три = Со входа ЗАХАДИ ДАРАГОЙ, а выхода два:
- налево пойдёшь - БАШКА ЙОК
- направо пойдёшь - ЖОПА СЫКТЫМ
В общем, грустно.

Я всё к тому, что ВСЕ роли надстраиваются над односвязным, либо над многосвязным списком!!!
Но они = ПРОИЗВОДНЫЕ от списков!

Реализуются, кстати, и массивами!

ezswift
26.07.2018, 17:14
Наиболее универсальным линейным списком, является Двусвязный Список.

Это - мультиконтейнер, с которым можно работать как с единым объектом.
В то же время, так как такой мультиконтейнер программно абстрагирован от содержимого каждого узла, то приходится описывать также и содержимое узлов.

Сначала давайте разберёмся с узлами списка.

Вот прямая аналогия - вагон состава:
1. Узлом списка является Платформа (node)
2. Частью двусвязного списка Платформа становится ТОЛЬКО после того как к ней прикреплены передняя и задняя сцепки (prev, next). На платформе они антисимметричны!, то есть зацепление происходит только если применены разные сцепки.
3. Контейнер может быть разного типа (сухогруз, цистерна...), но должен быть правильно описан.
65850

Вот картинка схемы двусвязного узла:
Аналогия почти совершенная.
65848

Откуда ведётся доступ?
Разумеется из кабин машинистов, то есть машиниств Тягача и машиниств Толкача.

Зачем такое излишество? Это - для свободы доступа к любому узлу и для поддержания Целостности списка. (при длинном составе, возможен разрыв сцепок, поэтому аналогия прослеживается и здесь.)
Даже, потеряв один адрес (толкача, например) мы можем методом траверсирования (от тягача) его просто восстановить. Это справедливо для любого звена списка.

А вот сам двусвязный список:
Как Вы видите, присутствует и тягач и толкач (header, trailer).
65849

Роль создаваемых переменных - только коммуникативная.
Это аналог радиосвязи от сортировочной станции (или от выпускающей станции) до машинистов.

Теперь о различных функционалах списка.
Идея такова: в заыисимости от задачи, создаётся переменная, которая упрощает (сильно ограничивает) доступ к элементам списка.
То есть сам список может значительно больше, но задача этого не требует.

1. Самый простой функционал - стек.
Создаваемая для коммуникации переменная может доступаться только до header. И с header можно либо взять, либо положить туда какие-то значения.

2. Следующий функционал - очередь посложнее, так как создаваемая для коммуникации переменная может доступаться и до trailer(только для того чтобы положить данные) и до header, чтобы снять оттуда данные.

3. Следующий функционал - двунаправденная очередь.
Это самый интересный функционал и сегодня я Вам продемонстрирую программку, которая с ним работает.
Так как это всё же очередь, то создаваемая для коммуникации переменная может доступаться как до header, так и до trailer. Однако цель диктует расширение доступа то есть можно как положить, так и забрать данные как с header, так и с trailer.

Во всех случаях описанных функционалов доступ к внутренним узлам невозможен, то есть траверсирования в чистом виде НЕТ! (А список это может!)

Вот текст программки:

program deque;
type
(* Container type *)
infoT = Char;

(* Types for DLL *)
linkT = ^nodeT;

nodeT = Record
data: infoT;
next, prev: linkT;
End;

(* Type for deque *)
deqT = Record
front,rear: linkT;
End;

(* Useful functions *)
Function isEmpty(Var deq: deqT): Boolean;
Begin
isEmpty := (deq.front = nil);
End;

Procedure printDeq(Var deq: deqT);
Var p: linkT;
Begin
WriteLn( 'Printing Deque ...' );
writeln;
If isEmpty(deq) Then
Begin
WriteLn('<empty>'); Exit;
End;
p := deq.front;
While p <> nil Do
Begin
Write( p^.data, ' ' );
p := p^.next;
End;
WriteLn;
End;

Procedure pushFront(Var deq: deqT; Ch: infoT);
Var neo: linkT;
Begin
new(neo);
neo^.next := deq.front;
neo^.prev := nil;
neo^.data := Ch;
If deq.front <> nil Then
deq.front^.prev := neo
Else
deq.rear := neo;
deq.front := neo;
End;

Procedure pushRear(Var deq: deqT; Ch: infoT);
Var neo: linkT;
Begin
new(neo);
neo^.next := nil;
neo^.prev := deq.rear;
neo^.data := Ch;
If deq.rear <> nil Then
deq.rear^.next := neo
Else
deq.front := neo;
deq.rear := neo;
End;

Function popFront(Var deq: deqT): infoT;
Var toDelete: linkT;
Begin
popFront := #0;
If isEmpty(deq) Then Exit;
toDelete := deq.front;
deq.front := toDelete^.next;
If deq.front <> nil Then
deq.front^.prev := nil
Else
deq.rear := nil;
popFront := toDelete^.data;
Dispose(toDelete);
End;

Function popRear(Var deq: deqT): infoT;
Var toDelete: linkT;
Begin
popRear := #0;
If isEmpty(deq) Then Exit;
toDelete := deq.rear;
deq.rear := toDelete^.prev;
If deq.rear <> nil Then
deq.rear^.next := nil
Else
deq.front := nil;
popRear := toDelete^.data;
Dispose(toDelete);
End;

Procedure initDeq(Var deq: deqT);
Begin
deq.front := nil;
deq.rear := nil;
End;

Procedure killDeq(Var deq: deqT);
var D: infoT;
Begin
While not isEmpty(deq) Do D := popFront(deq);
End;

Var mD: deqT;
Begin
initDeq(mD);
pushFront(mD, 'A');
pushFront(mD, 'B');
pushRear(mD, 'F');
pushRear(mD, 'G');
writeln;
writeln('Forward traversing');
printDeq(mD);
writeln;
writeln('Backward traversing');
writeln('Printing Deque ...');
writeln;
While not isEmpty(mD) Do
WriteLn( popRear(mD) );
killDeq(mD);
End.

А вот результат:
65851

zen

Shiny
26.07.2018, 18:05
гм. нам читали эти лекции о списках на первом курсе. выглядело как rocket science.

ezswift
26.07.2018, 19:57
Здравствуйте Шынни.

Ну, честно скажу, что то, что тема сложна, то это заслуга препов.
Методика преподавания - от простого к сложному.
Всегда в первую очередь давали самый простой Односвязный список.
В то же время, при нём присутствовали ВСЕ сложности, связанные со списками!

Для чего нужно заглавное звено? Можно ли программировать без него.
На чём можно строить Деки, Стеки и очереди?

То есть, вроде просто, но жуть как сложно, так как Односвязный список НЕ УНИВЕРСАЛЕН.

Начинали бы с двусвязного... Усложнение мизерное, но на нём сразу можно делать все функционалы.
То есть выучил двусвязные и УЖЕ знаешь односвязные.

Хочешь строить деревья - пожалуйста...

Подпрограммы работы с Деком пригодны и для Стека и для Очереди.
До сих пор интернет полон байками о том, что на двусвязном списке нельзя построить стек.
МОЖНО!

Для полной библиотеки Двухсвяхных списков нужно ещё маленько функций для доступа в середину списка.

Всё остальное готово.

zen

bigral
26.07.2018, 20:37
гм. нам читали эти лекции о списках на первом курсе. выглядело как rocket science.

Думаю нам тоже это читали на первом курсе по "паскалю" но так как препод стоял у доски и шото мямлил себе под нос читая прямо с какой-то переводной книжки красного цвета про borland 5.5 то "в теме" ни одного человека на потоке не было (может и были но я их не знаю). Это я про наше образование.

Но и про сам вопрос тоже не все так просто. Вот нам дают некую парадигму что можно добавить контейнер или вставить или убрать... выглядит все как сказка какая-то, особенно для меня который к тому времени уже знал как написанны игры под ZX. Я думал: ОГО, чувакам пофиг сколько кода и данных надо будет чтоб организовать "КУЧУ памяти" и потом ее обслуживать. А тем кто попридумывал эти списки про КУЧУ думать и не надо было она у них просто УЖЕ была и все тут. Не говоря уже о том что 99% алгоритмов в заумных книгах никогда не обсуждают вопросы лимитов адресного пространства или глубину машинного стека (просто подразумевается что стека должно хватить так же как и памяти). Кроме того не рассматривается НИКОГДА альтернатива... ну простой массив. Короче все эти заумные вещи которым учат (а их там сотни! ооп, патерны, рекурсионные алгоритмы) довольно бесполезные вещи когда надо написать 99% эффективный код (а в нашем 8-bit хобби это именно так).

Shiny
26.07.2018, 21:07
Самое интересное - это применение. Например, можно ли сортировать двухсвязный список? и применима ли модель стека?

На zx я тоже баловался с Паскалем из-за рекурсивного алгоритма. Но Hisoft беден по сравнению с Borland.

- - - Добавлено - - -


А вот результат:

чот я не понял - вроде бы должно быть A B G F

bigral
27.07.2018, 13:48
Самое интересное - это применение. Например, можно ли сортировать двухсвязный список? и применима ли модель стека?

Можно сортировать любое "множество" (в конце концов можно эмулировать поведение реализуя нужный интерфейс). Да и вообще разница только в реализации (скорость\ресурсоемкость доступа, добавления, замены, убирания зависит от реализации). Применим ли стек... ГЫ, алгоритмов сортировки придумали штук 50...80 вполне возможно что стеки в некоторых из них вполне применимы. Кроме того стек реализованный в процессорах это МАССИВ с дополнительной функцией быстрого занесения\вычитывания по специальному указателю.

Shiny
27.07.2018, 13:58
Можно сортировать любое "множество" (в конце концов можно эмулировать поведение реализуя нужный интерфейс)
ой ли? а где множества реализованы, кроме в Борман Паскаль?



Кроме того стек реализованный в процессорах это МАССИВ с дополнительной функцией быстрого занесения\вычитывания по специальному указателю.
упрощенная аналогия

ezswift
27.07.2018, 14:47
Самое интересное - это применение. Например, можно ли сортировать двухсвязный список? и применима ли модель стека?

На zx я тоже баловался с Паскалем из-за рекурсивного алгоритма. Но Hisoft беден по сравнению с Borland.

- - - Добавлено - - -



чот я не понял - вроде бы должно быть A B G F

Не, он засовывает AB спереду, а FG сзаду. В обратном порядке.

А Множества (Sets) реализованы в CLSN Pascal

type
dir_type = (up,down,left,right)
dir_list = set of dir_type;

Тут меня другое поразило:
Вот эта строчка означает, что минимально программируя можно считывать джойстики!
isEmpty := (deq.front=nil);
В строчке проверяется булево значение поля front в Деке. То есть в скобках - Вопрос: Равно ли значение deq.front = nil?

Точно также можно проверять джойстики!!!
В CLSN как и в PL65 они заданы массивом системных адресов.

Но никто не сказал, действует ли системная логика Атари в CLSN! (могла быть перепрограммирована...)

Теперь знаю - действует.

Вот вариант для Бейсика:
20 S = STICK(S)
30 DX = (S = 5 OR S = 6 OR S = 7) - (S = 9 OR S = 10 OR S = 11)
40 DY = (S = 5 OR S = 9 OR S = 13) - (S = 6 OR S = 10 OR S = 14)
50 RETURN

Для CLSN напишу позже...
zen

Shiny
27.07.2018, 14:58
а, миль пардон, название смутило.

ezswift
27.07.2018, 15:54
Я не проверял, так как нуня писать программку, но про джойстики, приблизительно так:


(* Globals *)
type
stk = record
dx,dy: byte; (* to be returned to main program *)
end;
var
s: byte;

procedure rdStick();
var i: integer; s: byte;
begin
for i := 0 to 1 do begin
s := stick[i];
stk.dx := (s=5 or s=6 or s=7) - (s=9 or s=10 or s=11);
stk.dy := (s=5 or s=9 or s=13) - (s=6 or s=10 or s=14);
end;
end;
Это - О-о-очень быстро.

И поправил быстро. stick[] - это системная переменная Паскаля.
zen

Shiny
27.07.2018, 16:08
на бейсике это выглядело так:

REPEAT
UNTIL STICK(0)<>15
S=STICK(0)

205 V=(S=13)-(S=14);14-вверх,13-вниз
NY=PY+V
V=(S=7)-(S=11);7-вправо,11-влево
NX=PX+V

ezswift
27.07.2018, 16:19
И ещё.
Если в модели Deque не применять подпрограммы для "Сзаду", то это будет Кошерный Стэк, так как все остальные подпрограммы работают и с ним.

Мне бы хотелось написать библиотеку для Двусвязного списка, но пока - в поиске.
Он, бедолага, вообще "За всё - про всё..." . Не могу определить НЕОБХОДИМЫЕ!

А применение Стэка - это линейное меню.
То есть, как хранилище всёх подменю...
Высвечено последнее (на вершине стэка)
Если не нужно, Esc. Стирается.
Предыдуще-Вызванное-Подменю высвечивается.

Собственно для ЭТОГО был создан Компьютер.
Автоматизация, майт-ли...
zen

- - - Добавлено - - -

А ТАМ учтены ещё и диагональные движения.
Я соглашусь, что это - не везде обязательно.

ezswift
11.08.2018, 16:41
Моя Борьба.

Здравствуйте, мои дорогие и уважаемые восьми-БИТНИКИ и восьми-ХИППИ!

Снова с Вами я, Евгений Золотарёв и сегогдня меня торкнуло в философию.

Ну, скажем, в философию языков программирования. За исключением Бейсика!!!
Так вышло, что я его сразу отрыгнул...

Как ни странно, но здесь на форуме, я почти НИЧЕГО не писал о ПЕРВОЙ моей любви в программировании. Языке Си.

Это была ПЕРВАЯ любовь! Значит, смелая, трудная и сумбурная.

Я искал Си, но это не был КАКОЙ-ТО из РАЗНЫХ диалектов.
Мне нужен был ЕДИНСТВЕННЫЙ!

Им стал диалект CC8.

Начинал я с доступного, с Deep Blue C, прошел через разные (были весьма кручёные!) ... и НАКОНЕЦ!.. Вот ОНА - ЛЮБОВЬ!!!

Что я для неё сделал?
Родил библиотеку непосредственного доступа к видеопамяти и библиотеку доступа к куче, в смысле к оставшейся, после компиляции ОЗУ. (Ищите НЮАНС!!!)

Устал!!!

Далее, не стал возиться и стал искать новые смыслы в языках.

Нашёл в Лиспе.

Так как никто, кроме меня с ним и не возился, пришлось БУРАВИТЬ ПОРОДУ!
Написал приличные (разумеется, никому не нужные...) библиотеки, макросы и пр.

Просто, язык, - к этому моменту, уже 30 лет не мог предложить НИЧЕГО из области ПРОГРАММИРОВАНИЯ!
Лисп требует память, а ЭТОТ Лисп не использовал даже память 130XE.

Устал!!!

Тем не менее, я познакомился с новой ЭКЗИСТЕНЦИЕЙ!

Она меня кинула в озёра Вам уже знакомые.

Я перенёсся к PL65.
Жутко красиво и интересно!

Написал пару-тройку библиотек и несколько присущих языку конструкций.
Проникся и въехал.
Круто!

Устал!!!

Занялся Ассемблером МАЕ!
Раньше я программировал на разных, но относился к Ассемблерам, как к вспомогательным средствам.

Устал!!!

Занялся Паскалем.
В принцыпе и раньше пытался, но не вышло.
Уж чересчур убогие они... для Атари. Нет ЛЮБВИ... Трах один...

Теперь CLSN - моя любовь.
ПРОНИКСЯ! Понял идеологию. Новые ЗНАНИЯ!

А сейчас, увидев одну программку на BASIC, снова вернулся ... к Си!!!
Идея родилась, майтли...

Маркс говорил, что история идёт по кругу...
Я с бородачами не спорю! - Защекочет!!!

Дело в том, что CLSN, как и МАЕ Ассемблер, сам сидит в расширенной памяти!
То есть, предлагая её как Кучу, он в момент компиляции с ней ИНТЕРФЕРИРУЕТ!
То есть, однозначно дать прогноз о памяти НЕЛЬЗЯ.

А вот милый мне Си, сидит в основной памяти и НЕ ЗАБОТИТСЯ о Куче!!!
А по функционалу - почти соответствует!..

То есть, если я напишу нормальную программку обработки кучи, я и впрямь стану прогармонистом!!! ИГРАЙ МОЙ ГАРМОН!

Я это к тому, что следующий пост будет о баньках памяти Атари!
О том как их программировать.

zen

- - - Добавлено - - -

Здравствуйте, Дорогие Атаристы Восьмибитники!

Воодушевлённый великолепной работой CLSN Паскаля с Кучей, я было попытался сорганизовать демку, объясняющую работу с банками памяти, но так как CLSN сам сидит в расширенной памяти и рассматривает почти ВСЮ 48KiB расширенную память Атари как Кучу, это вышло проблематичным, а тема меня заинтересовала...

Ну, у Атари много языков и я стал искать подходящий. Не только, для демки, а и для функционала тоже.

Взялся за CC8 Си-Компилятор. В нём есть масса вкусностей. Хотел на нём создать обработку Кучи.
В своё время я создал на нём стэк для проверки работы его списков, однако не на Куче.
Обломилось. Его рантаймы вылазают в окно доступа к Банкам, то-есть интерференция будет.

И вдруг вспомнил про PL65.
Там указатели ровно такие же как в Паскале!
А распределение памяти о-о-очень удобное.

Итак, теория...
В 130XE наличествует 4 банка памяти по 16KiB каждый.
Подключаются они в окно доступа с адреса $4000 и за управлением банками следит регистр PORTB $D301.

PORTB: По умолчанию можно считать все биты установленными в 1!

Бит 7 - Включает программу СелфТеста 1=ON
Бит 6 - НЕ ИСПОЛЬЗУЕТСЯ =1
Бит 5 - Доступ АNTIC к расширенной памяти 1=OFF
Бит 4 - Доступ 6502 к расширенной памяти 1=Основная 0-Расширенная
Бит 3 - Выбор подключаемого банка...
Бит 2 - Выбор подключаемого банка ИТОГО: 2 бита (банки нумеруются от 0 до 3)
Бит 1 - Включает Бейсик. (Выключить, если программируете не на Бейсике!!!) 1=OFF
Бит 0 - Включает OS-ROM (ПЗУ Системы) 0=OFF
Это необходимо для использования переписанного ПЗУ. Так работают все Трансляторы...

- Система следующая Вы копируете старое ПЗУ системы в ОЗУ, затем устанавливаете здесь заветный нолик
(вместо ПЗУ подставляется ОЗУ!) и снова копируете ПЗУ на прежнее место и ПЗУ становится ОЗУ, а затем можно и Русский язык вставлять...
Я думаю, с Бейсиком та же история, то есть можно всё-таки вместо READY вставить ГОТОВ...

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

У меня расширение по схеме RAM574XE - 32 банка по 16KiB.
Фактически оно повторяет 8 раз схему 130XE.

Очевидно, что надо от каких то управляющих возможностей отказываться ради адресации банков.
Это делается именно трансляцией ПЗУ.

Отказываются обычно от СелфТеста, и Доступа Антика. Так как есть неиспользуемый бит, его тоже используют.
Итого получаем 5 битов Адресации. Это число=31(Дес), то есть адресуются банки (от 0 до 31). Вуаля.

А вот рабочая программа для PL65.
Она тестирует стандартные 4 банка Atari 130XE.


!====================================!
! BANKS.PRG !
! Using 130XE Extended Banks in !
! PL65 Programming Language !
!------------------------------------!
! Evgeny Zolotarev,(aka 576XE), 2018 !
!====================================!

INCLUDE TERMINAL.LIB

POINTER BP BYTE BV BASED BP
CONST PORTB=$D301
CONST BKFRM=$4000

!- Some kind of String Array --------!
STRING S0$[30] DATA "Tiffany was HERE";
STRING S1$[30] DATA "Beauford was HERE";
STRING S2$[30] DATA "Ron was HERE";
STRING S3$[30] DATA "Nuke was HERE";
STRING S4$[30] DATA "Sam was HERE";

!- Wait for Any Key Pressed ---------!
PROC anyKey()
CONST none=255
BYTE CH=764
BEGIN
WHILE CH=none DO ENDWHILE
CH=none
END
!- Clear Screen Procedure -----------!
PROC cls()
CONST clr=255
BEGIN WRTSTR(CHR$(125)) END

MAIN()
!- Local Variables ------------------!
INT I,J STRING S$[30]
BEGIN
cls() WRTLN("Press <Return> to Continue") CR() anyKey()

FOR I=0 TO 4 DO
IF I=0 THEN WRTLN("Writing to NORMAL Memory") ENDIF
IF I<>0 THEN WRTSTR("Writing to BANK #") WRITE(I) CR() ENDIF
!- Some kind of BASIC's 'Read A$' ---!
CASE I
OF 0 DO S$=S0$ ENDOF
OF 1 DO S$=S1$ ENDOF
OF 2 DO S$=S2$ ENDOF
OF 3 DO S$=S3$ ENDOF
OF 4 DO S$=S4$ ENDOF
ENDCASE
!- For BASIC use 221 instead of 223 -!
BP=.PORTB BV=223+I*4
FOR J=0 TO LEN(S$)-1 DO
BP=.BKFRM+J BV=ASC(S$[J,J]) WRTLN(S$[J,J])
NEXT
CR() CR()
NEXT

WRTLN("Press <Return> to Continue") CR() anyKey() cls()

FOR I=0 TO 4 DO
IF I=0 THEN WRTLN("Reading from NORMAL Memory") ENDIF
IF I<>0 THEN WRTSTR("Reading from BANK #") WRITE(I) CR() ENDIF
CASE I
OF 0 DO S$=S0$ ENDOF
OF 1 DO S$=S1$ ENDOF
OF 2 DO S$=S2$ ENDOF
OF 3 DO S$=S3$ ENDOF
OF 4 DO S$=S4$ ENDOF
ENDCASE
BP=.PORTB BV=223+I*4
FOR J=0 TO LEN(S$)-1 DO
BP=.BKFRM+J WRTSTR(CHR$(BV))
NEXT
CR() CR()
NEXT
END

ezswift
11.03.2019, 16:43
Здравствуйте, ДРУЗЬЯ!

Недавно произошел большой ПРОРЫВ в Паскальском деле.
Я, будучи СОВСЕМ не программистом, но знатоком многих нужных языков высокого уровня, всё же решился пообщаться с Корифеями Атари!

Проблема была в том, что ДВА этих ХитроМырдина, а именно Атари и Паскаль на протяжении ГОДА мешали мне создать программу инвертирования экранных символов!

1. Что есть в Атари?
В Атари есть Атари Бейсик, написанный на Ассемблере и прекрасно работающий на 1,79 МГц.
ПОЧТИ ВСЕ творцы и творцыцы языков программирования так или иначе используют ВНУТРЕННИЕ процедуры Атари Бейсика для своих целей!!!
Однако известно, что в Атари Бейсике ВСЕ переменные - ЦЕЛЫЕ!!!

2. Что есть в Паскале?
А вот точного, как в PL65 указания, КАК Паскаль воспримет данные НЕТУТИ!

Сплошной обман, длиною в ГОД!

Тем не менее, связавшись с Отцами Нации, я извлёл из них ПОЛЬЗУ!
(* Славься Кей, Славься Кей!!! *)

Кароче...
Сначала я поблагодарю Карстена и Роланда из AtariAge!!!

И вот - код инверсии строки экранных байтов символов для GR.0 Атари.
Можно рисовать курсор меню как инверсную строку!!!


program cas;
const
Esc=#27;
var
sMem : word absolute $58;


procedure invLine(x,y,len : byte);
var
sAdr : integer absolute $f2;
_l : byte absolute $f0;
begin
_l := len;
sAdr := sMem + y*40 + x-1;
inline($ac/_l/ (* ldy #_l *)
$b1/$f2/ (* lda($f2),y *)
$49/$80/ (* eor #$80 *)
$91/$f2/ (* sta($f2),y *)
$88/ (* dey *)
$d0/$f5); (* bne -9 *)
end;


(*= Main Procedure =================*)
(*- Locals -------------------------*)
var
_X,_Y,_L: byte; _C: char;
begin
writeln; writeln; writeln;
_X := 0; _Y := 0; _L := 40;
repeat
_C := readkey;
while _C='' do ; (* Wait... *)
invLine(_X,_Y,_L);
until _C=Esc;
end.

ПРОСТО ПРЕДСТАВЬТЕ - ГОД!!!

Всё возможно для того, кто верит!
zen

Shiny
11.03.2019, 21:54
В Атари есть Атари Бейсик, написанный на Ассемблере и прекрасно работающий на 1,79 МГц.
меня больше устраивает TurboBasic XL - пошустрее и нв 65ХЕ работает.


И вот - код инверсии строки экранных байтов символов для GR.0 Атари.
Можно рисовать курсор меню как инверсную строку!!!

Знакомство пошло на пользу (:

ezswift
06.04.2019, 18:35
Привет Всем!

Опубликовал два видео, паскальского программирования для Атари.
Может, кто "чтотановаго" узнает. :)

Смотрите по ссылкам...
https://youtu.be/Bv3dwfXWW1M
https://youtu.be/HIjRv_I6eWI

zen

ezswift
27.04.2019, 19:16
Здравствуйте, здравствуйте.
Сегодня - реальный прогресс. Блоковая операция на CLSN Pascal машкодах.

Уж, поверьте, трудоёмкая и не простейшая задачка.
(На ассемблере в сто раз легче)

А вот, киношка...
https://www.youtube.com/watch?v=2RFRYEe3XcY

С уважением, zen

Shiny
27.04.2019, 19:22
Хотелось бы услышать сравнение с MadPascal

ezswift
20.12.2020, 13:17
Увы, я не работал с MAD-Паскалем, но зная ребят, которые в MAD, думаю, что всё там в порядке.

Правда, это консольный компилятор, значит нужен какой либо EMAX/VIM, чтобы цветные буковки и синтаксис отслеживать.
Конечно, хотелось бы попробовать, например на MAD-Паскале сваять простейший интерпретатор Пролога, но пока я абсолютно не в теме.

С наступающим Новым Годом!

ezswift
13.03.2021, 11:29
Сегодня опубликовал видео о Блочных процедурах в CLSN Паскале на Ассемблерной скорости.
Думаю разработать библиотеку таких процедур :)

На Дискорде создал сервер, посвящённый Атари. rutari#3806. Как пользоваться - без понятия...
На всякий случай, приглашаю всех.

ezswift
06.04.2021, 05:53
Создал Динамическую библиотеку ML доступа к экрану
Вот зипчик:
75159

Вот код главной программы:


(*==================================*)
(* CLSN Pascal DYNBLK Library *)
(* Demonstration Program *)
(* ZenSoft*)
(*----------------------------------*)
(*Evgeny Zolotarev,(aka 576XE), 2021*)
(*==================================*)
program savres;

type
bArr = array[0..0] of byte;
bufP = ^bArr;
ptrT = pointer;
adrT = word;
var
SAVMSC: adrT absolute $58;
buf: bufP;
srcP: ptrT absolute $ca;
srcA: adrT absolute $ca;
dstP: ptrT absolute $cd;
dstA: adrT absolute $cd;
sWid: byte absolute $d0;
sHei: byte absolute $d1;

include 'D1:DYNBLK.PAS';

(*= Main Procedure =================*)
label loop;
var
x,y,w,h: byte; ch: char;
begin
x:=2; y:=2; w:=20; h:=10;

loop:
ch := readkey; if ch=#27 then exit;
getmem(buf,sizeof(byte)*w*h);
saveBlk(x,y,w,h);
putFram(x,y,w,h);
invLine(x+1,y+1,w-2);

ch := readkey;
restBlk(x,y,w,h);
freemem(buf,sizeof(byte)*w*h);
if ch=#27 then exit;
goto loop;
end.

Видео1 https://www.youtube.com/watch?v=7L6l6ewUV9A
Видео2 https://www.youtube.com/watch?v=7L6l6ewUV9A

zen :)

ezswift
18.04.2021, 01:31
Добавил видео создания утилиты по автоматизации прорисовки красивого меню.
https://www.youtube.com/watch?v=pQ3GFialjx0
75249

Сама высчитывает все необходимые значения и выводит меню на экран.
В дальнейшем, позволит создать сохраняемые на диск фреймы меню.
:)
zen

ezswift
24.12.2021, 14:43
Здравствуйте, Друзья!
Добавил видео о разных типах меню на Атарьке в контексте разработки системы меню.
Так как типов больше всего в ДОСах, то там много и о различных ДОСах.
Демонстрирую программку управления меню в системе меню.
zen

ezswift
29.01.2022, 22:24
Коллеги, Здравствуйте!

Как и обещал, добавил ещё парочку видосов об эмуляции TopDOS Pro в Алтирре.

https://www.youtube.com/results?search_query=enzolot
zen