Язык РЕФАЛ: сложные конструкции Рефала-2
Спецификации переменных
В том случае, когда нужно ограничить множество значений, которое может принимать переменная (левой части рефал-предложения), в описание переменной вводится спецификация, которая должна находиться между типом переменной и индексом без пробелов. Спецификация может быть определена непосредственно в круглых скобках либо именем спецификатора (для предопределенных имен спецификаторов ). Например, переменная S('+-')z может принимать значения только знаков '+' или '-'. При этом цепочка символов '+-' определяется как спецификатор переменной Sz. В правой части рефал-предложения спецификация не пишется. Между скобками спецификации и спецификатором может быть любое количество пробелов.
Каждый спецификатор в общем случае представляет собой описание некоторого множества термов (допустимые значения переменной). Это описание строится из некоторого набора элементарных множеств, обозначения которых мы будем называть элементами. В качестве элементов могут быть использованы любые символы, а также следующие конечные множества:
- S - множество всех символов;
- B - множество структурных термов;
- W - множество всех термов;
- F - множество символов-меток;
- N - множество символов-чисел;
- R - множество символов-ссылок;
- O - множество символов-литер (знаков символов);
- L - множество прописных букв (латинских и русских);
- D - множество десятичных цифр.
В простом случае используется , которая представляет собой объединение множеств элементов будем называть набор опций, описывающих необязательные возможности.. Например, функция Ident проверяет, является ли выражение аргумента идентификатором (последовательностью больших букв и цифр, начинающейся с буквы), и возвращает символ 'T' при положительном ответе, а символ 'F' при отрицательном ответе.
Ident S(L)1 E(LD)2 = 'T' Ex = 'F'
Функция Ident при интерпретации первого рефал-предложения проверяет, является ли первый символ выражения аргумента буквой (для этого первый выделяемый символ S1 имеет спецификацию L) и являются ли все последующие символы, если они есть (переменная типа E может интерпретировать пустое значение), буквами или цифрами. Если это так, то возвращается символ 'T'. А если это не так, то второе рефал-предложение (всегда выполнимое) возвратит символ 'F', что и требовалось.
В общем случае спецификатор имеет вид:
P1(Q1)... Pn(Qn)P0,
где - произвольные цепочки элементов спецификатора (возможно, пустые). Если P0 - пустая цепочка, то считается P0=W (множеству всех термов). Наличие цепочки элементов спецификатора в круглых скобках означает, что элементы множества этой цепочки удаляются, а цепочка без круглых скобок ведет к добавлению в суммарное множество элементов множества этой цепочки. Поэтому множество спецификатора рассматривается как множество:
.
Так, например, переменная W(('ABC'))1 может принимать значения любых термов, кроме символов литер 'A', 'B', 'C', а переменная S(('A')L('0')D)2 может принимать значения любых символов, кроме буквы 'A' и цифры '0'.
Для спецификатора можно задать имя с тем, чтобы употреблять это имя вместо выражения спецификатора. Это делается описанием имени спецификатора:
<имя спецификатора> S <спецификатор>
Пусть, например, вводятся имена спецификаторов для операций типа сложения и для операций типа умножения:
ADDOP S '+-' MULTOP S '*/'
Теперь имена спецификаторов, ограниченные с обеих сторон символами двоеточие ":", можно употреблять в спецификациях. Например, переменная S:ADDOP:1 может принимать значения только символов операций типа сложения, а переменная S((:ADDOP::MULTOP:)S)2 может принимать значения всех символов, кроме символов арифметических операций.
Рассмотрим еще 1 пример: функция Separate отделяет от выражения аргумента самый большой идентификатор слева и заключает его в структурные скобки, оставляя неизменным оставшуюся часть выражения. Если же такого идентификатора нет, то функция помечает выражение символом '*'. Решение, основанное на функции Ident не годится, так как нужно знать, где заканчивается идентификатор. Однако если использовать отождествление справа налево, то мы получаем следующее простое решение.
Separate R S(L)1 E(LD)2 Ex = (S1 E2) Ex Ex = '*' Ex
Структура рефал-программы и ее модулей
Рефал-программа состоит из одного или нескольких модулей - частей программы, которые компилируются отдельно. Каждый модуль программы представляет собой последовательную запись директив программы и комментариев. Каждый комментарий записывается в отдельной строке и начинается с литеры "*". Перед этим символом могут стоять пробелы в любом количестве, а после - любой текст, который игнорируется при выполнении программы.
Каждая директива находится в отдельной строке и имеет следующий вид:
[<идентификатор>] [<ключевое слово>] [<информация>]
Присутствие всех трех частей директивы необязательно. Между отдельными частями директивы должен быть хотя бы 1 пробел, но если идентификатор директивы отсутствует, то строка директивы начинается с пробела. Так, директива описания функции содержит только ее идентификатор и, возможно, информацию в виде рефал-предложения. Директива рефал-предложени я содержит только информацию. Директива спецификатора имеет идентификатор спецификатора, ключевое слово S и в качестве информации директивы сам спецификатор. Если все 3 компоненты директивы отсутствуют, то это пустая директива, которая используется для улучшения общего вида программы.
Ключевые слова директив приведены в табл. 3.1.
Необязательное имя модуля (программы) дается директивой START, а конец модуля (программы) отмечается директивой END без имени (директива начинается с пробела).
Имена всех функций модуля, которые могут быть использованы в других программах и модулях, должны быть объявлены входами директивой ENTER (без имени). Имена функций других модулей, используемых в программе, должны быть объявлены внешними директивой EXTRN. Благодаря директиве EXTRN программа связывается с внешним миром, так как функции ввода исходных данных и вывода результатов описываются на других языках1Такие первичные функции мы опишем в следующем разделе. (ассемблер, С). Модуль может не содержать директивы EXTRN, если он предназначен для использования в других модулях и программах (в этом случае директива ENTRY должна быть обязательно). Имена входов и внешних функций даются их идентификаторами. Но если в одном модуле используется идентификатор внешней функции, отличный от ее описания в другом модуле, то в скобках дается имя этой внешней функции в модуле ее описания.
Смысл директивы SWAP мы дадим при описании специальных видов памяти, с которой может работать программа.
Приведем пример двухмодульной программы.
M1 START ENTRY COMMUN EXTRN DREAM COMMUN E1 '+' E2 = k/DREAM/ E1. k/DREAM/ E2. END M2 START ENTRY DREAM EXTRN общение(COMMUN) DREAM Sx E1 = Sx k/общение/ E1. END
Здесь функция модуля M1 с внутренним именем COMMUN имеет вызов в модуле M2 с внутренним именем "общение".
Каждое имя, которое употребляется внутри модуля, должно быть описано либо как имя внутренней функции или спецификатора, либо как имя внешней функции или спецификатора в директиве EXTRN.
Упражнения
- Разработайте рефал-функцию, которая отделяет от выражения самый большой идентификатор справа и заключает его в структурные скобки.
- Разработайте спецификатор, который ограничивает множество символов цифрами (кроме 0) и символами-числами.
- Разработайте спецификатор, который ограничивает множество термов любыми термами кроме составных символов.
- Разработайте рефал-функцию, которая реверсирует последовательность структурных и функциональных термов.
- Разработайте двухмодульную программу, один из модулей которой содержит описание функций выделения в стуктурные скобки самого левого идентификатора, переменной, самого левого числа и самой левого символа арифметической операции, а другой модуль производит лексический анализ выражения, определенного аргументом у функции лексического анализа, функция которого описана с помощью функций первого модуля.