ОБЪЕКТНО-ОРИЕНТИРОВАННЫЙ ИНТЕРФЕЙС РЕФАЛ-С В СИСТЕМЕ РЕФАЛ-6 "Использование машинных операций является неотъемлемой чертой программирования на языке Рефал". (В.Турчин, 1972) В настоящее время вместо слов "машинная операция" мы говорим "встроенная функция". Важно, что для ее определения используется язык машинного уровня, в данном случае С. Термин "машинная операция" больше подходит для тех функций, которые реальзуются одной машинной командой, но это уже крайний случай. Но даже в этом случае, еще несколько команд уйдет на организацию передачи данных из формата Рефала в формат операндов команды и обратно, поэтому всегда речь будет идти о программировании встроенной функции на определенном языке (С). Правила, обеспечивающие прием и передачу данных и управления между Рефалом и С образуют ИНТРЕФЕЙС Рефал - С. В системе Рефал-6 следует различать основной интерфейс, используемый при программировании обычных встроенных функций и объектно-ориентированный, связанный с програмированием методов объектов, которыми минипулируют встроенные функции и которые представлены в языке Рефал символами-ссылками. Типичным примером объектов являются ящики, строки и т.п. Наличие выделенного ОО-интерфейса позволяет обеспечить абстракцию структур данных, иными словами, независимость собственно встроенных функций (и тем более, Рефал-программ) от конкретного представления объектов. Так, по опреденным правилам, пользователь может определить свое представление для объектов типа "ящик", обеспечить несколько базовых функций доступа, после чего все встроенные функции работы с ящиками распространят свое действие на новые объекты. Общие правила программирования объектов Объект в Рефале-6 занимает по меньшей мере один элемент из пула свободных элементов списковой памяти (звеньев). Его структура несколько отличается от структуры самого звена. Напомним, что списковый элемент содержит информационную часть, включающую поле типа TYPE(p) и поле значения VAL(p), и два списковых указателя NEXT(p) и PREV(p) того же типа, что и p. Для представления объекта тот же элемент памяти, который будем называть головой объекта (указатели на голову, имеющие тип headptr, будем обозначать h, h1, h2,...) используется следующим образом. В поле VAL(h) - счетчик ссылок (2 байта), в поле TYPE(h) - тип объекта в форме: 4*Imode+Cat, где Imode - номер элемента регистрационного массива (массивов) данного типа, Cat - категория объекта: чистое значение (Сat=2) или объект (Cat=3). (Значения Cat=0 и Cat=1 используются иртерпретатором языка сборки.) Два ссылочных поля NEXT и PREV используются произвольным образом для хранения информации, скрытой в объекте. С нею работают только функции доступа объекта. В дальнейшем, говоря об объектах, всесто термина тип, во избежании конфликта с понятием типа в языке С, будем употреблять термин вид (mode). Регистрационный массив видов объектов состоит из следующих частей: PFUNC(Imode) - ссылка на так называемую P-функцию объекта, через которую осуществляется вызов любого метода объектов данного вида; PNAME(Imode) - указатель строковой константы - имя вида. Регистрационный массив расчитан на 64 элемента. Таким образом тип (вид+категория) объекта занимает один байт в поле flag. Второй байт поля flag может использоваться опять же для внутренних данных объекта. Этим байтом, пользуются, например все объекты класса ВЫТЕСНЯЕМЫЕ для размещения признаков изменения и обращения. Классом объектов будем называть группу видов, обладающих общими свойствами. Класс обычно характеризуется набором сообщений, которые "понимают" объекты этих видов. При этом каждый вид может иметь свою программу отклика на сообщение (метод), но для внешнего пользователя они не различимы. Группа методов данного вида задается в форме P-функции. P-функция - это функция языка С, которая принимает: указатель объекта h, код сообщения m и выдает: указатель на функцию языка С (так называемую i-фукнцию), исполняющую данный метод, и некоторый ключ (часто совпадающий с указателем h), предназначенный для передаче выданной i-функции в качестве идентификатора объекта (вместо указателя объекта h). Размер этого ключа (тип key) определяется классом (и возможно, кодом сообщения m), но он должен быть одинаковым для всех видов класса. Рассмотрим для примера класс КАНАЛЫ. В нем есть вид ФАЙЛ, и возможно, некоторые другие виды (КЛАВИАТУРА, ЭКРАНЫ и т.п.). P-функция канала "понимает" сообщения: GETC, PUTC и другие. Для вида ФАЙЛ объект содержит внутри себя указатель файла f (типа FILE*). На сообщение _PUTC (которое есть просто некоторая целочисленная константа) P-функция объектов вида ФАЙЛ (p_fileio) выдает: i-функцию void fputc(int,FILE*) и указатель f. Это сообщение будет "послано" программой, реализующей встроенную функцию работы с каналом, например WRITE!, которая затем будет многократно обращаться к полученной i-функции fputc, передавая ей в качестве второго аргумента указатель f. Более точно, заголовки P-функций в языке С имеют вид: ifunc P_modename(headptr h, integer m, addr ak); где ifunc - тип, имеющий определение: int (*ifunc) (); addr - тип указателя (void*), совместимый с типом любого другого указателя (здесь по смыслу требуется key*). Для некоторых сообщений некоторых классов p-функция может выполнять требуюмую работу непосредственно при ее вызове. В этом случае в качестве i-функции вырабатывается формальная функция itrue, которая при вызове вырабатывает 1 (т.е. TRUE). Такие сообщения будем называть сообщениями непосредственного действия. Если P-функция получает сообщение, которое она не понимает, то вырабатывается стандартная i-функция inull, которая при вызове аварийно прекращает работу системы. При программировании P-функций одних видов можно использовать другие P-функции, которые могут быть общими для класса и определяют "стандартную" реакцию на некоторые сообщения, которая может в некоторых видах данного класса быть переопределена, а в некоторых оставлена без изменения. Такие p-функции являются p-функциями классов, а не видов. Возможно, они не имеют порожденных объектов, а служат лишь для определения p-функций других видов. Будем называть их p-функциями абстрактных видов, в противоположность p-функциям конкретных видов, т.е. видов, имеющих порожденные объекты. Примером p-функции абстрактного вида может служить функция p-channel. Возможно также определение на основе одного конкретного вида другого вида, путем переопределение части его методов или добавления новых. То есть роль абстрактного вида может играть и конкретный вид. P-функции абстрактных видов также могут определяться друг через друга. В целом возникает сложная иерархия классов с присущим ей механизмом наследования (использования одних P-функций при определении других). Заметим, что возможности организации иерархии здесь далеко не исчерпываются строго древовидными структурами. По-видимому, здесь возможностей даже больше, чем при так называемом мультинаследовании, таком, как например в языке Turbo C++. В иерархии классов имеется корень - самый большой класс, включающий все объекты. Это абстрактный класс ОБЪЕКТЫ. Его p-функция p_object понимает единственное сообщение _DONE, посылка которого вызывает прекращение существование объекта. Естественно, этот метод во многих видах будет переопределяться. Стандартный метод DONE не производит никаких действий. Класс ПЕРЕМЕЩАЕМЫЕ Для представления многих видов требуется дополнительная память. Причем часто она должна быть выделена единым связным куском. Кроме того, во избежании измельчения свободной памяти мы потребуем, чтобы такая дополнительная память была перемещаемой, т.е. чтобы в любой момент система могла путем перемещения занятых сегментов памяти объединить всю свободную память в один или небольшое число связных сегментов. Доступ к дополнительной памяти таких объектов производится только через голову, в которой в некоторой форме лежит ее адрес. В самом сегменте хранится адрес головы, который используется для корректировки головы в случае перемещения. Имеется возможность через голову получить адрес сегмента, однако нужно учитывать, что с течением времени этот адрес может меняться. Отсюда следует, что хранить адрес объекта можно только в форме адреса головы, но никак не адреса связанного с ним сегмента. Сегменты могут не только перемещаться внутри доступной памяти, но и "уплывать" в менее доступные виды памяти, например в EMS, на диски и т.п. Поэтому потребуем, чтобы каждый сеанс работы с перемещаемым объектом начинался с посылки ему специального сообщения _START и заканчивался посылкой сообщения _END. Это сообщения непосредственного действия. Сообщение _START обеспечивает "приплытие" сегмента в доступную память и запрет на его неявное перемещение до поступления сообщения _END. Между START и END объект позволяет выполнить с ним остальные операции доступа (посредством посылки сообщений и вызова соответствующих i-функций). Вызов операций START и END обеспечивается макросами START(h) и END(h). При написании i-функций доступ к сегменту осуществляется через следующие макросы: HDGETA(h,a) - поместить в a адрес сегмента объекта h; HDSETA(h,a) - поместить адрес a в голову h в качестве адреса сегмента. Впрочем, можно и явно использовать макрос AD(h), обозначающий место хранения адреса a в голове h. (Хотя это, возможно, затруднит трюки с упаковкой адреса в голове.) Адрес в голове можно изменять только если он изменился в результате изменения длины сегмента. Для изменения длины имеется макрос REALLOC(a,L) где a - переменная с адресом, а L - новая длина. Логический результат показывает, достаточно ли было памяти для размещения нового сегмента. Таким образом полный код для изменения длины объекта h может быть, например, таким: { adr a; START(h) ... HDGETA(h,a) if (NOT REALLOC(a,L)) printf("Not enough memory"); HDSETA(h,a) ... END(h) } Длина сегмента L задается в байтах. Она выражает размер сегмента, предоставляемого пользователю. Для доступа к сегменту следует использовать макросы: SEGLEN(a) - длина сегмента в байтах; SEGINF(a) - указатель начала сегмента (имееющий тип void*, т.е. совместимый с любым другим типом указателя). Если сегмент имеет ненулевую длину, то он реально существует как свой собственный сегмент данного объекта h. Если длина равно 0, то используется один постоянный сегмент-константа с адресом ANULL (ANULL не есть 0 или NULL !). Поэтому для освобождения сегмента можно (и нужно) использовать макрос REALLOC(a,0). При создании головы в нее следует занести адрес ANULL: HDSETA(h,ANULL) Если операцию REALLOC не удалось выполнить по причине отсутствия свободного сегмента нужной длины (и система не смогла такой сегмент высвободить), то в переменную a помещается 0. Поэтому после REALLOC должна стоять проверка, например if(a==0) { rf_error = ERRSTOR; return(FALSE); } Ничто не мешает автору вида объекта открыть его структуру, чтобы использовать все описанные выше макросы вне i-функций. Это плохо единственно тем, что нельзя будет в дальнейшем подменять вид объекта динамически. Класс КОНТЕЙТЕРЫ Этот класс объединяет все объекты, единственное назначение которых - хранить в некоторой форме рефальское выражение. В этом классе имеются объекты разных видов: ящики, вектора, строки, маски. Они различаются множеством допустимых выражений и способом их хранения. Для доступа к хранимому выражению на уровне Рефала имеются следующие встроенные функции: Чтение: == eX Замена: == t.R1 Чтение n-го терма: == tN Замена n-го терма: == t.R1 Номер терма: == sn | -1 Чтение части: == eX Замена части: == t.R1 Сдвиг части: == t.R1 Слияние: == t.R1 Длина содержимого: == sL Для реализации этих функций для доступа к объекту на уровне С имеется набор i-функции, приводимый ниже. Указатели на эти i-функции вырабатываются p-функцией объекта, в ответ на сообщение с соответствующим кодом. При этом вырабатывается также некоторый ключ k, который будет передаваться i-функциям в качестве идентификатора объекта. Для локализации позиции внутри выражения и обеспечения эффективного последовательного просмотра вводится понятие указателя хвоста t. Он может указывать либо на содержимое объекта целиком, либо на его часть, полученную путем отбрасывание нескольких первых элементов (в том числе и всех). Одновременно можно иметь несколько разных указателей на разные хвосты одного объекта (в пределах участка программы между START и END для данного объекта), но если по одному из них произведена операция модификации, то все остальные теряют силу. Содержательно, i-функция может иметь несколько результатов. Но собственно значением в смысле С будет только один из них. Обычно это параметр flag, имеющий тип LOGICAL, или int. Другие результаты помещаются в переменные, ссылки на которые передаются как параметры вызова. Далее следует перечень форматов i-функций в абстрактной форме. inew(k) -> h : создать новый пустой объект на основе головы h (того же вида, что и объект, из которого взята i-функция inew. isetpos(k,t,n) -> t,flag : установить позицию (хвост) со сдвигом на n вправо относительно начала (n >= 0) или на (-n-1) влево относительно конца (0 - начало, -1 - конец, 1 - без первого элемента, -2 - один последний элемент, 2 - без двух первых, -3 - два последних и т.д.). flag=FALSE, если требуется выйти за пределы содержимого объекта, в этом случае t устанавливается в конечную (начальную) позицию. igetn(k,t) -> t',c,flag : если хвост t не пуст, то c - значение первого элемент хвоста, t' - остаток хвоста без первого элемента, flag=TRUE; иначе c не определено, t'=t, flag=FALSE. Результат c - актуальный. Здесь и ниже параметр c обозначает значение терма, представленное в форме С-значения (cvalue). irepn(k,t,c) -> t',flag : если хвост пуст, он наращивается элементом c, иначе первый элемент заменяется на c, t' - остаток хвоста без первого элемента, flag=TRUE; если операцию выполнить не удалось из-за нехватки памяти, то flag = FALSE. Аргумент с - актуальный. igetlen(k,t) -> l : узнать длину хвоста. isetlen(k,t,l) -> t',flag : установить новую длину хвоста l>=0, обрезая лишнее или наращивая хвост с конца, t' обозначает хвост, начинающийся в той же позиции, что и t (однако физически t' может отличаться от t); flag=FALSE, если операцию выполнить не удалось из-за нехватки памяти. ishift(k,t,n) -> flag : отсечь (n<0) или нарастить (n>0) хвост с начала; flag=FALSE, если операцию выполнить не удалось из-за нехватки памяти. Операции isetlen и ishift могут создавать новые места для термов. Они инициализируются значением литеры ' ' (пробел).