Как стать автором
Обновить

Комментарии 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++ митапах тема падения производительности при злоупотреблении исключениями периодически поднимается).

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации