Рефал-05

Установка и использование, конфигурирование, отладка

Сборка компилятора

Готовых инсталляторов вроде setup.exe нет, компилятор нужно вручную собирать из исходников и правильно настроить переменные окружения.

Компилятор зависит от этого набора библиотек:

https://github.com/Mazdaywik/refal-5-framework

поэтому предварительно её надо установить согласно имеющейся там инструкции, без неё компилятор не соберётся.

Исходные тексты можно загрузить со страницы репозитория на GitHub

https://github.com/Mazdaywik/Refal-05

кликнув по зелёной кнопке «Clone or download↓», либо, если у вас установлен Git, склонировать его командой

git clone https://github.com/Mazdaywik/Refal-05

Для дальнейшей раскрутки компилятора понадобятся установленные компиляторы языка Си и Рефала-5 или Рефала-5λ.

Дальнейшие шаги зависят от операционной системы. На POSIX-системах (Linux, macOS) достаточно запустить скрипт ./bootstrap.sh (если установлен Рефал-5) или ./bootstrap.sh lambda (если установлен Рефал-5λ). После этого автоматически будут созданы папка bin и файл c-plus-plus.conf.sh с параметрами компилятора Си, используемого для раскрутки. По умолчанию там указан компилятор gcc с опциями -Wall -g. При желании компилятор можно поменять (по инструкциям в комментариях) и выполнить раскрутку ещё раз. Если команда gcc в командной строке недоступна (используется какой-нибудь другой компилятор Си), то раскрутка пройдёт неуспешно, нужно будет в c-plus-plus.conf.sh вписать правильную команду для запуска компилятора Си.

Примечание. На macOS часто ставится только Clang, но при этом команда gcc является псевдонимом для запуска Clang’а, поэтому никаких дополнительных действий делать не требуется.

На Windows всё немного интереснее: компиляторов языка Си целый зоопарк, а значит, некоторый «дефолтовый», который доступен почти везде, вписать в конфигурационный файл нельзя. Поэтому установка выполняется в две стадии. После первого запуска bootstrap.bat (если установлен Рефал-5) или bootstrap.bat lambda (если установлен Рефал-5λ) установка завершится заведомо с ошибкой и будет создан файл c-plus-plus.conf.bat. После этого в него нужно вписать командную строку для запуска компилятора и вызвать bootstrap.bat/bootstrap.bat lambda второй раз. В конфигурационном файле уже есть закомментированные заготовки для запуска компиляторов BCC 5.5, Visual C++, GCC, Clang и Watcom — их можно раскомментировать, уточнив пути к папкам.

Примечание. Такой же механизм конфигурации для раскрутки (и не только раскрутки) применяется в Рефале-5λ, только там используется компилятор C++. Имя файла c-plus-plus.conf.* унаследовано оттуда. В следующих версиях конфигурационный файл может быть переименован.

Установка и конфигурирование

После успешной раскрутки у Вас должна появиться папка bin, содержащая файл refal05c.exe или refal05c со флагом исполнимости (в зависимости от ОС). Этот файл и есть исполнимый файл компилятора — его уже (не выполняя следующих шагов) можно использовать для трансляции исходников Рефала-05 в Си. Чтобы его можно было запускать из любой папки, добавьте его в переменную окружения PATH (настройка переменных окружения зависит от операционной системы, поэтому мы не будем здесь давать подробных шагов).

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

Для этого нужно установить следующие переменные окружения:

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

Примечание. На POSIX-системах (Linux или macOS) в переменную R05CCOMP желательно добавлять -DR05_POSIX (например, gcc -DR05_POSIX) — в этом случае функция System будет корректно возвращать код возврата. Без данного флага работать всё равно всё будет, только System будет возвращать сырое значение функции system языка Си, которое может отличаться от фактического кода возврата (см. man 2 wait для более подробных сведений).

Если вы пользуетесь компилятором Рефал-5 и раскрутка Рефала-05 производилась с его помощью, в папке bin будут располагаться .rsl-файлы для всех файлов каталога src. Путь к папке bin можно добавить к переменной окружения REF5RSL.

