Комментарии 11
Поинтересуюсь: а сейчас подпрограммы - рекурсивные? (Когда появился RPG, аппаратного стека не было, и адрес возврата записывался в фиксированное место в памяти, что приводило к зацикливанию программы при рекурсивных вызовах.)
Честно скажу -не знаю. Не задумывался об этом и не пробовал.
Надо будет попробовать ради интереса.
Одну подпрограмму из другой вызвать не проблема. Т.е. какой-то стек точек возврата там есть.
Можно ли сабрутину саму из себя вызвать - не знаю...
Не только саму из себя, кстати. Может ещё быть косвенная рекурсия, самый сложный вариант для поиска ошибки, когда не организован полноценный стек вызовов-возвратов.
Ну в рекурсиях вообще ошибки сложнее искать.
Впрочем, сейчас сабрутины скорее как средство структурирования кода внутри прцедур испольются. Ну и чтобы избежать дублирования одинаковыз участков кода.
Одно время было модно делать выход из процедуры через сабрутину - такая единая точка выхода которую можно из любого места процедуры вызывать. Сейчас не актуально т.к. появился on-exit и можно просто ставить return где надо, а в точке выхода уже всю необходимую финализацию делать.
Так что сабрутины сейчас на второй план отшли.
Так и осталось
Пробуем
dcl-s recLevel int(5);
dcl-s counter int(5) inz(5);
exsr srRecursion;
return;
begsr srRecursion;
recLevel += 1;
dsply ('Recursion Level - ' + %char(recLevel));
dsply ('Counter - ' + %char(counter));
if counter > 0;
counter -= 1;
exsr srRecursion;
endif;
recLevel -= 1;
dsply ('Recursion Level - ' + %char(recLevel));
endsr;
Не компилируется - *RNF7112 30 EXSR or CASxx in subroutine SRRECUR... calls the same subroutine; the specification is ignored.
Пытаемся обмануть
dcl-s recLevel int(5);
dcl-s counter int(5) inz(5);
exsr srRecCall;
return;
begsr srRecCall;
recLevel += 1;
dsply ('Recursion Level - ' + %char(recLevel));
exsr srRecursion;
recLevel -= 1;
dsply ('Recursion Level - ' + %char(recLevel));
endsr;
begsr srRecursion;
dsply ('Counter - ' + %char(counter));
if counter > 0;
counter -= 1;
exsr srRecCall;
endif;
endsr;
Так компилируется, но...
DSPLY Recursion Level - 1
DSPLY Counter - 5
DSPLY Recursion Level - 2
DSPLY Counter - 4
DSPLY Recursion Level - 3
DSPLY Counter - 3
DSPLY Recursion Level - 4
DSPLY Counter - 2
DSPLY Recursion Level - 5
DSPLY Counter - 1
DSPLY Recursion Level - 6
DSPLY Counter - 0
DSPLY Recursion Level - 6
DSPLY Recursion Level - 5
DSPLY Recursion Level - 4
DSPLY Recursion Level - 3
DSPLY Recursion Level - 2
DSPLY Recursion Level - 1
DSPLY Recursion Level - 0
DSPLY Recursion Level - -1
DSPLY Recursion Level - -2
...
И дальше только ENDJOB помогает.
На мой вкус, странно, что wasError
имеет логический тип. Я на фортране использую примерно такой же стиль обработки выхода (только реализованный через goto), и мне намного чаще надо передавать через wasError
номер ошибки, а не просто факт ее наличия. Ошибки-то могут быть разные...
Ну... как есть.
На самом деле тут речь идет об необработанных системных исключениях. Типа деление на 0, выход за границу массива и т.п. Т.е. это флаг что в блок on-exit попали не по штатному return, а по исключению.
В PRG программа есть такая штука: PSDS - Program Status Data Structure
Dcl-Ds ProgramStatusInfo PSDS len(429) Qualified;
PROC_NAME *PROC; //* Procedure name - char(10)
PGM_STATUS *STATUS; //* Status code - zoned(5:0)
PRV_STATUS Zoned(5:0) Pos(16); //* Previous status
LINE_NUM Char(8) Pos(21); //* Src list line num
ROUTINE *ROUTINE; //* Routine name - char(8)
PARMS *PARMS; //* Num passed parms - zoned(3:0)
MSG_ID char(7) pos(40);
EXCP_TYPE Char(3) Pos(40); //* Exception type
EXCP_NUM Char(4) Pos(43); //* Exception number
PGM_LIB Char(10) Pos(81); //* Program library
EXCP_DATA Char(80) Pos(91); //* Exception data
EXCP_ID Char(4) Pos(171); //* Exception Id
DATE Char(8) Pos(191); //* Date (*DATE fmt)
YEAR Zoned(2:0) Pos(199); //* Year (*YEAR fmt)
LAST_FILE Char(8) Pos(201); //* Last file used
FILE_INFO Char(35) Pos(209); //* File error info
JOB_NAME Char(10) Pos(244); //* Job name
USER Char(10) Pos(254); //* User name
JOB_NUM Zoned(6:0) Pos(264); //* Job number
JOB_DATE Zoned(6:0) Pos(270); //* Date (UDATE fmt)
RUN_DATE Zoned(6:0) Pos(276); //* Run date (UDATE)
RUN_TIME Zoned(6:0) Pos(282); //* Run time (UDATE)
CRT_DATE Char(6) Pos(288); //* Create date
CRT_TIME Char(6) Pos(294); //* Create time
CPL_LEVEL Char(4) Pos(300); //* Compiler level
SRC_FILE Char(10) Pos(304); //* Source file
SRC_LIB Char(10) Pos(314); //* Source file lib
SRC_MBR Char(10) Pos(324); //* Source file mbr
PROC_PGM Char(10) Pos(334); //* Pgm Proc is in
PROC_MOD Char(10) Pos(344); //* Mod Proc is in
End-DS;
и если мы влетели в on-exit с wasError = *on, то там будет заполнена полная информация об ошибке - что за ошибка, в какой строке и т.п. И можно эту информацию оттуда взять и вернуть в качестве выходного параметра "структурированная ошибка".
Структурированные ошибки поддерживаются на уровне системы - есть специальные message file где содержатся текстовые расшифровки всех ошибок (можно создавать свои файлы и заполнять их своими ошибками). Каждая ошибка это 7-символьный код (как правило первые три символа - тип, потом 4 символа номер) плюс массив данных (по-моему, до 5-ти элементов, мы используем три).
Ошибки в *MSGF выглядят так:
Показать описания сообщений
Система: ALFALAB1
Файл сообщений: QCPFMSG Библиотека: QSYS
Поместить на . . . . . . ИД сообщения
Введите опции, нажмите Enter.
5=Показать сведения 6=Печать
Опц ИД сообщения Серьезность Текст сообщения
MCH0201 40 Object &2 not eligible for access group &1.
MCH0401 40 Access condition &1 not supported by machine
MCH0601 40 Space offset &2 or &9 is outside current limit
MCH0602 40 Boundary alignment of pointer or template not
MCH0603 40 Range of subscript value or character string e
MCH0604 40 Data object &1 not found.
MCH0605 40 Tried to address a space in object that does n
MCH0607 40 Unsupported space use.
MCH0609 40 Space address is not a teraspace storage addre
MCH0801 40 Argument associated with external or internal
Еще...
F3=Выход F5=Обновить F12=Отмена
Или подробно
Показать текст форматированного сообщения
Система: ALFALAB1
ИД сообщения . . . . . . . . : MCH0201
Файл сообщений . . . . . . . : QCPFMSG
Библиотека . . . . . . . . : QSYS
Сообщение . . . : Object &2 not eligible for access group &1.
Cause . . . . . : An ineligible condition was detected: An object and access
group do not have the same existence attribute, the object is restricted by
design from membership in the access group or the object is restricted by
the machine from membership in the access group. (The requested function
was not done.)
Конец
Для продолжения нажмите Enter.
F3=Выход F11=Показать текст неформатированного сообщения F12=Отмена
Object &2 not eligible for access group &1 - текст сообщения. &1 и &2 - места куда будет подставлены данные из массива.
Мы используем такую вот структуру
dcl-ds t_dsError37 qualified template;
errCode char(7) inz;
errParm1 char(10) inz;
errParm2 char(10) inz;
errParm3 char(10) inz;
errParmAll char(30) samepos(errParm1);
errParm12 char(20) samepos(errParm1);
errParm23 char(20) samepos(errParm2);
errStr char(37) samepos(errCode);
end-ds;
с тремя параметрами. Ну и для своих ошибок есть message file KSMMSGF.
Чтобы получить текст ошибки в RPG есть BIF %msg(error code: messge file: error data)
Если где-то заполнили ошибку
Error.errCode = 'ECL0046';
Error.errParmAll = 'УИК или персональные данные';
которая описана как "Недостаточно входных параметров. Должен быть передан &1&2&3", то вызвав
errStr = %msg(Error.errCode: 'KSMMSGF': Error.errParmAll);
получим в errStr текст "Недостаточно входных параметров. Должен быть передан УИК или персональные данные".
Спасибо, познавательно!
Я-то у себя стараюсь все ошибки перехватывать в своем коде, до системного уровня не доводить. Так, любой пользовательский ввод (не только прямой, но и косвенный, например прочитанный из файла) проверяется сразу же. В любых диалогах это железное правило. Поэтому на них у меня всегда навешивается куча подсказок на все случаи жизни (в зависимости от различий фактического и ожидаемого ответа). Ну и шаблон ввода, естественно. Мы еще в глубоких 1980-х годах реализовали в каждом диалоге запоминание предыдущей введенной строки (или чисел), и если она по смыслу примерно подходит, то при следующем открытии диалога подставляется в качестве умолчания. Одной из самых таинственных загадок для меня много лет был вопрос: почему другие программы почти никогда так не делают, это ж удобно?! ;-) Вообще, при кодировании я исхожу из того, что обычно юзер морозит глупости, а корректный ввод - это скорее приятное исключение ;-)
А при запросах к системе (среда, устройства, файлы) в фортране обычно можно прямо в операторе написать "err=...
", и получить дополнительную информацию средствами языка, что я и делаю. Ну а деление на ноль проверяю "вручную" (на самом деле до нуля вообще доходить не должно, так как я стараюсь контролировать потерю точности всюду, где есть такая опасность. Поэтому ошибка выбросится намного раньше, чем переменная обнулится). В результате обработка системных и "своих" ошибок получается единообразной.
Ну пользовательского ввода у нас немного - мы все больше про то, что само там крутится.
Но там, где пользовательский ввод (у нас это называется "опция ведения таблицы"), там по нашему стандарту 5 модулей - дисплейник (*DSPF - специальный файл где "нарисован" набор экранных форм с полями ввода-вывода на DDS), MR - код, работающий непосредственно с дисплейником (обработка клавиш, переходы с одного экрана на другой и т.п.), VR - модуль валидации введенных полей, UR - собственно модуль работы с файлом (чтение-запись) и RR - это т.н. "внешний ввод" - когда нужно данные вводить не в интерактиве, а программно.
Данные приходят или из интерактива - MR или из внешнего ввода - RR, Пропускаются через VR и если все ок, то уходят в UR.
В подробностях все это очень долго объяснять...
Что касается ошибок, то структурированная ошибка - универсальный механизм. Тот же VR может заполнить такую ошибку и вернуть ее наверх. И этот же механизм используется системными исключениями. Которые можно не обрабатывать (тогда получишь падение программы и вывод всей информации в дамп), или перехватывать (например, через monitor/on-excp - аналог try/catch). Тогда падения не будет - исключение перехватывается и на его данных можно формировать структурированную ошибку которая возвращается наверх.
Сами мы исключения не генерируем, хотя возможность самому бросать свои ошибки в виде системных исключений есть. Но это очень плохо по производительности (причем, в принципе плохо - на C++ митапах тема падения производительности при злоупотреблении исключениями периодически поднимается).
Ну и ошибка - не всегда ошибка. Бывают ситуации когда одна и та же ошибка в одном случае требует остановки программы, а в другом ее нужно просто залогировать и продолжить выполнение дальше... Поэтому нужно перехватить исключение, сформировать структурированную ошибку и дальше уже решать что с ней делать.
Современный RPG — что может и зачем нужен