Ярославский Государственный Университет им. П.Г. Демидова
Опубликован: 06.11.2008 | Доступ: свободный | Студентов: 989 / 62 | Оценка: 4.50 / 4.00 | Длительность: 10:47:00
Лекция 5:

Язык РЕФАЛ: дополнительные виды памяти

< Лекция 4 || Лекция 5: 12 || Лекция 6 >

Обменная статическая и динамическая память Рефала

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

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

Работа обменной функции осуществляется термом

<имя обменной функции>/ <рефал-выражение>.

В результате вызова такой функции содержимое ящика помещается в поле зрения, а в ящик записывается указанное рефал-выражение (происходит обмен информацией между полем зрения и ящиком ).

Все ящики делятся на статические и динамические. Статические ящики описываются директивой SWAP:

<пробел> SWAP <пробел> <имя ящика 1>, ... ,<имя ящика N>

и не могут уничтожаться во время работы. Например, в следующем фрагменте программы

SWAP    X,Y
swxy         = k/X/'A'. k/Y/ 'B'. k/sw/ /X/ /Y/.
sw    Sx Sy  = k Sx k Sy k Sx...

при вызове функции

k/swxy/.

в ящики X и Y сначала будут занесены символы 'A' и 'B' соответственно, а затем в результате выполнения терма

k/sw/ /X/ /Y/.

содержимое ящиков поменяется местами.

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

k/new/ <рефал-выражение>.

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

EXTRN     new
swrr           = k/sw/ k/new/'A'/. k/new/ 'B'..
sw     Sx Sy   = k Sx k Sy k Sx...

Теперь при вызове функции swrr будут выполнены 2 вызова функции new, в результате которых образуются 2 ящика, например, с именами /%0321514/ и /%0674236/. В эти ящики будут записаны символы 'A' и 'B' соответственно, а затем при вызове функции sw их содержимое поменяется местами.

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

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

Функция gtr (взять по ссылке)

k/gtr/ <ссылка>.        ,

где ссылка - это имя статического (симовол-метка) или динамического (символ-ссылка) ящика, извлекает и возвращает содержимое ящика.

Функция rdr (прочитать по ссылке)

k/rdr/ <ссылка>.

копирует в поле зрения содержимое ящика.

Функция ptr (положить по ссылке)

k/ptr/ <ссылка> <рефал-выражение>.

добавляет в ящик справа рефал-выражение аргумента и возвращает пустое выражение.

Функция wtr (записать по ссылке)

k/wtr/ <ссылка> <рефал-выражение>.

заменяет в ящике содержимое на рефал-выражение аргумента и возвращает пустое выражение.

Функция swr (Обменять по ссылке)

k/swr/ <ссылка> <рефал-выражение>.

делает обмен между содержимым ящика и полем зренияполе зрения выдается содержимое ящика, а в ящик записывается рефал-выражение аргумента).

Рассмотрим пример работы с поисковым списком. Элементы списка будут располагаться в динамических ящиках. Структура каждого элемента:

<ссылка следующего ящика> (<ключ>) <данные>

Последний элемент в цепочке динамических ящиков имеет пустую ссылку следующего ящика, а на первый элемент ссылается статический ящик, который определяет голову списка. Функции Init, In, Find, Out производят

  1. инициализацию списка;
  2. ввод (добавление) элемента;
  3. поиск по ключу данных (возвращает ссылку на ящик, если найдет, или пустое выражение, если не найдет; кроме того, возвращает в статическом ящике pred ссылку на предыдущий ящик в цепочке списка);
  4. удаление элемента (по ключу).

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

0. Завершить программу.
1. Инициализировать список.
2. Добавить элемент списка.
3. Найти данные по ключу.
4. Удалить элемент по ключу.
Введите пункт меню.

и осуществляет эти действия.

LIST     START
         ENTRY      Init, In, Find, Out, List
         EXTRN      prout, card, numb, symb,
*                     ключ, данные, указатели головы списка и
*                     элементов текущего, предыдущего и след.
         SWAP       key, data, pl, cur, pred, next
         
*                     начальный вывод меню и выбор действия
List     =          k/menu/. k/act/ k/pnkt/..

*                      вывод меню
menu     =          k/prout/ '0. Завершить программу.'.
                    k/prout/ '1. Инициализировать список.'.
                    k/prout/ '2. Добавить элемент списка.'.
                    k/prout/ '3. Найти данные по ключу.'.
                    k/prout/ '4. Удалить элемент по ключу.'.
                   k/prout/ ' Выберите пункт меню.'.
							 
*                    выбор пункта меню
pnkt            =  k/numb/ k/card/..

*                    выбор действия
act             =
         /0/    =
         /1/    =  k/Init/. k/menu/. k/act/ k/pnkt/..
         /2/    =  k/In/. k/menu/. k/act/ k/pnkt/..
         /3/    =  k/inkey/. k/Find/. k/prd/. k/menu/. k/act/ k/pnkt/..
         /4/    =  k/inkey/. k/Out/. k/prd/. k/menu/. k/act/ k/pnkt/..
         E1     =
		  
*                     ввод ключа
inkey    E1     =  k/key/ k/numb/ k/prout/'Ключ: '. k/card/...

*                     ввод данных
indat    E1     =  k/data/ k/prout/'данные: '. k/card/..

*                     инициализация списка
Init     E1     =  k/pl/.

*                     добавление элемента
In       E1     =  k/pl/ k/new/ k/gtr/ /pl/. (k/rdr/ /key/ k/inkey/..)
                                                                   k/rdr/ /data/ k/indat/....

*                     поиск по ключу: опред. тек.эл. cur
Find            =  k/wtr/ /pred/. k/wtr/ /cur/ k/rdr/ /pl/..
                                                                    k/f1/ k/rdr/ /cur/..

*                     нет текущего? не найден : анализ текущего
f1              =   k/data/ 'Не найден'.
      S(R)c     =   k/f2/ k/rdr/ k/rdr/ /cur/...

*                         анализ текущего
f2  S(R)n(S(N)k)Ed   =   k/wrt/ /next/ Sn. k/data/ Ed. k/f3/ Sk.

*                     сравнение ключей k–key
f3    S(N)k      =   k/f4/ k/sub/ (Sk) k/rdr/ /key/...

*                    k=key? найден : следующий
f4    /0/        =
      E1         =  k/pred/ k/cur/ k/next/... k/f1/ k/rdr/ /cur/..

*                    вывод результата из данных
prd   E1         =  k/prout/ k/rdr/ /data/..

*                    удаление: есть ли ключ?
Out   E1         =  k/Find/. k/o1/ k/rdr/ /cur/..

*                    удаление: есть ли предыдущий?
o1               =  k/data/ 'Не найден'.
      Vc         =  k/o2/ k/rdr/ /pred/..

*                    удаление: есть ли предыдущий?
o2               =  k/pl/ k/rdr/ /next/.. k/data/ 'Удален'.
      Vp         =  k/o3/ k/rdr/ k/rdr/ /pred/...

*                    удаление: изменение предыдущего
o3   S(R)cEd     =  k/wtr/ /pred/ k/rdr/ /next/. Ed. k/data/ 'Удален'.

              END

Отметим, что в случае, когда в статическом ящике A находится ссылка на другой динамический ящик %B, к которой мы непосредственно не можем обратиться, то для получения данных последнего ящика нужно дважды вызвать данные:

k/rdr/ k/rdr/ /A/..

В результате первого вызова функции rdr в поле зрения появится ссылка на динамический ящик (как содержимое ящика A ), а в результате второго вызова этой функции в поле зрения будет возвращено содержимое динамического ящика. Это и используется в вышеприведенной программе. Прокомментируем некоторые сложные функции этой программы.

Добавление элемента определяется функцией In. Ее выполнение начинается с получения ссылки в ящике головы списка pl (для первого вводимого элемента списка эта ссылка пустая и означает конец списка, а для последующих данных, определяемых динамическими ящиками, она уже не пустая), которая является первой частью тройки добавляемого элемента. Затем осуществляется ввод ключа нового элемента и определение этого значения в структурных скобках и, наконец, ввод данных этого элемента и определение этого значения в качестве последней части тройки элемента. После этого эти данные записываются в новый динамически определяемый элемент, а ссылка на него записывается в ящик головы списка.

Поиск элемента по ключу определяется функцией Find и вспомогательными функциями f1, f2, f3, f4. Действия функции Find начинаются с записи в ящик pred предыдущего элемента пустой ссылки и с записи в ящик cur текущего элемента ссылки на первый элемент списка, находящейся в ящике pl головы списка. Затем для текущего элемента списка вызывается функция f1, которая в случае неуспешности поиска (ссылка на текущий элемент пустая) записывает в ящик data отыскиваемых данных фразу 'Не найден'. В случае же непустой ссылки на текущий элемент f1 вызывает функцию f2, передавая ей на анализ этот элемент. Функция f2 анализирует данные текущего элемента, разбивая его на ссылку на следующий элемент (записывается в ящик next ), ключ и данные (записываются в ящик data данных). Затем она отправляет этот ключ функции f3 (для сравнения с поисковым ключом в ящике key ), которая готовит разность ключей и отправляет ее для решения функции f4. Эта функция при равенстве ключей (разность ключей равна 0) заканчивает вычисления поиска (в ящике data уже находятся искомые данные), а в ином случае сначала изменяет ссылку текущего элемента на следующий элемент, а ссылку предыдущего элемента - на прежний текущий элемент, и затем вызывает функцию f1 для нового текущего элемента.

Удаление элемента по ключу осуществляется функцией Out и вспомогательными функциями o1, o2, o3. Действия начинаются с поиска Find элемента по ключу и передачи текущего определенного элемента функции o1 для анализа. Функция o1 при пустой ссылке текущего элемента записывает в ящик data неуспешность поиска, а в ином случае передает эту ссылку функции o2 для подготовки удаления элемента из списка. Функция o2 в случае пустой ссылки предыдущего элемента изменяет ссылку в головном указателе pl на ссылку на следующий элемент (и этим ликвидирует текущий элемент списка), а в случае непустой ссылки предыдущего элемента передает функции f3 эту ссылку для удаления текущего элемента списка. Функция f3 записывает в ящик pred предыдущего элемента ссылку на следующий элемент, сохраняя остальные данные предыдущего элемента. Этим также заканчивается удаление текущего элемента.

Упражнения

  1. Разработайте рекурсивную программу на Рефале-2 вычисления числа Фибоначчи с вводимым номером.
  2. Разработайте рекурсивную программу на Рефале-2 сортировки последовательности целых чисел, введенных в один из стеков копилки, при помощи двух других стеков копилки.
  3. Разработайте программу-интерпретатор на Рефале-2 вычисления арифметического выражения с целыми неотрицательными числами, операциями сложения и умножения и скобками, определяющими порядок выполнения операций.
  4. Разработайте программу на Рефале-2 для динамического графа, которая бы добавляла и удаляла вершины и ребра, а также выводила граф на устройство вывода.
< Лекция 4 || Лекция 5: 12 || Лекция 6 >