Пример. Для операционной системы Windows 98, компилятора BCC 5.5 и данного дистрибутива, расположенного в C:\Refal-05 нужно в конец autoexec.bat добавить следующие строчки:

set PATH=%PATH%;C:\Refal-05\bin
set R05CCOMP=bcc32 -w
set R05PATH=C:\Refal-05\lib;C:\Refal-05\src
set REF5RSL=%REF5RSL%;C:\Refal-05\bin

Настройка окружения завершена. Компилятором теперь можно пользоваться.

Использование компилятора

Синтаксис командной строки простой, компилятор может вызываться двумя способами:

refal05c имяфайла1 имяфайла2 имяфайла3...

или

refal05c @списокфайлов

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

Файлы, которые перечислены в командной строке, должны быть исходными текстами либо на Рефале (расширение .ref), либо на Си (.c). Для каждого файла последовательно проверяется его наличие в текущей папке, затем по каждому из путей, перечисленных в R05PATH и REF5RSL. Если расширение не указано, то по каждому пути поиска (текущая папка и папки из R05PATH и REF5RSL) проверяется наличие сначала с расширением .ref, потом с расширением .c.

Каждый файл на Рефале компилируется в одноимённый файл с расширением .c, который находится в той же папке, что и исходный. Отсюда и ограничение, что пути поиска, перечисленные в R05PATH должны быть доступны для записи.

Если переменная R05CCOMP установлена и не пустая, то после компиляции всех исходников имена сишных файлов (как заданных пользователем, так и сгенерированных) передаются компилятору R05CCOMP. Для каждого пути поиска в командную строку запуска компилятора добавляется опция -I (include path) с путём до каждой папки в R05PATH и REF5RSL.

Компилятор также понимает переменную среды R05CFLAGS, содержимое которой (если она установлена) добавляется в командную строку компилятора Си. Эта переменная используется для временного задания ключей для конкретного запуска, например, для компилятора GCC можно задавать имя целевого файла при помощи -ofilename (без этого ключа будет создан исполнимый файл с именем по умолчанию вроде a.exe или a.out).

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

Никакие исходные файлы в командную строку неявно не добавляются, поэтому пользователь должен сам всегда явно указывать два вспомогательных файла: библиотеку поддержки времени выполнения (runtime support library, далее будем называть её рантайм) refal05rts.c и библиотеку встроенных функций языка Library.c.

Рантайм refal05rts.c содержит реализацию функций, вызываемых из сгенерированного кода — элементарных команд сопоставления с образцом и построения результата, имитацию абстрактной рефал-машины, средства отладки (прежде всего, отладочный дамп) и функцию main() языка Си.

Библиотека встроенных функций Library.c содержит реализации всех встроенных функций языка, написанные на Си.

Файлы refal05rts.c и Library.c, а также refal05rts.h, содержащий определения внутренних структур данных и прототипы функций, используемых в сгенерированном коде, располагаются в каталоге lib. Поскольку для папок, перечисленных в R05PATH и REF5RSL, автоматически формируется опция -Iпапка, файл refal05rts.h будет найден компилятором Си без каких-либо дополнительных действий со стороны пользователя.

Каталог src содержит исходные тексты компилятора, выполненные как компоненты повторного использования.

Таким образом, любой запуск компилятора требует указания refal05rts и Library.

Пример. Пусть компилятор установлен правильно (настроены R05CCOMP и R05PATH) и записана такая командная строка:

refal05c myprogram refal05rts Library LibraryEx R05-Generator

Компилятор в этом случае найдёт myprogram.ref в текущей папке, refal05rts.c и Library.c в папке lib, LibraryEx.ref в библиотеке «refal-5-framework», R05-Generator.ref в папке src. Будут созданы файлы myprogram.c в текущей папке, LibraryEx.c в папке библиотеки и R05-Generator.c в папке src. Если задана непустая переменная R05CCOMP, будет вызван компилятор Си, которому будут переданы четыре сишных файла и ключи -I с путями к папкам lib и src.

Средства отладки и диагностики программ на Рефале-05

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

