Рефал-05

Язык Рефал-05, его отличия от Рефала-5 и общее подмножество

Синтаксис Рефала-05

Синтаксис Рефала-05 является точным подмножеством синтаксиса Рефала-5 (версия PZ Oct 29 2004) — если программма компилируется без ошибок Рефалом-05, то она будет без ошибок скомпилирована и Рефалом-5 тоже.

Обратное неверно — Рефал-05 может выдавать синтаксические ошибки на некоторые корректные программы Рефала-5. Это следующие ошибки:

Так сделано намеренно. Рефал-05 — это не Рефал-5, это другой язык, у него могут быть другие правила.

Объявления и определения

Будем говорить, что функция объявлена, если имя этой функции присутствует в списке $EXTERN.

Будем говорить, что функция определена, если в файле присутствует тело этой функции.

Избыточные внешние объявления

С точки зрения Рефала-05 каждая функция может быть либо встроенной, либо объявленной, либо определённой. Причём, и объявлена, и определена она может только однократно.

Отсюда следует, что

Неиспользуемые функции

Рефал-05 считает синтаксической ошибкой объявления и определения, которые не используются в программе.

Рефал-05 использует раздельную компиляцию, он за раз анализирует только один файл исходного текста, поэтому он не может понять, что в конкретной программе какая-либо функция, помеченная $ENTRY не используется. Более того, наличие таких фактически неиспользуемых функций скорее правило, ведь пользователь может подключать к программе библиотеку (например, LibraryEx), из которой использовать далеко не все функции. Поэтому функции, помеченные $ENTRY, являются используемыми по определению.

Определим формально, что такое используемые функции и используемые определения.

Множество используемых функций — это минимальное множество, определённое следующими правилами:

Множество используемых объявлений — это минимальное множество, определённое следующими правилами:

Соответственно, неиспользуемые функции и объявления — это функции и объявления, которые не входят во множества используемых. На них компилятор Рефала-05 выдаёт синтаксические ошибки.

Правила вида «если функция F используемая, её тело содержит составной символ G…» связаны с семантикой нагруженных идентификаторов — см. раздел про них и функцию Mu ниже.

Могут существовать программы на Рефале-5, в которых есть неиспользуемые по определению выше функции, но они по факту используются, т.к. вызываются косвенно через Mu. Такие программы были бы несовместимы с Рефалом-05 из-за отличий в семантике Mu, даже если бы неиспользуемые функции не были бы синтаксической ошибкой, прерывающей компиляцию. Об этом тоже будет написано в разделе про нагруженные идентификаторы и функцию Mu ниже.

Отличия в семантике Рефала-05 и Рефала-5

Стартовая функция только Go

В Рефале-5 стартовая функция может называться Go или GO, причём вторая имеет приоритет (при наличии в программе обеих функций будет вызываться вторая). В Рефале-05 стартовая функция всегда должна называться Go.

Таким образом, можно написать программу, которая различает Рефал-5 и Рефал-05:

$ENTRY GO {
  = <Prout 'Refal-5'>
}

$ENTRY Go {
  = <Prout 'Refal-05'>
}

Нагруженные идентификаторы и функция Mu

Функция Mu в Рефале-5 и Рефале-05: общее

В языке Рефал-5 (и в Рефале-05 тоже) есть функция Mu, осуществляющая косвенный вызов — имея идентификатор, совпадающий с именем функции, вызвать её:

Hello {
  = <Prout 'Hello!'>
}

$ENTRY Go {
  /* пусто */
    = <Mu Hello>                       /* напечатает «Hello!» */
      <Prout <Mu Add 2 3>>             /* напечатает «5» */
      <Prout <Mu 72 101 108 108 111>>  /* напечатает «Hello» */
      <Mu Bye>                         /* напечатает «Bye» */
}

Bye {
  = <Prout 'Bye!'>
}

В первом и последнем вызовах функции Go вызываются функции, определённые в том же файле. Во втором и третьем — вызовы встроенных функций.

Её формат условно можно описать так (обозначения будут описаны в разделе про встроенные функции):

<Mu s.WORD e.Arg> ≈≈ <s.WORD e.Arg>

Обозначение условное, т.к. запись <s.WORD …> синтаксически некорректная в Рефале-5 и в Рефале-05.

С функцией Mu всё просто и понятно, пока программа состоит из одного файла: если идентификатор является именем функции, определённой пользователем или встроенной функции — вызывается соответствующая функция, иначе — ошибка.

В программах из нескольких файлов уже могут появиться одноимённые функции, т.к. в файле можно определить функцию без пометки $ENTRY с любым именем, не совпадающем с именем встроенной функции — конфликта имён не будет.

И для таких программ становится неочевидно, какую из одноимённых функций вызовет Mu, если мы укажем её имя. Рассмотрим пример:

Файл main.ref:

*$FROM lib
$EXTERN Greeting;

$ENTRY Go {
  = <Greeting Hello 'Mazdaywik'> <Greeting Bye 'Mazdaywik>
}

Hello {
  = 'Hello'
}

Bye {
  = 'Good bye'
}

Файл lib.ref:

$ENTRY Greeting {
  s.Greeting e.Name
    = <Prout <Mu s.Greeting> ', ' e.Name '!'>
}

Hello {
  = 'Hi'
}

Bye {
  = 'Bye'
}

В обоих исходных файлах есть и функция Hello, и функция Bye. Спрашивается, какую вызывать?

И здесь поведение Рефала-5 и Рефала-05 расходится.

Особенности функции Mu в Рефале-5

Рассмотрим вызов

 <Mu s.Name e.Arg>

Функция с именем s.Name ищется в три этапа:

  1. Все функции того файла, где записан вызов функции Mu,
  2. все функции программы с пометками $ENTRY,
  3. все встроенные функции.

Первая найденная функция вызывается. Если функцию найти не удалось, то программа завершается аварийно.

Второй этап — поиск среди функций с пометками $ENTRY — официально незадокументирован в учебнике Турчина, однако, описан в приложении к книге.

Поиск среди функций с пометками $ENTRY позволяет записывать библиотеки функций высшего порядка, такие как функции Map, Reduce и MapAccum библиотеки LibraryEx. Чтобы функция, например, Map, могла вызвать функцию пользователя, последняя должна быть написана с пометкой $ENTRY. Без поиска по $ENTRY-функциям библиотеки функций высшего порядка в Рефале-5 были бы невозможны.