Аварийный отладочный дамп

Основным средством поиска и диагностики ошибок является аварийный дамп. Если для некоторого вызова функции не удалось сопоставить аргумент ни с одним из образцов, выполнение программы прерывается и на stderr выводится ошибочное первичное активное подвыражение и вслед за ним всё поле зрения. Похожий дамп выводится, если программе не хватило памяти (в этом случае дамп может быть очень длинным), либо если произошла ошибка при вызове встроенной функции (например, деление на ноль в Div и Mod или файл для открытия в Open не существует).

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

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

Средства отладки и диагностики рантайма

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

В большинстве компиляторов макрос препроцессора устанавливается опцией -Dимя — просто определяет макрос и -Dимя=значение — устанавливает заданное значение.

Например, следующий запуск

 gcc -DPLATFORM_WIN32 -DPAGE_SIZE=4096 myprogram.c

откомпилирует myprogram.c с установленными макросами PLATFORM_WIN32 и PAGE_SIZE, причём второй будет заменяться на значение 4096.

При отладке программ на Рефале диагностические макросы можно непосредственно задавать в R05_CCOMP, но рекомендуется эту переменную не трогать, вместо чего использовать R05_CFLAGS.

Пример. Соберём программу с выводом статистики и лимитом по памяти 1000000 узлов. Командная строка для Windows:

set R05_CFLAGS=-DR05_SHOW_STAT -DR05_MEMORY_LIMIT=1000000
refal05c myprogram refal05rts Library LibraryEx

Командная строка для Bash:

R05_CFLAGS="-DR05_SHOW_STAT -DR05_MEMORY_LIMIT=1000000"
refal05c myprogram refal05rts Library LibraryEx

Далее мы перечислим все доступные макросы.

Макрос R05_SHOW_STAT — общий профиль работы программы

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

Total program time: 2.594 seconds (100.0 %).
(Total refal time): 1.393 seconds (53.7 %).
Builtin time: 1.201 seconds (46.3 %).
Linear result time: 0.740 seconds (28.5 %).
Linear pattern time: 0.653 seconds (25.2 %).
Open e-loop time (clear): 0.142 seconds (5.5 %).
t- and e-var copy time: 0.046 seconds (1.8 %).
Repeated e-var match time (inside e-loops): 0.031 seconds (1.2 %).
Step count 3610740
Memory used 270327 nodes, 270327 * 16 = 4325232 bytes

Для времени работы программы отдельно выписываются различные виды затрат, в скобках указывается процент от полного времени работы. Если затраты времени на некоторую компоненту оказались равны нулю (меньше погрешности измерения времени), соответствующая строчка не печатается.

Рассмотрим каждую из строчек подробнее:

«Линейное» время сопоставления с образцом и построения результата зависит только от вида левых и правых частей, остальные компоненты — циклы удлинения, сравнение на равенство повторных переменных, копирование переменных в правой части — зависят и от входных данных. Подробнее эти компоненты мы обсудим при рассмотрении деталей реализации.

Макрос R05_SHOW_DEBUG=n

Иногда по состоянию поля зрения на момент ошибки трудно понять, откуда же эта ошибка взялась, как сформировался некорректный аргумент для функции. Макрос R05_SHOW_DEBUG позволяет увидеть временной контекст ошибки — некоторое количество шагов, предшествующее падению. Если макрос установлен и равен неотрицательному числу n, то начиная с n-го шага на stderr будет на каждом шаге выводиться дамп поля зрения (в том же формате, что и при ошибке).

Поэтому, если непонятен контекст ошибки, установите макрос R05_SHOW_DEBUG равным числу, немного меньшему, чем номер шага, который привёл к ошибке. На сколько меньшему — определяется опытным путём.

Также макрос может быть полезен при отладке зависаний. Если программа после правки начала работать аномально долго — есть подозрение, что она вошла в бесконечный цикл (бесконечную рекурсию), то можно установить достаточно большой номер шага и, перенаправив stderr в файл, прибить (Ctrl-C) процесс спустя некоторое время. Скорее всего, в дампе будет виден искомый бесконечный цикл.