В примере с Greeting программа на Рефале-5 напечатает

Hi, Mazdaywik!
Bye, Mazdaywik!

т.к. поиск имён Hi и Bye будет осуществляться среди функций файла lib.ref, в котором записан вызов <Mu s.Greeting>.

Можно условно считать, что компилятор Рефала-5 в каждый файл неявно добавляет экземпляр функции Mu без пометки $ENTRY следующего вида

Mu {
  …
  ‹имя-функции› e.Arg = <‹имя-функции› e.Arg>;
  …
  s.Unknown e.Arg = ‹поиск среди $ENTRY и встроенных›;
}

Где в качестве ‹имени-функции› перечисляются имена всех функций, определённых в текущем файле (включая эту неявно добавленную Mu). Последнее предложение перехватывает случай, когда функции с искомым именем среди функций текущего файла не нашлось и нужно искать среди функций с пометками $ENTRY и встроенных.

В файл lib.ref, можно считать, что неявным образом добавляется следующая функция Mu:

$ENTRY Greeting {
  s.Greeting e.Name
    = <Prout <Mu s.Greeting> ', ' e.Name '!'>
}

Hello {
  = 'Hi'
}

Bye {
  = 'Bye'
}

Mu {
  Greeting e.Arg = <Greeting e.Arg>;
  Hello e.Arg = <Hello e.Arg>;
  Bye e.Arg = <Bye e.Arg>;
  Mu e.Arg = <Mu e.Arg>;

  s.Unknown e.Arg = ‹поиск среди $ENTRY и встроенных›;
}

В реальности компилятор Рефала-5 действительно в каждый скомпилированный модуль неявно добавляет функцию Mu, тело которой состоит из одной инструкции RASL’а (байткода Рефала-5) BUILT_IN1 с двумя аргументами: указателем на таблицу функций текущего файла и номером встроенной функции Mu — 1 (эти номера возвращает ListOfBuiltin).

Особенности функции Mu в Рефале-05 и нагруженные идентификаторы

Рассмотрим вызов

<Mu s.Name e.Arg>

Рефал-05 вызовет ту функцию s.Name, которая видима в файле, где был записан этот идентификатор. Под видимой функцией подразумевается функция, вызов которой можно дописать в любую функцию данного исходного файла: функция, которая определена в нём, объявлена в нём или является встроенной. Если в файле, где был записан идентификатор, передаваемый в Mu, не было видимой функции с таким же именем, то вызов Mu остановит программу с ошибкой невозможности отождествления.

Идентификаторы в Рефале-05 могут быть нагруженными и ненагруженными.

Если в файле, где был записан идентификатор ‹имя› функция с именем ‹имя› видима, то такой идентификатор называется нагруженным. Такой идентификатор в поле зрения несёт «нагрузку» — ссылку на соответствующую функцию. Если же в файле функция с таким именем отсутствовала, то идентификатор ненагруженный, никакой нагрузки он не несёт.

Таким образом, функция Mu для нагруженных идентификаторов просто вызывает соответствующие функции, для ненагруженных — аварийно завершает программу.

В примере с Greeting выше программа напечатает

Hello, Mazdaywik!
Good bye, Mazdaywik!

т.к. идентификаторы Hello и Bye созданы в файле main.ref и они несут нагрузку из ссылок на одноимённые функции из этого файла.

Нагрузка идентификаторов влияет только на поведение функции Mu, другими средствами Рефала-05 её заметить невозможно. В частности, при сравнении значений на равенство кратными вхождениями переменных образца нагрузка никак не учитывается, учитываются только имена; функции вывода нагрузку не печатают; функция Type нагрузку также не учитывает.

Если переменная в левой части встречается один раз, в правой части — несколько и значение переменной содержит идентификаторы, то нагрузка копируется.

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

Файл fileA.ref:

$ENTRY GetX { = X }

X { = 'A' }

Файл fileB.ref:

$EXTERN GetX;

X { = 'B' }

$ENTRY Go {
  /* пусто */
    = <Test1 <GetX> X>
      <Test1 X <GetX>>
      <Test2 <GetX> X>
      <Test2 X <GetX>>
}

Test1 {
  s.X s.X = <Prout <Mu s.X> <Mu s.X> <Mu s.X>>
}

Test2 {
  t.X t.X = <Prout <Mu t.X> <Mu t.X> <Mu t.X>>
}

На момент написания этих строк данная программа печатает

AAA
BBB
BAA
ABB

Видно, что поведение s- и t-переменных различается. В других версиях Рефала-05 вывод может оказаться иным.

Если же эту программу откомпилировать Рефалом-5, то увидим

BBB
BBB
BBB
BBB

В Рефале-5 нет понятия нагруженных идентификаторов. Функция Mu для имени X всегда будет вызывать функцию X из файла fileB.ref, т.к. все шесть её вызовов записаны в этом файле.

Нагруженные идентификаторы очевидным образом позволяют использовать библиотеки функций высшего порядка, например, LibraryEx. Для того, чтобы передать, например, в Map некоторую функцию, достаточно написать её в текущем файле и передать функции Map идентификатор с её именем — идентификатор будет нагруженным и функция Map его вызовет.

В предыдущем разделе определялось понятие используемых функций и используемых объявлений — в определениях говорилось:

Теперь должно быть понятно, откуда эта оговорка про «тело содержит составной символ G» — соответствующий символ становится нагруженным идентификатором и несёт в себе ссылку на соответствующую определённую или объявленную функцию.

Заметим также, что если в некотором исходном файле есть имя функции F в списке $EXTERN, функция в файле нигде не вызывается, но имеется составной символ F в теле одной из используемых функций, но при этом нигде в программе нет функции F с пометкой $ENTRY, то такая программа не скомпилируется. Компоновщик языка Си выдаст ошибку. В скомпилированном файле ссылка на эту внешнюю функцию будет, т.к. составной символ F будет нагруженным и будет на эту функцию ссылаться. В Рефале-5 в этом случае ошибки не будет — объявления функций, которые не вызываются, ошибкой не являются и молча игнорируются.

Знаковая «полудлинная» арифметика

В отличие от Рефала-5, в Рефале-05 арифметика «полудлинная» — аргументы арифметических операций не могут быть длинными числами (состоящими из нескольких макроцифр), но результат (у Add, Mul, Sub) может. Эти три функции в случае переполнения возвращают две макроцифры.

Формат арифметических функций может быть описан так (см. про обозначения далее):

e.ArithmArg ::=
    (s.Sign? s.NUMBER) s.Sign? s.NUMBER
  | s.Sign? s.NUMBER s.Sign? s.NUMBER
s.Sign ::= '+' | '-'

<Add e.ArithmArg> == '-'? 1? s.NUMBER
<Sub e.ArithmArg> == '-'? 1? s.NUMBER
<Mul e.ArithmArg> == '-'? s.NUMBER? s.NUMBER
<Div e.ArithmArg> == '-'? s.NUMBER
<Mod e.ArithmArg> == '-'? s.NUMBER
<Divmod e.ArithmArg> == ('-'? s.NUMBER) '-'? s.NUMBER
<Compare e.ArithmArg> == '-' | '0' | '+'

Аргументы арифметических функций — числа с необязательным предшествующим знаком '+' или '-', первый аргумент функции может быть заключён в скобки. Результат — одна или две макроцифры, которым может предшествовать знак '-'. У функций Add и Sub предпоследняя макроцифра, если есть, всегда имеет значение 1, у функции Mul может иметь почти любое значение. У функций Div и Mod в результате всегда только одна макроцифра по очевидным причинам.

Семантика функции Divmod может быть условно описана как

Divmod {
  e.Arg = (<Div e.Arg>) <Mod e.Arg>
}

Не все встроенные функции поддерживаются

Раздел в процессе написания

Библиотека встроенных функций

Внимание! В этом разделе могут быть перечислены не все функции.

Далее мы перечислим встроенные функции в том порядке и с теми номерами, в каком их перечисляет встроенная функция ListOfBuiltin.

Нотация для записи типов функций

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

Тип функции:

<ИмяФункции тип-аргумента>
  == тип-результата

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

Именованный тип:

переменная-типа ::= тип-выражения1 | тип-выражения2 | … | тип-выраженияN

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

Несколько именованных типов могут иметь одинаковое описание:

перем1, перем2, перем3 ::= тип-выражения

Тип выражения записывается как образцовое выражение, где после термов (включая переменные) могут использоваться квантификаторы * (0 и более раз), + (1 и более раз) и ? (0 или 1 раз).

Переменные s.CHAR, s.NUMBER и s.FUNCTION описывают, соответственно, произвольную литеру, число и функцию.

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

e.AnyExpr ::= t.AnyTerm*
t.AnyTerm ::= s.AnySymbol | (e.AnyExpr)
s.AnySymbol ::= s.CHAR | s.NUMBER | s.FUNCTION

Входная точка лексического анализатора (длинный список лексем сокращён):

<R05-LexScan-File e.SourceName>
  == e.Tokens
e.Tokens ::= (s.TokType t.SrcPos e.Info)
t.SrcPos ::= (s.Row s.Col)

s.TokType e.Info ::=
    TkChar s.CHAR
  | TkClose s.Bracket
  | TkCloseBlock
    …
  | TkError e.Message
  | TkExtern
  | TkName e.Name
  | TkNative (e.SourceName s.LineNo) (s.CHAR*)*
  | TkNumber s.NUMBER
  | TkOpen s.Bracket
  | TkOpenBlock
  | TkReplace
  | TkSemicolon
  | TkUnexpected e.BadCharacters
  | TkVariable s.Mode e.Index

s.Bracket ::= Bracket | CallBracket
e.Message, e.Name, e.SourceName, e.BadCharacters, e.Index ::= s.CHAR+
s.LineNo ::= s.NUMBER
s.Mode ::= 's' | 't' | 'e'

Входная точка синтаксического анализатора:

<R05-Parse-File e.SourceFile>
  == Success e.Tree
  == Fails e.Errors

e.Errors ::= ((s.Row s.Col) e.Message)*

Часть описания дерева:

e.Tree ::= t.TreeItem*
t.TreeItem ::=
    (Extern e.Name)
  | (Function s.Scope (e.Name) e.Body)
  | (Native e.Native)

e.Name ::= s.CHAR+
s.Scope ::= Entry | Local
e.Body ::= Sentences t.Sentence* | Native e.Native
e.Native ::= (e.SourceName s.Line) (s.CHAR*)*
s.Line ::= s.NUMBER
e.SourceName ::= s.CHAR+

1. Mu

<Mu s.FUNCTION e.AnyExpr> == e.AnyExpr

Семантика: функция имеет следующую реализацию на Рефале

$ENTRY Mu {
  s.Func e.Arg = <s.Func e.Arg>;
}

Функция нужна для совместимости с Рефалом-5. Зачем она нужна, мы подробно расскажем в следующем разделе.

Совместимость с Рефалом-5 См. раздел «Совместимость с Рефалом-5 и общее подмножество».

2. Add

<Add e.ArithmArg> == '-'? 1? s.NUMBER

e.ArithmArg ::=
    (s.Sign? s.NUMBER) s.Sign? s.NUMBER
  | s.Sign? s.NUMBER s.Sign? s.NUMBER
s.Sign ::= '+' | '-'

Семантика. Вычисляет сумму двух чисел. Если среди аргументов есть отрицательные слагаемые, то результат может быть отрицательным. В случае арифметического переполнения к результату добавляется макроцифра 1, вторая макроцифра равна сумме аргументов по модулю 2³².

Совместимость с Рефалом-5. В Рефале-5 поддерживаются длинная арифметика, аргументы могут содержать несколько макроцифр. Рефал-05 допускает наличие в аргументах только одной макроцифры.

3. Arg

<Arg s.ArgNo> == e.Argument

s.ArgNo ::= s.NUMBER
e.Argument ::= s.CHAR*

Семантика: возвращает аргумент командной строки с указанным номером. Нулевой аргумент — имя вызываемой программы. Если запрашиваемый аргумент не существует — фактическое их число меньше, чем s.ArgNo, возвращается пустая строка.

Совместимость с Рефалом-5. Интерпретатор Рефала-5 пропускает все аргументы, начинающиеся на знак минус, поэтому в переносимых программах не рекомендуется использовать ключи командной строки, начинающиеся на минус.

4. Br

<Br e.Key '=' e.Value> == пусто

e.Key, e.Value ::= e.AnyExpr

Семантика: Сохраняет в копилке соответствующую пару «ключ-значение».

Совместимость с Рефалом-5: полная.

5. Card

<Card> == s.CHAR* 0?

Семантика. Считывает перфокарту строчку со стандартного ввода. Если встречен символ конца файла, в конец прочитанной строки добавляется число 0.

Совместимость с Рефалом-5. Встроенные функции Card и Get некорректно считывают строки, содержащие внутри себя символ с кодом нуля \x00, поэтому переносимые программы не должны читать двоичные файлы.

6. Chr

<Chr e.AnyExpr> == e.AnyExprChr

e.AnyExprChr ::= t.AnyTermChr*
t.AnyTermChr ::= s.CHAR | s.NUMBER | (e.AnyExprChr)

Семантика: функция заменяет в своём аргументе все числа на символы-литеры с соответствующим ASCII-кодом (по модулю 256).

Совместимость с Рефалом-5: полностью совместима.

8. Dg

<Dg e.Key> == e.Value

Семантика: Извлекает из копилки последнее сохранённое значение с заданным ключом. Если с заданным ключом ничего не сохранялось, возвращается пустое выражение.

Совместимость с Рефалом-5: полная. Включая следующую ошибку:

$ENTRY Go {
  = <Br 'A=B=C'> <Prout <Dg 'A=B'>>
}

И Рефал-5, и Рефал-05 распечатают C.

10. Div

<Div e.ArithmArg> == '-'? s.NUMBER

Семантика. Выполняет целочисленное деление. Если делитель равен нулю, программа аварийно останавливается выдачей дампа поля зрения и ошибки «деление на ноль». Если аргументы разных знаков, результат будет отрицательным.

Совместимость с Рефалом-5: см. Add.

11. Divmod

<Divmod e.ArithmArg> == ('-'? s.NUMBER) '-'? s.NUMBER

Семантика. Вычисляет частное и остаток от деления, частное заключает в скобки. Если делитель равен нулю, программа аварийно останавливается выдачей дампа поля зрения и ошибки «деление на ноль».

Если аргументы разных знаков, частное будет отрицательным. Знак остатка совпадает со знаком делимого.

Совместимость с Рефалом-5: см. Add.

12. Explode

<Explode s.FUNCTION> == s.CHAR+

Семантика. Для символа-функции возвращает её имя как последовательность литер. Пример:

<Explode R05-Parse-File> → 'R05-Parse-File'
<Explode findfile_AnalyzeFile-ByFolders> → 'findfile_AnalyzeFile-ByFolders'

Совместимость с Рефалом-5: полная.

13. First

<First s.Len e.Items> == (e.Prefix) e.Suffix

e.Items : e.Prefix e.Suffix
|e.Prefix| == s.Len || { |e.Prefix| < s.Len && |e.Suffix| == 0 }

Семантика. Отделяет от строки префикс указанной длины. Если строка короче, то она вся возвращается как префикс.

Совместимость с Рефалом-5: полностью совместима.

14. Get

<Get s.FileNo> == s.CHAR* 0?

s.FileNo ::= s.NUMBER

Семантика. Функция читает из файла с заданным номером. Номер файла вычисляется как остаток от деления s.FileNo на 40:

file_no = s.FileNo % 40

Если величина file_no равна нулю, то читается стандартный ввод, т.е. вызов <Get 0> (или <Get 40>, <Get 80> и т.д.) будет эквивалентен вызову <Card>.

Если файл с указанным номером не был открыт при помощи функции Open, то открывается файл с именем REFAL<file_no>.DAT в режиме «для чтения», где вместо <file_no> означает запись file_no в десятичном виде. Например, если файл с номером 33 не был открыт, то вызов <Get 143> откроет для чтения файл REFAL33.DAT.

Точно также, как и функция Card, при достижении конца файла функция загружает в поле зрения число 0.

Совместимость с Рефалом-5. См. Card.

15. Implode

<Implode e.ValidPrefix e.Suffix> == s.FUNCTION e.Suffix
<Implode e.Suffix> == 0 e.Suffix

e.ValidPrefix ::= s.Lettern { s.Letter | s.Digit | '_' | '-' | '$' }
s.Letter ::= 'A' | … | 'Z' | 'a' | … | 'z'
s.Digit ::= '0' | … | '9'

Семантика. У аргумента выделяется префикс максимальной длины, являющийся записью корректного имени функции в Рефале-5. Если этот префикс не пустой, функция Implode возвращает функцию, имя которой записано в префиксе и суффикс. Если же префикс пустой, то функция возвращает число 0 и суффикс (фактически, совпадающий со всем аргументом).

Особенность Рефала-05 — если указано имя встроенной функции, то возвращается встроенная функция (нагруженный идентификатор), если другое имя — пустая функция (ненагруженный идентификатор):

<Mu <Implode 'Add' 12 34>>  →  46

Совместимость с Рефалом-5. Полная. В частности, и Рефал-5, и Рефал-05 допускают в идентификаторе, создаваемом Implode, знак $, однако, в Рефале-5 эта особенность недокументированная.

19. Mod

<Mod e.ArithmArg> == '-'? s.NUMBER

Семантика: вычисляет остаток от деления. При делении на нуль — см. Div.

Знак остатка совпадает со знаком делимого.

Совместимость с Рефалом-5: см. Add.

20. Mul

<Mul e.ArithmArg> == '-'? s.Number? s.NUMBER

Семантика: вычисляет произведение двух чисел. Если ответ превышает 2³²−1, то ответ представляется в виде двух макроцифр X и Y, что следует трактовать как X × 2³² + Y.

Совместимость с Рефалом-5: см. Add.

21. Numb

<Numb s.Sign? s.CHAR*> == '-'? s.NUMBER

Семантика. Если аргумент начинается с последовательности цифр, то возвращается число по модулю 232. В противном случае возвращается 0.

Совместимость с Рефалом. В Рефале-5 поддерживаются длинная арифметика, поэтому результат может содержать несколько макроцифр.

22. Open

<Open s.Mode s.FileNo e.FileName?> == пусто