Макрос R05_DUMP_FREE_LIST

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

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

Макрос R05_DUMP_BURIED

Если этот макрос установлен, то программа при завершении выводит содержимое копилки.

Макрос R05_MEMORY_LIMIT=n

Макрос должен принимать целочисленное значение. Если макрос установлен, то при превышении объёма распределённой памяти (в узлах, см. выше про Memory used) программа будет останавливатся с ошибкой недостатка памяти, даже если в системе памяти достаточно.

Макрос предназначен для отладки программ, которые зависают с потреблением памяти в бесконечном цикле — остановка при достижении порога позволяет их прервать раньше.

Макрос R05_PROFILER

Если этот макрос установлен, то подсчитывается время выполнения шагов каждой функции. Программа при завершении формирует файл __profile-05.ref с примерно следующим содержимым (для примера я самоприменил компилятор):

Total steps: 2792534
Total time: 2.172 secs
Mean step time: 0.778 us

  1. System               688.000 ms ( 31.68 % +=  31.68 %),      1 calls, 884559.573 steps
  2. Putout               109.000 ms (  5.02 % +=  36.69 %),  57367 calls,      2.443 steps
  3. DoGenResult           78.000 ms (  3.59 % +=  40.29 %),  16157 calls,      6.207 steps
  4. EscapeName-GlueCodes  63.000 ms (  2.90 % +=  43.19 %),  42450 calls,      1.908 steps
  5. Expression            62.000 ms (  2.85 % +=  46.04 %),  30131 calls,      2.646 steps
  6. Map                   48.000 ms (  2.21 % +=  48.25 %), 160512 calls,      0.384 steps
  7. DoScan_cont           48.000 ms (  2.21 % +=  50.46 %),  16082 calls,      3.837 steps
  8. Apply                 47.000 ms (  2.16 % +=  52.62 %), 187162 calls,      0.323 steps
  9. Inc                   46.000 ms (  2.12 % +=  54.74 %), 117604 calls,      0.503 steps
  . . .

В начале отображается общее количество шагов, общее время работы и продолжительность среднего шага (в примере один шаг выполняется за 0,778 мкс).

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

В зависимости от операционной системы и её настроек, суммарное время выполнения функций может квантоваться. Например, этот запуск в примере производился на Windows, поэтому квант времени 1/64 секунды ≈ 15,6 мс, что мы и видим: время их работы функций составляет, соответственно, 44, 7, 5, 4, 4, 3, 3, 3 и 3 кванта.

Если суммарное время работы функции равно нулю, то она в профиле не пишется.

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

Колонки таблицы следующие:

Накопительный итог интересен тем, что позволяет убедиться в наличии узких мест — наибольший вклад в общее время работы вносит сравнительно небольшое количество функций. В примере выше половина (50,46 %) от общего времени работы тратится на выполнение всего семи функций.

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

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

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

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

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

Третья строчка — функция DoGenResult, которая выполняется 3,59 % от общего времени выполнения и один её шаг занимает 6,2 средних шага программы. 6,2 > 1, т.к. внутри DoGenResult активно используются открытые e-переменные. Если мы её каким-то чудом перепишем так, чтобы она работала мгновенно, то общее время работы программы сократится всего на 3,59 %, так что оптимизировать её нецелесообразно.

Макрос R05_CLOCK_SKIP=N

Замеры времени, выводимые макросом R05_SHOW_STAT, выполняются всегда, сам макрос только разрешает и запрещает вывод статистики в stderr. Замер времени осуществляется функцией clock() языка Си, которая в операционных системах Linux и macOS работает медленно, что вносит существенные накладные расходы в программы на Рефале-05.

Для ускорения работы на Linux и macOS предусмотрен макрос R05_CLOCK_SKIP=N, который подменяет встроенную функцию clock() на более быстрый эквивалент. Если макрос установлен, обращение к системной функции clock() будет осуществляться в N раз реже. Это может снизить точность замеров (оценки точности не проводились), однако существенно ускоряет выполнение программ.

В создаваемом по умолчанию файле c-plus-plus.conf.sh макрос R05_CLOCK_SKIP по умолчанию устанавливается в 20.

Использование отладчика языка Си

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

Однако, отладчик полезен при отладке функций, которые сами пишутся на Си, или рантайма. В частности, автор использовал GDB для просмотра точки падения и трассировки стека при ошибках доступа к памяти (SEGFAULT’ах).

Отладчики Рефала-5 (reftr) и Рефала-5λ

Если программа написана на общем подмножестве, то её можно собрать другим компилятором Рефала-5 и использовать средства отладки другой реализации.

Метод неприменим, если программа не написана на общем подмножестве.

Отладка на уровне исходного кода — отладочная печать

Средств для отладки у Рефала-05 немного, поэтому при поиске и устранении ошибок в программах часто приходится прибегать к довольно примитивному инструменту — отладочной печати. Заключается она, как не трудно догадаться, во вставке в программу операций вывода — либо Prout для печати на консоль, либо Putout для печати в файл.

Поскольку открывать файлы в Рефале-05 (как и в Рефале-5) необязательно, простота вывода в файл сравнима с простотой вывода на консоль — вместо Prout пишем Putout с неиспользуемым в программе номером. А дальше просто смотрим текстовый файл REFAL*.DAT.

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

Допустим, мы хотим распечатать каждый вызов функции DoFib из листинга ниже:

Fibonacci {
  1 = 1;
  s.N = <DoFib 2 s.N 1 1>;
}

DoFib {
  s.N s.N s.Prev s.Cur = s.Cur;

  s.K s.N s.Prev s.Cur =
    <DoFib <Add 1 s.K> s.N s.Cur <Add s.Prev s.Cur>>;
}

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

Для этого сначала переименуем DoFib, например, в DoFib-DEBUG.

DoFib-DEBUG {
  s.N s.N s.Prev s.Cur = s.Cur;

  s.K s.N s.Prev s.Cur =
    <DoFib <Add 1 s.K> s.N s.Cur <Add s.Prev s.Cur>>;
}

Отлаживаемая функция имеет формат <DoFib s.K s.N s.Prev s.Cur>. Добавим новую функцию с именем DoFib, левая часть которой совпадает с форматом исходной. В правой части запишем вызов переименованной функции.

Важно. В общем случае, если отлаживаемая функция имела модификатор $ENTRY, функция-оболочка с отладочной печатью тоже должна быть entry.

Удобно это делать перед исследуемой функцией:

DoFib {
  s.K s.N s.Prev s.Cur =
    <DoFib-DEBUG s.K s.N s.Prev s.Cur>;
}

DoFib-DEBUG {
  s.N s.N s.Prev s.Cur = s.Cur;

  s.K s.N s.Prev s.Cur =
    <DoFib <Add 1 s.K> s.N s.Cur <Add s.Prev s.Cur>>;
}

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

DoFib {
  s.K s.N s.Prev s.Cur =
    <Putout 13 '<DoFib>'>
    <Putout 13 '  s.K = ' s.K>
    <Putout 13 '  s.N = ' s.N>
    <Putout 13 '  s.Prev = ' s.Prev>
    <Putout 13 '  s.Cur = ' s.Cur>
    <Putout 13>
    <DoFib-DEBUG s.K s.N s.Prev s.Cur>;
}

DoFib-DEBUG {
  s.N s.N s.Prev s.Cur = s.Cur;

  s.K s.N s.Prev s.Cur =
    <DoFib <Add 1 s.K> s.N s.Cur <Add s.Prev s.Cur>>;
}

В этом примере отладочная печать будет писаться в файл REFAL13.DAT, который будет открыт автоматически (при этом программист должен быть уверен, что файл № 13 не используется в данный момент).