s.Mode ::=
    'r' | 'w' | 'a'
  |  r  |  w  |  a
  |  rb |  wb |  ab
e.FileName ::= s.CHAR+

Семантика. Функция открывает файл с заданным номером в заданном режиме. Номер файла вычисляется по формуле

file_no = s.FileNo % 40

Если файл с номером file_no был открыт ранее, он закрывается. Если имя файла не задано, то открывается файл с именем REFAL<file_no>.DAT, где <file_no> — десятичная запись file_no.

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

Режимы:

Файл с номером 39 используется в LibraryEx функциями LoadFile и SaveFile.

Совместимость с Рефалом-5. Функция полностью совместима.

23. Ord

<Ord e.AnyExpr> ::= AnyExprOrd

e.AnyExprOrd ::= t.AnyTermOrd*
t.AnyTermord ::= s.NUMBER | s.FUNCTION | (e.AnyExprOrd)

Семантика: заменяет в своём аргументе все литеры на их ASCII-коды.

Совместимость с Рефалом-5: полностью совместима.

25. Prout

<Prout e.AnyExpr> == пусто

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

Совместимость с Рефалом-5: полностью совместима.

27. Putout

<Putout s.FileNo e.Expr> == пусто

Семантика. Распечатывает объектное выражение в файл с указанным номером. Преобразование в цепочку литер осуществляет точно также, как и Prout. Номер файла определяется по формуле:

file_no = s.FileNo % 40

Если файл с номером file_no не открыт, то открывается файл с именем REFAL<file_no>.DAT, где <file_no> — десятичная запись file_no в режиме (пере)записи (см. 'w' у функции Open).

Совместимость с Рефалом-5: полностью совместима.

30. Sub

<Sub e.ArithmArg> == '-'? 1? s.NUMBER

Семантика: вычисляет разность двух чисел. Если первое число меньше второго, возвращается литера '-' и значением модуля их разности. Иначе просто возвращается их разность. В случае, если аргументы разных знаков, возможно арифметическое переполнение, в этом случае к ответу добавляется макроцифра 1, вторая макроцифра есть сумма абсолютных значений аргументов по модулю 2³².

Совместимость с Рефалом-5: см. Add.

31. Symb

<Symb e.Sign s.NUMBER> == e.Sign s.CHAR+
e.Sign ::= '+' | '-' | пусто

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

Совместимость с Рефалом-5. Рефал-5 поддерживает длинную арифметику, поэтому в аргументе может быть указано несколько чисел-макроцифр, таким образом, функция Symb Рефала-05 обрабатывает помножество области определения функции Рефала-5.

33. Type

<Type e.AnyExpr> == s.Type s.SubType e.AnyExpr

s.Type s.SubType ::=
    'Lu' — uppercase latin letter
  | 'Ll' — lowercase latin letter
  | 'D0' — decimal digit
  | 'Wi' — identifier (function)
  | 'N0' — number
  | 'Pu' — isprint() && isupper()
  | 'Pl' — isprint() && ! isupper()
  | 'Ou' — other && isupper()
  | 'Ol' — other && ! isupper()
  | 'B0' — brackets
  | '*0' — empty expression

Семантика: возвращает тип первого терма аргумента. Если аргумент пустой, возвращает '*0'. Остальные типы и подтипы:

Работа функции зависит от установленной локали (которая по умолчанию вроде "C"), если локаль изменена (конфигурацией операционной системы или внешней функцией на Си), то функция будет работать иначе. Лексический анализатор полагается на эту функцию.

Совместимость с Рефалом-5: полная.

51. GetEnv

<GetEnv e.EnvName> == e.EnvValue
e.EnvName, e.EnvValue ::= s.CHAR*

Семантика: возвращает значение переменной среды с заданным именем. Если переменная среды не установлена, возвращает пустую строку.

Совместимость с Рефалом-5: полная.

52. System

<System e.Command> == e.RetCode
e.Command ::= s.CHAR*
e.RetCode ::= '-'? s.NUMBER

Семантика. Выполняет команду e.Command при помощи функции system() языка Си.

На POSIX (если установлен ключ -DR05_POSIX компилятора Си), если запущенный процесс успешно завершился, возвращает код его возврата, иначе возвращает '-' 1. На Windows (т.е. когда не установлен -DR05_POSIX) возвращает то, что вернула system() как есть.

Отрицательный код возврата представляется как число с предшествующей литерой '-'.

Совместимость с Рефалом-5: полная.

53. Exit

<Exit e.RetCode>
e.RetCode ::= '-'? s.NUMBER

Семантика. Завершает программу с заданным кодом возврата. Отрицательное значение записывается как литера '-' с последующим числом.

Совместимость с Рефалом-5: полная.

54. Close

<Close s.FileNo> == пусто

Семантика. Закрывает открытый файл с номером s.FileNo % 40. Если файл с этим номером не был открыт, функция ничего не делает.

Совместимость с Рефалом-5: полная.

55. ExistFile

<ExistFile e.FileName> == True | False
e.FileName ::= s.CHAR*

Семантика. Функция пытается открыть файл с указанным именем для чтения при помощи fopen(). Если удаётся — закрывает открытый файл и возвращает True, в противном случае возвращает False. Функции True и False не являются встроенными функциями, это значит, что их нужно явно подключать при помощи $EXTERN. Так сделано из соображений простоты и переносимости.

Совместимость с Рефалом-5. Рефал-5 действует честнее — использует средства операционной системы, чтобы понять, существует файл или нет. А это значит, что если файл существует, но недоступен для чтения, функции ExistFile Рефала-5 и Рефала-05 увидят его по-разному.

58. Implode_Ext

<Implode_Ext s.CHAR*> == s.FUNCTION

Семантика. Строит составной символ из литер в аргументе. Точно также, как и в случае Implode, если составной символ образует имя встроенной функции, составной символ оказывается «нагруженным» указателем на эту функцию. Если не образует, то вызов такого составного символа при помощи Mu приведёт к ошибке отождествления.

В Рефале-05 символы "%", "*", "+", "-", "/" и "?" являются именами соответствующих встроенных функций (синонимы для Mod, Mul, Add, Sub, Div и Residue соответственно).

Совместимость с Рефалом-5. У Рефала-5 есть недокументированное расширение — фактический формат имеет вид:

<Implode_Ext s.CHAR* e.ANY> == s.FUNCTION

где e.ANY не начинается с литеры. Рефал-05 такое расширение не поддерживает.

В остальном совместимость полная.

60. TimeElapsed

<TimeElapsed 0?> == s.CHAR+

Семантика. Функция возвращает число секунд, прошедших с момента предыдущего вызова <TimeElapsed 0>, либо с начала программы, если <TimeElapsed> ни разу не вызывалась. Число секунд возвращается в виде десятичной записи дробного числа вида '12.345'.

Совместимость с Рефалом-5. Полная.

61. Compare

<Compare e.ArithmArg> == '-' | '0' | '+'

Семантика. Возвращает '-', если первое число меньше второго, '0' — если они равны и '+', если первое больше второго. Иначе говоря, возвращает знак разности этих двух чисел.

Совместимость с Рефалом-5. Рефал-5 поддерживает длинную арифметику, в том числе и для функции Compare (см. Add).

67. ListOfBuiltin

<ListOfBuiltin> == (s.FuncNo s.Name s.BuiltinType)+

s.FuncNo ::= s.NUMBER
s.Name ::= s.FUNCTION
s.BuiltinType ::= special | regular

Семантика. Выводит список встроенных функций в указанном формате. Величины s.FuncNo и s.BuiltinType в данной реализации смысла не имеют, добавлены для совместимости с Рефалом-5. Значения s.FuncNo и s.BuiltinType текущая реализация возвращает те же, что и Рефал-5 версии PZ Oct 29 2004.

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

Совместимость с Рефалом-5. Эта функция в Рефале-5 перечисляет гораздо больше имён. Значения s.FuncNo и s.BuiltinType относятся ко внутренней реализации интерпретатора Рефала-5.

Рефал-05 и Рефал-5: общее подмножество

Внимание! В этом разделе сведения могут быть неточными, они позже будут уточнены.

Рефал-05 проектировался как язык, имеющий общее подмножество с Рефалом-5, притом это подмножество должно быть удобным для программирования (сам компилятор должен быть написан на нём). И действительно, при наложении небольших ограничений на стиль кодирования можно писать программы, которые одинаково работают на обоих диалектах.

Далее под фразой «общее подмножество» мы будем подразумевать общее подмножество Рефала-5 и Рефала-05.

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

Итак, как надо писать на Рефале-05, чтобы полученная программа также работала на Рефале-5:

И наоборот, как нужно писать программы на Рефале-5, чтобы они работали в Рефале-05:

Вообще, при программировании на Рефале-05 рекомендуется придерживаться общего подмножества. По двум причинам:

А вообще — пустые функции не нужны

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

Рефал — функциональный язык. Рефал-05 и Рефал-5 — функциональные языки с ограниченной поддержкой функций высшего порядка (в Рефале-5 она более ограничена). Поддержка высшего порядка есть и в общем подмножестве, тонкостям которого посвящён следующий подраздел.

А пустые функции — костыль, упрощающий реализацию компилятора и рантайма.

Поэтому хорошей практикой при программировании и на Рефале-05, и на общем подмножестве (можно и на обычном Рефале-5 при желании) является использование непустых функций для символических имён.

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

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

Поэтому если действие «по умолчанию» найти можно, то его обработку можно поместить не в функцию, которая выбирает операцию в зависимости от символа, а в сам символ — определив его непустой функцией. А потом его можно вызвать при помощи функции Mu (но есть много тонких нюансов — см. следующий подраздел).

Например, пусть у нас есть синтаксическое дерево такого вида

t.Expr ::=
    (ExNumber s.Number)
  | (ExVariable e.Name)
  | (ExAdd t.Left t.Right)
  | (ExSub t.Left t.Right)
  | (ExMul t.Left t.Right)
  | (ExDiv t.Left t.Rigth)
  | (ExMinus t.Expr)

t.Left, t.Right ::= t.Expr

И есть функция вывода этого дерева в виде арифметического выражения:

StrFromExpr {
  (ExNumber s.Number) = <Symb s.Number>;
  (ExVariable e.Name) = e.Name;
  (ExAdd t.Left t.Right) =
     '(' <StrFromExpr t.Left> '+' <StrFromExpr t.Rigth ')';
  ...
}

Если выбрать действием по умолчанию печать, то имена узлов дерева можно определить как функции:

$ENTRY ExNumber {
  s.Number = <Symb s.Number>;
}

$ENTRY ExVariable {
  e.Name = e.Name;
}

$ENTRY ExAdd {
  (s.Left e.LeftVal) (s.Right e.RightVal) =
    '(' <Mu s.Left e.LeftVal> '+' <Mu s.Right e.RightVal> ')';
}

...

Теперь функции с символическими именами не пустые, а уже имеют некоторе осмысленное тело.

В самом компиляторе Рефала-05 токены, формируемые лексическим анализатором, умеют печатать сами себя — для любого токена вида

(s.TokType t.SrcPos e.Info)

можно вызвать

<Mu s.TokType e.Info>

и получить текстовую строку. Аналогично ведёт себя синтаксическое дерево — любой его узел при вызове печатает себя в виде кода на Рефале-05. Внутри кодогенератора для команд промежуточного языка определены функции, формирующие код на Си этой конструкции.

Лирическое отступление: ООП в Рефале-05

Вообще, это подход можно развить до идиоматической реализации ООП (без наследования). Каждый объект — скобочный терм, начинающийся с символического имени. Функция символического имени является виртуальной таблицей — может обрабатывать различные сообщения. Методы эти сообщения посылают.

Выглядит это так. Фигуры «Прямоугольник» (ORect) и «Круг» (OCircle) могут распечатать себя в текстовом виде (метод MPrint), сдвинуться (метод MMove) и ответить, содержат ли они точку с заданными координатами (метод MHasPoint). Точка (OPoint) тоже может себя печатать и двигаться:

ORect {
  (ORect t.LeftTop t.RightBottom) MPrint =
    'Rectangle [' <MPrint t.LeftTop> '-' <MPrint t.RightBottom> ']';

  (ORect t.LeftTop t.RightBottm) MMove t.Offset =
    (ORect <MMove t.LeftTop t.Offset> <MMove t.RightBootom t.Offset>);

  (ORect (OPoint s.Left s.Top) (OPoint s.Right s.Bottom))
  MHasPoint (OPoint s.X s.Y)
    <And <InRange s.Left s.X s.Right> <InRange s.Bottom s.Y s.Top>>;
}

OCircle {
  (OCircle t.Center s.Radius) MPrint =
    'Circle [center: ' <MPrint t.Center> ', radius: ' <Symb s.Radius> ']';

  (OCircle t.Center s.Radius) MMove t.Offset =
    (OCircle <MMove t.Center> s.Radius);

  (OCircle (OPoint s.CX s.CY) s.Radius) MHasPoint (OPoint s.X s.Y) =
    <InRange
      0
      <Add <Square <Dist s.CX s.X>> <Square <Dist s.CY s.Y>>>
      <Square s.Radius>
    >;
}

OPoint {
  (OPoint s.X s.Y) MPrint = '(' <Symb s.X> ', ' <Symb s.Y> ')';

  (OPoint s.X s.Y) MMove (OPoint s.dX s.dY) =
    (OPoint <Add s.X s.dX> <Add s.Y s.dY>);
}

MPrint {
  t.Object = <Send t.Object MPrint>;
}

MMove {
  t.Object t.Offset = <Send t.Object MMove t.Offset>;
}

MHasPoint {
  t.Object t.Point = <Send t.Object MHasPoint t.Offset>;
}

Send {
  (s.VTable e.Data) s.Method e.Args =
    <Mu s.VTable (s.VTable e.Data) s.Method e.Args>;
}

(Функции InRange, Dist и Square имеют очевидную, но громоздкую реализацию, которая для краткости не приведена.)

И кто теперь скажет, что Рефал-05 не объектно-ориентированный язык 😉?

Косвенный вызов в Рефале-5 и Рефале-05, разная семантика Mu

Внимание! Этот раздел актуален за исключением того, что теперь писать <s.X …> нельзя. Позже он будет переписан в терминах нагруженных идентификаторов.

Косвенный вызов в Рефале-05: вызов по указателю

В Рефале-05 всё достаточно просто: имена функций — это указатели на сами функции, когда имя функции оказывается справа от < — она вызывается.

Рассмотрим пример, пусть у нас есть такие четыре файла:

Файл go.ref:

*$FROM a.ref
$EXTERN IndirectA;

*$FROM b.ref
$EXTERN IndirectB;

*$FROM c.ref
$EXTERN IndirectC;

$ENTRY Go {
  /* пусто */ =
    <IndirectA>
    <IndirectB>
    <IndirectC>;
}

Файл a.ref:

*$FROM b.ref
$EXTERN Call;

$ENTRY IndirectA { = <Call Callable> }

Callable { = <Prout 'A'> }

Файл b.ref:

$ENTRY IndirectB { = <Call Callable> }

Callable { = <Prout 'B'> }

$ENTRY Call {
  s.Func = <Mu s.Func>;
}

Файл c.ref:

*$FROM b.ref
$EXTERN Call;

$ENTRY IndirectC { = <Call CallableC> }

/*$ENTRY*/ CallableC { = <Prout 'C'> }
*    ↑——— раскомментировать для Рефала-5

(О комментариях будет сказано в следующем подпараграфе.)

Программа напечатает:

A
B
C

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

Символы-функции содержат указатель на код, при активации этот код просто выполняется.

Поэтому функция IndirectA поместит в поле зрения указатель на функцию Callable из файла a.ref, и именно её «косвенно» вызовет функция Call. В результате будет напечатана буква A. Аналогично будут вызываться Callable из файла b.ref и CallableC из файла c.ref.

Косвенный вызов в Рефале-5: вызов по имени

Если мы попробуем откомпилировать и запустить код из предыдущего подпараграфа, то Рефал-5 даст нам по рукам два раза. Первый раз при компиляции — потребуется заменить синтаксически некорректный косвенный вызов в функции Call на функцию Mu. Второй раз во время выполнения — программа вылетит при вызове Mu с аргументом CallableC. Чтобы это исправить, нужно сделать функцию CallableC entry-функцией. (Правки, которые необходимо внести, отмечены комментариями.)

Тогда программа корректно отработает и напечатает

B
B
C

С добавлением Mu в функцию Call всё понятно — синтаксис Рефала-5 требует, чтобы открывающая угловая скобка буквально содержала имя функции (скобка и имя рассматриваются как единый токен, между ними даже нельзя поставить пробел). А вот с CallableC и первой строчкой B всё не так очевидно.

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

Косвенный вызов в Рефале-5 осуществляется при помощи функции Mu — она первым термом принимает имя функции, остальная часть трактуется как аргумент. Имя может быть записано либо как символ-слово, либо как последовательность литер в круглых скобках:

<Mu t.FuncName e.AnyExpr> == e.AnyExpr

t.FuncName ::= s.WORD | (s.CHAR+)

Символы-слова в Рефале-5 никаких указателей на функции (адреса кода) не содержат, а уж цепочки литер — и подавно.

Функция Mu находит выполняемую функцию по её имени, причём поиск осуществляется сначала в том файле, где записан вызов функции Mu, а потом (если имя не найдено) — среди всех entry-функций программы.

Иначе говоря, можно считать, что в каждом файле исходной программы неявно определена своя локальная функция Mu, имеющая по паре предложений для каждой функции файла, каждой внешней функции и каждой встроенной функции. Например, для файла b.ref функция Mu имела бы такой вид:

Mu {
  /* все функции файла */
  IndirectB e.Arg = <IndirectB e.Arg>;
  ('IndirectB') e.Arg = <IndirectB e.Arg>;

  Callable e.Arg = <Callable e.Arg>;
  ('Callable') e.Arg = <Callable e.Arg>;

  Call e.Arg = <Call e.Arg>;
  ('Call') e.Arg = <Call e.Arg>;

  /* все entry-функции */
  Go e.Arg = <Go e.Arg>;
  ('Go') e.Arg = <Go e.Arg>;

  IndirectA e.Arg = <IndirectB e.Arg>;
  ('IndirectA') e.Arg = <IndirectB e.Arg>;

  IndirectB e.Arg = <IndirectB e.Arg>;
  ('IndirectB') e.Arg = <IndirectB e.Arg>;

  IndirectC e.Arg = <IndirectB e.Arg>;
  ('IndirectC') e.Arg = <IndirectB e.Arg>;

  CallableC e.Arg = <CallableC e.Arg>;
  ('CallableC') e.Arg = <CallableC e.Arg>;

  /* все встроенные функции */
  Mu e.Arg = <Mu e.Arg>;
  ('Mu') e.Arg = <Mu e.Arg>;

  Add e.Arg = <Add e.Arg>;
  ('Add') e.Arg = <Add e.Arg>;

  Arg e.Arg = <Arg e.Arg>;
  ('Arg') e.Arg = <Arg e.Arg>;

  ...
}

Примечание. Поиск среди entry-функций не документирован в учебнике Турчина, но о нём написано в дополнениях к учебнику.

Теперь рассмотрим, что происходит в нашем примере. Функция IndirectA помещает в поле зрения вызов <Call Callable>, где <Call — вызов функции Call из b.ref (прямые вызовы разрешаются при загрузке программы), а Callable — это просто символ-слово. Заметим, что к функции Callable из файла a.ref этот символ никакого отношения не имеет.

Функция Call определена в файле b.ref и она вызывает функцию Mu, которая будет осуществлять поиск в файле, где её вызов записан. Т.е. в b.ref. А там уже есть своя функция с именем Callable — она и будет вызвана. Поэтому первой строчкой напечатается B.

Абсолютно аналогично будет выполняться вызов функции IndirectB.

С функцией IndirectC интереснее — в поле зрения будет помещён <Call CallableC>, который раскроется в <Mu CallableC>. Функция CallableC не определена в файле b.ref, поэтому функция Mu будет осуществлять поиск среди entry-функций. CallableC — entry-функция, поэтому она будет вызвана и напечатает C. До того, как мы добавили ключевое слово $ENTRY, программа падала, поскольку функция Mu ничего не находила.

Примечание. Косвенный вызов в Рефале-5 может осуществляться также функциями Up, Ev-met и недокументированной Residue, имеющей короткий синтаксис <? …>, но для них поиск имени осуществляется точно так же.

Благодаря тому, что функция Mu может вызывать любые entry-функции программы, на Рефале-5 можно писать библиотеки функций высшего порядка. Например, можно написать функцию Map такого вида:

$ENTRY Map {
  s.Func t.Next e.Items = <Mu s.Func t.Next> <Map s.Func e.Items>;
  s.Func /* пусто */ = /* пусто */;
}

(Функция Map в LibraryEx определена немного иначе). А чтобы Mu могла находить вызываемую функцию по имени, её надо будет определять как entry:

$EXTERN Map;

$ENTRY Go {
  = <Map PrintLine ('One') ('Two') ('Three')>;
}

$ENTRY PrintLine {
  (e.Line) = <Prout e.Line>;
}

При этом надо быть уверенным, что файл с определением Map сам не содержит локальную функцию с именем PrintLine, поскольку в таком случае будет вызвана не та функция.

Косвенный вызов в общем подмножестве

Механизмы косвенного вызова в Рефале-05 и Рефале-5 существенно различаются, однако, для них можно найти общий знаменатель.

Очевидные синтаксические ограничения:

Тонкие семантические особенности:

Да, в Рефале-5 локальные функции не такие локальные: если в файле есть вызов Mu, можно извне вызвать любую из них.

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

Как же избежать конфликта имён? В исходных текстах Рефала-05 принят такой подход. Имена всех функций по умолчанию пишутся с большой буквы. Функций, которые косвенно вызываются извне и должны быть entry, начинаются с префикса, представляющего собой имя файла с маленькой буквы и прочерк. Такое странное имя, во-первых, предотвращает возможный конфликт, если в разных файлах потребуется определить entry-Функции с одинаковыми именами, во-вторых, намекает, что эта entry-функция является деталью реализации и не входит в интерфейс модуля. Пример такого имени: generator_GenCommand, которая определена в файле R05-Generator.ref

С другой стороны — локальные функции часто являются частью алгоритма какой-то другой entry-функции. Имена таких вспомогательных функций строятся из имени entry-функции путём добавления либо префиксов Sw и Do, либо смысловых суффиков вида Функция-Подфункция (см. Приложение A). Таким образом имена точек входа и имена вспомогательных функций заметно различаются и тем самым минимизируется конфликт.

Подытожим. Чтобы программа на Рефале-05 была корректной программой на Рефале-5:

Чтобы программа на Рефале-5 была корректной программой на Рефале-05:

Для иллюстрации последнего правила рассмотрим следующий пример. Файл go.ref:

$EXTERN CallA, CallB, Foo, Bar;

$ENTRY Go {
  = <CallA Foo> <CallB Foo> <CallA Bar> <CallB Bar>
}

Файл a.ref:

$ENTRY CallA { s.Func = <Mu s.Func> }

$ENTRY Foo { = <Prout 'A Foo'> }
Bar { = <Prout 'A Bar'> }

* В Рефале-05 неиспользуемая локальная функция является ошибкой синтаксиса
$ENTRY A { = Bar }

Файл b.ref:

$ENTRY CallB { s.Func = <Mu s.Func> }

Foo { = <Prout 'B Foo'> }
$ENTRY Bar { = <Prout 'B Bar'> }

* В Рефале-05 неиспользуемая локальная функция является ошибкой синтаксиса
$ENTRY B { = Foo }

Эта функция нарушает последнее правило, поскольку в вызове <CallB Foo> символ Foo является именем функции Foo из файла a.ref, а в точке вызова функции Mu (в теле функции CallB) видна локальная функция Foo файла b.ref. Та же проблема и с вызовом <CallA Bar>. И действительно, не смотря на то, что эта программа успешно компилируется и запускается обоими языками, результат выводится разный:

C:\…>refal05c.exe go.ref a.ref b.ref refal05rts Library
*Compiling go.ref:
*Compiling a.ref:
*Compiling b.ref:
+Linking C:\…\refal05rts.c
+Linking C:\…\Library.c
*** Compilation successed ***

C:\…>go.exe
A Foo
A Foo
B Bar
B Bar

C:\…>refc go.ref a.ref b.ref
Refal-5 Compiler. Version PZ Oct 29 2004
Copyright: Refal Systems Inc.

C:\…>refgo go+a+b
A Foo
B Foo
A Bar
B Bar