Если отладочная функция располагалась перед исходной, то её довольно легко удалить — нужно стереть строки от DoFib (не включая) до DoFib-DEBUG (включая), удаляемые строки помечены крестиком:

  DoFib {
×   s.K s.N s.Prev s.Cur =
×     <Putout 13 '<DoFib>'>
×     <Putout 13 '  s.K = ' s.K>
×     <Putout 13 '  s.N = ' s.N>
×     <Putout 13 '  s.Prev = ' s.Prev>
×     <Putout 13 '  s.Cur = ' s.Cur>
×     <Putout 13>
×     <DoFib-DEBUG s.K s.N s.Prev s.Cur>;
× }
×
× DoFib-DEBUG {
    s.N s.N s.Prev s.Cur = s.Cur;

    s.K s.N s.Prev s.Cur =
      <DoFib <Add 1 s.K> s.N s.Cur <Add s.Prev s.Cur>>;
  }

Как упростить отладку — соблюдать форматы функций

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

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

* ЭТО ПЛОХОЙ ПРИМЕР, НЕ ПИШИТЕ ТАК!!!

Fibonacci {
  1 = 1;

  s.N = <Fibonacci 2 s.N 1 1>;

  s.N s.N s.Prev s.Cur = s.Cur;

  s.K s.N s.Prev s.Cur =
    <Fibonacci <Add 1 s.K> s.N s.Cur <Add s.Prev s.Cur>>;
}

Здесь мы объединили функции Fibonacci и DoFib, пользуясь тем, что они имеют разные форматы.

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

Но здесь мы имеем случай с разными несовместимыми форматами. Перепутать одно число и четыре трудно. Но если формат различается на один терм и/или содержит e-переменную на верхнем уровне, допустить ошибку становится проще (а обнаружить наоборот сложнее).

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

* Это хороший пример

$ENTRY refal05c_PrintNotFound {
  (NotFound e.FileName) =
    <Prout 'COMMAND LINE ERROR: file ' e.FileName ' not found'>;

  (Output e.FileName) = ;

  (Source (e.Source) e.Output) = ;
}

можно «сократить» так:

* А ЭТО ПЛОХОЙ ПРИМЕР, НЕ ПИШИТЕ ТАК!!!

$ENTRY refal05c_PrintNotFound {
  (NotFound e.FileName) =
    <Prout 'COMMAND LINE ERROR: file ' e.FileName ' not found'>;

  e.Other = ;
}

Функцию

* Это хороший пример

DoParseBlock {
  t.ErrorList (e.References) (e.Sentences) (TkCloseBlock t.SrcPos) e.Tail =
    (Sentences e.Sentences) t.ErrorList (e.References) e.Tail;

  t.ErrorList (e.References) (e.Sentences) (TkEOF t.SrcPos) e.Tail =
    (Sentences e.Sentences)
    <EL-AddErrorAt
      t.ErrorList t.SrcPos 'Unexpected EOF, expected "}"'
    >
    (e.References)
    (TkEOF t.SrcPos) e.Tail;

  t.ErrorList (e.References) (e.Sentences) e.Tokens =
    <ParseSentence t.ErrorList (e.References) (e.Sentences) e.Tokens>;
}

«упростить» так (см. последнее предложение):

* А ЭТО ПЛОХОЙ ПРИМЕР, НЕ ПИШИТЕ ТАК!!!

DoParseBlock {
  t.ErrorList (e.References) (e.Sentences) (TkCloseBlock t.SrcPos) e.Tail =
    (Sentences e.Sentences) t.ErrorList (e.References) e.Tail;

  t.ErrorList (e.References) (e.Sentences) (TkEOF t.SrcPos) e.Tail =
    (Sentences e.Sentences)
    <EL-AddErrorAt
      t.ErrorList t.SrcPos 'Unexpected EOF, expected "}"'
    >
    (e.References)
    (TkEOF t.SrcPos) e.Tail;

  e.Args = <ParseSentence e.Args>;
}

Обе функции (конечно, в хороших примерах) взяты из исходников компилятора Рефала-05.

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

Коды возврата скомпилированных программ

При корректном завершении (когда поле зрения становится пассивным — не содержит вызовов функций) программа завершается с кодом возврата 0.

Если программа завершилась вызовом функции Exit, то её кодом возврата будет указанное значение с точностью до особенностей платформы. Например, на Linux будут учитываться только младшие 8 бит кода возврата.

В случае аварийного останова код возврата будет следующим: