zOs/REXX.O08/CHECKRTC

/* rexx ****************************************************************
   rebuild null ||| und prüfen ||||

***********************************************************************/
call mapIni
call sqlIni
parse arg list
if 0 & list = '' then
    list = QR30403
Pref = dsn2jcl('~CHECKRTS')
tsPref = pref'.OLITS'
ixPref = pref'.OLIIX'
m.spPref = dsn2jcl('~CHECKRTS.OPR')

if list = '-alloc' | list = '-delete' then do
     f  = substr(list, 2, 1)
     call alcDlt f, A540769.CHECKRTS.OLIIXNEW, 'F'
     call alcDlt f, A540769.CHECKRTS.OLIIXOLD, 'F'
     call alcDlt f, A540769.CHECKRTS.OLITSNEW, 'F'
     call alcDlt f, A540769.CHECKRTS.OLITSOLD, 'F'
     call alcDlt f, A540769.CHECKRTS.OPRIXNEW, 'V'
     call alcDlt f, A540769.CHECKRTS.OPRIXOLD, 'V'
     call alcDlt f, A540769.CHECKRTS.OPRTSNEW, 'V'
     call alcDlt f, A540769.CHECKRTS.OPRTSOLD, 'V'
     call alcDlt f, A540769.CHECKRTS.SYSPRINT, 'V'
     exit
     end
if list = '-c' then do
    call countNew pref'.OPRTSNEW'
    call countNew pref'.OPRIXNEW'
    exit
    end
call sqlConnect 'DBTF'

call qeysIni   'E     equal'                 ,
             , 'NLN   n LoadNull'            ,
        /*   , 'NB    n Reb noNu only' */    ,
             , 'NBN   n Rebu null'           ,
             , 'NO1N  n old 1 null'          ,
        /*   , 'NRN   n ReoNul LoaOld' */    ,
             , 'NZ    n rows=0'              ,
             , 'NM    n no RTS'              ,
             , 'OS    o rows<100'            ,
             , 'OLG   o ReoOldLoaNew'        ,
             , 'OSP   o spaeter'
if list = '' | list = '*' then do
    call cmpPds tsPref'OLD', tsPref'NEW'
    call qeysSayLong
    call cmpPds ixPref'OLD', ixPref'NEW'
    end
else do
    say m.qTit
    do lx=1 to words(list)
        lw = word(list, lx)
        say '*** comparing' lw
        call cmpMbr lw, tsPref'OLD', tsPref'NEW'
        call cmpMbr lw, ixPref'OLD', ixPref'NEW'
        end
    say m.qTit
    end
call sqlDisconnect
call qeysSayLong
exit

alcDlt: procedure expose m.
parse arg fun, dsn, ii
     if fun = 'd' then
         call adrTso "delete '"dsn"'"
     else do
         ff = dsnAlloc(dsn'(A) dd(x) ::'ii)
         interpret subword(ff, 2)
         end
     return
cmpPds: procedure expose m.
parse arg old, new
    iO = lmmBegin(old)
    mO = lmmNext(iO)
    iN = lmmBegin(new)
    mN = lmmNext(iN)
    say m.qTit
    do forever
        if mO = mN then do
            if mO = '' then
                leave
            if 0 & mO > 'QR02501' then
                leave
            call cmpMbr mO, old, new
            mO = lmmNext(iO)
            mN = lmmNext(iN)
            end
        else
            call err 'member old' mO '<>' mN
        end
    call lmmEnd iO
    call lmmEnd iN
    say m.qTit
    return
endProcedure cmpPds

cmpMbr: procedure expose m.
parse arg mbr, old, new
    yeOl = translate('1234-56-78', (date(s) - 10000)'-', '12345678-')
    yeOl = left(yeOl, 8)right(right(yeOl,2)+1, 2, 0)  /* SchaltJahr */
    call mapReset c, 'K'
    m.type = ''
    call ext c, 'old', old'('mbr')'
    call ext c, 'new', new'('mbr')'
    k = mapKeys(c)
    do kx=1 to m.k.0
        ff = mapGet(c, m.k.kx)
        tt = left(m.type, 1)
        if ff = '=' then do
            m.cCnt.E = m.cCnt.E + 1
            iterate
            end
        call selRts m.type, m.k.kx
        q = ''
        if m.r.0 <> 1 then do
            if ^ (m.r.0 = 0 & ff = 'new') then do
                say '??? 1 <>' m.r.0 'rts count' tt mbr m.k.kx
                if m.r.0 = 0 then
                    iterate
                end
            if m.r.0 = 0 then
                m.r.1.nActive = m.sql.null
            end
        if m.r.0 = 0 & ff = 'new' then
            q = NM
        else if ff = 'new' & m.r.1.reorgLastTime ^== m.sql.null ,
                      & m.r.1.loadRLastTime == m.sql.null then
            q = NLN
   /*   else if ff = 'new' & m.r.1.reorgLastTime == m.sql.null ,
                      & m.r.1.loadRLastTime ^== m.sql.null ,
                      & left(m.r.1.loadRLastTime, 10) << yeOl then
            q = NRN
        else if ff = 'new' & tt = 'I' ,
               & m.r.1.REBUILDLASTTIME ^== m.sql.null ,
               & m.r.1.reorgLastTime == m.sql.null ,
               & m.r.1.loadRLastTime == m.sql.null then
            q = NB
   */   else if ff = 'new' & tt = 'I' ,
               & (m.r.1.REBUILDLASTTIME == m.sql.null    ,
                  | m.r.1.reorgLastTime == m.sql.null    ,
                  | m.r.1.loadRLastTime == m.sql.null )  ,
               & left(m.r.1.rebuildLastTime, 10) << yeOl ,
               & left(m.r.1.reorgLastTime  , 10) << yeOl ,
               & left(m.r.1.loadRLastTime  , 10) << yeOl then
            q = NO1N
        else if ff = 'new' & tt = 'T' ,
               & (  m.r.1.reorgLastTime == m.sql.null    ,
                  | m.r.1.loadRLastTime == m.sql.null )  ,
               & left(m.r.1.reorgLastTime  , 10) << yeOl ,
               & left(m.r.1.loadRLastTime  , 10) << yeOl then
            q = NO1N
        else if ff = 'new' ,
                    & ((tt = 'T' & m.r.1.totalRows <= 0) ,
                      |(tt = 'I' & m.r.1.totalEntries <= 0)) then
            q = NZ
        else if ff = 'old' ,
                    & ((tt = 'T' & m.r.1.totalRows <  100) ,
                      |(tt = 'I' & m.r.1.totalEntries < 100)) then
            q = OS
        else if ff = 'old' & m.r.1.reorgLastTime ^== m.sql.null ,
                      & left(m.r.1.reorgLastTime, 10) << yeOl,
                      & m.r.1.loadRLastTime ^== m.sql.null ,
                      & left(m.r.1.loadRLastTime, 10) >>= yeOl then
            q = OLG
   /*   else if m.r.1.UPDATESTATSTIME >> '2008-04-06-15.31 ???' then
            q = N
   */   else if ff = 'old' & spaeter(mbr, m.type, m.k.kx) then
            q = oSp
 /*     else if ff = 'new' & tt = 'I' ,
               & m.r.1.REBUILDLASTTIME == m.sql.null then
            q = NBN
 */     else do
            say '??? no explanation for' mbr ff m.type m.k.kx
            say '   ' m.spaeter
            end
        if q <> '' then do
            if 1 & m.cCnt.q = 0 then
                say '?? first' q 'for' mbr ff m.type m.k.kx,
                   'cAct' m.cAct.q 'nActive' m.r.1.nActive
            m.cCnt.q = m.cCnt.q+1
            if m.r.1.nActive ^== m.sql.null then
                m.cAct.q = m.cAct.q + m.r.1.nActive
            end
        end
    if m.k.0 > 0 then
        say qeysFmt(ff, tt, mbr)
    return
endProcedure cmpMbr

qeysFmt: procedure expose m.
parse arg ff, ty, mbr
    r = left(ff, 4) left(ty, 1) left(mbr, 8)
    do qx=1 to words(m.qeys)
        qq = word(m.qeys, qx)
        r = r || right(m.cCnt.qq, 6)
        end
    return r
endProcedure qeysFmt

qeysSayLong: procedure expose m.
    do qx=1 to words(m.qeys)
        qq = word(m.qeys, qx)
        say left(qq ,3) left(strip(m.qeyTxt.qx), 20) ,
                         right(m.cCnt.qq, 10) right(m.cAct.qq, 20)
        end
    return
endProcedure qeysSayLong

qeysIni: procedure expose m.
    qx = 0
    m.qeys  = ''
    do ax=1 to arg()
        parse value arg(ax) with k m.qeyTxt.ax
        m.cCnt.k = k
        m.qeys = m.qeys k
        end
    m.qTit = qeysFmt()
    do qx=1 to words(m.qeys)
        qq = word(m.qeys, qx)
        m.cCnt.qq = 0
        m.cAct.qq = 0
        end
    return
qeysIni

spaeter: procedure expose m.
parse arg mbr, ty, obj ':' pa
    if abbrev(ty, 'TAB') then do
        dsn = 'TS'
        src = obj
        end
    else do
        dsn = 'IX'
        ox = pos('.', obj)
        call sql2st qq,
            , "select strip(creator) ||'.'|| strip(name) o",
                  "from sysibm.sysindexes",
                  "where dbName = '"left(obj, ox-1)"'",
                      "and indexspace = '"substr(obj, ox+1)"'"
        if m.qq.0 <> 1 then
            call err 'index not found for' mbr ty obj':'pa
        src = m.qq.1.o
        end
    dsn = m.spPref || dsn || 'NEW('mbr')'
    m.spaeter = 'not in new' mbr ty obj':'pa src
    if m.sp <> dsn then do
        call readDsn dsn, m.sp.
        m.sp = dsn
        end
    do ix=1 to m.sp.0
        w = word(m.sp.ix, 2)
        if word(m.sp.ix, 2) ^== src then
            iterate
        if word(m.sp.ix, 3) ^=   pa then
            iterate
        m.spaeter = strip(m.sp.ix)
        if word(m.sp.ix, 1) = 'spaeter' then
            return 1
        end
    return 0
endProcedure spaeter
ext: procedure expose m.
parse arg m, fun, dsn
    ty = m.type
    call readDsn dsn, x.
    do x=1 to x.0
        if word(x.x, 1) ^== 'INCLUDE' then
            iterate
        if ty == '' then
            ty = word(x.x, 2)
        else if ty ^== word(x.x, 2) then
            call err 'type change from' ty 'to' word(x.x, 2) ,
                      'in line' x x.x 'of' dsn
        obj = word(x.x, 3)
        pa = word(x.x, 4)
        if pa = '' then
            pa = 0
        else if ^ abbrev(pa, 'PARTLEVEL(') then
            call err 'bad part' pa 'in line' x x.x 'of' dsn
        else
            pa = substr(pa, 11, length(pa) - 11)+0
        obj = obj':'pa
        if ^ mapHasKey(m, obj) then
            call mapAdd m, obj, fun
        else if wordPos(mapGet(m, obj), '=' fun) > 0 then
            call err 'duplicate' fun obj 'old' mapGet(m, obj) dsn
        else
            call mapPut m, obj, '='
        end
    m.type = ty
    return
endProcedure ext

selRts: procedure expose m.
parse arg type, db'.'sp':'pa
    if type = 'INDEXSPACE' then
         s = "select r.*" ,
                 "from sysIbm.indexSpaceStats r",
                     "join sysIbm.sysIndexes i",
                         "ON      r.DBID          = i.DBID",
                             "AND r.ISOBID        = i.ISOBID",
                             "AND r.DBNAME        = i.DBName",
                             "AND r.indexSpace    = i.indexSpace",
             "where i.dbName = '"db"' and i.indexSpace = '"sp"'"
    else if type = 'TABLESPACE' then
         s = "select * from sysIbm.tableSpaceStats r",
                     "join sysIbm.sysTableSpace s",
                        "ON    r.DBID          = S.DBID" ,
                          "AND r.PSID          = S.PSID" ,
                          "AND r.DBNAME        = S.DBNAME",
                          "AND r.NAME          = S.NAME" ,
             "where s.dbName = '"db"' and s.name = '"sp"'"
    else
        call err 'bad type' type
    call sql2st r, s 'and partition =' pa , '*type'type
    return
endProcedure selRts

countNew: procedure expose m.
parse arg pds
    ii = lmmBegin(pds)
    mbr = lmmNext(ii)
    tot = 0
    reo = 0
    day = 0
    do while mbr <> ''
        call readDsn pds'('mbr')', i.
        do x=1 to min(i.0, 20)
            i.x = substr(i.x, 2)
            if wordPos('activePgByte', i.x) < 1 then
                iterate
            tot = tot + Word(i.x, words(i.x))
            end
        do x=i.0 by -1 to max(i.0-20, 1)
            i.x = substr(i.x, 2)
            if wordPos('reorganisiere', i.x) < 1 then
                iterate
            if words(i.x) ^= 7 & word(i.x, 7) ^= 'TagesLimite' then
                call err 'bad limite' mbr x i.x
            reo = reo + Word(i.x, 2)
            day = day + word(i.x, 5)
            leave
            end
        mbr = lmmNext(ii)
        end
    call lmmEnd ii
    say 'total' pds
    say '  tot' tot 'reo' reo 'day' day
    return
endProcedure cmpPds

/* copy adrIsp begin *************************************************/
/**********************************************************************
    lmd: catalog read
    call sequence: lmdBegin, lmdNext*, lmdEnd
        mit lmd service (mit save in file und read,
                         weil list zu langsam und listcat abstürzt)
        1. arg (grp)     als group dataset für lmd save
                         und dd name für file read
***********************************************************************/
lmdBegin: procedure
    parse arg grp, lev
    call adrIsp 'lmdinit listid(lmdId) level('lev')'
    res = adrIsp('lmdlist listid(&lmdId) option(save) group('grp')', 4)
    call adrIsp 'lmdfree listid(&lmdId)'
    if res = 0 then do
        call trc timing() 'lmdlist save' grp lev
        call adrTso 'alloc dd('grp') shr dsn('grp'.datasets)'
        end
    else do
        call trc 'no datasets found' timing() 'lmdlist save' grp lev
        call adrTso 'alloc dd('grp') dummy'
        end
    call readDDBegin grp
return /* end lmdBegin */

lmdNext:
    parse arg ggGrp, ggSt, withVolume
    if ^ readDD(ggGrp, ggSt) then
         return 0
    if withVolume ^== 1 then
        do ggIx=1 to value(ggSt'0')
            x = value(ggSt || ggIx, word(value(ggSt || ggIx), 1))
            end
    return 1
endSubroutin lmdNext

lmdEnd: procedure
    parse arg grp
    call readDDEnd grp
    call adrTso 'free dd('grp')'
return /* end lmdEnd */

lmd: procedure expose m.
    parse arg lev, withVol
    call lmdBegin gg1, lev
    do while lmdNext(gg1, q., withVol)
        do x=1 to q.0
           call jOut q.x
           end
        end
    call lmdEnd gg1
    return
endProcedure lmd
/**********************************************************************
    member list of a pds:
        call sequence x=lmmBegin(dsn) lmmNext(x) * lmmEnd(x)
***********************************************************************/
lmm: procedure expose m.
parse arg dsn
    id = lmmBegin(dsn)
    do ix=1 by 1
        m = lmmNext(id)
        if m = '' then
            leave
        call jOut m
        end
    call lmmEnd id
    return
endProcedure lmm

lmmBegin: procedure
parse arg dsn
    mbr = dsnGetMbr(dsn)
    pds = dsnSetMbr(dsn, )
    call adrIsp "LMINIT DATAID(lmmId) DATASET('"pds"') ENQ(SHRW)"
    call adrIsp "LMOPEN DATAID("lmmId") OPTION(INPUT) "
    res = lmmId
    if mbr <> '' then
        res = res 'pattern('mbr')'
    return res
endProcedure lmmBegin

lmmEnd: procedure
parse arg lmmId opt
    call adrIsp "LMMLIST DATAID("lmmId") option(free)", 8
    call adrIsp "LMCLOSE DATAID("lmmId")"
    call adrIsp "LMFREE DATAID("lmmId")"
    return
endProcedure lmmEnd

lmmNext: procedure
parse arg lmmId opt
    if adrIsp("LMMLIST DATAID("lmmid")" ,
               "OPTION(LIST) MEMBER(Mbr)" opt, 4 8) = 0 then
        return strip(mbr)
    else
        return ''
endProcedure lmmNext

lmmRm: procedure
parse arg dsn, mbrs
    mbrs = dsnGetMbr(dsn) mbrs
    pds = dsnSetMbr(dsn, )
    call adrIsp "LMINIT DATAID(lmmId) DATASET("pds") ENQ(SHRW)"
    call adrIsp "LMOPEN DATAID("lmmId") OPTION(OUTPUT) "
    err = ''
    do wx=1 to words(mbrs)
        m1 = word(mbrs, wx)
        rr = adrIsp("lmmDel dataid("lmmId") member("m1")", 0 8 12)
        if rc = 0 then
            say 'removed' m1 'from' pds
        else if rc = 8 then
            say 'not found' m1 'in' pds
        else do
            err = 'error deleting' m1 'in' pds 'rc' rr strip(zerrlm)
            say err
            leave
            end
        end
    call adrIsp "LMCLOSE DATAID("lmmId")"
    call adrIsp "LMFREE DATAID("lmmId")"
    if err <> '' then
        call err err
    return
endProcedure lmmRm
/*--- address ispf with error checking -------------------------------*/
adrIsp:
    parse arg ggIspCmd, ggRet
    address ispexec ggIspCmd
    if rc = 0 then return 0
    else if ggRet == '*' then return rc
    else if wordPOS(rc, ggRet) > 0 then return rc
    else
        call err 'adr ispExec rc' rc 'in' ggIspCmd':' strip(zerrlm)
endSubroutine adrIsp

/*--- address editor with error checking -----------------------------*/
adrEdit:
    parse arg ggEditCmd, ggRet
    address isrEdit ggEditCmd
    if rc = 0 then return 0
    else if ggRet == '*' then return rc
    else if wordPOS(rc, ggRet) > 0 then return rc
    else
        call err 'adr isrEdit rc' rc 'for' ggEditCmd
endSubroutine adrEdit
/* copy adrIsp end   *************************************************/
/* copy sql    begin ***************************************************
    sql interface
        sqlIni --> nur sql ohne o und j Anbindung
        sqlOini -->    sql mit  o und j Anbindung
***********************************************************************/
sqlIni: procedure expose m.
    if m.sql.ini == 1 then
        return
    m.sql.ini = 1
    call oFldIni
    m.sql.null = '---'
    return
endProcedure sqlIni

sql2St: procedure expose m.
parse arg st, src, ty, fmt
    cx = 49
    call sql2Cursor cx, src, ty, fmt
    call sqlOpen cx
    do ix=1 by 1 while sqlFetch(cx, st'.'ix)
        end
    m.st.0 = ix-1
    call sqlClose cx
    return ix-1
endProcedure sql2St

sqlPrepare: procedure expose m.
parse arg cx, src, desc
     call sqlExec 'prepare s'cx 'from :src'
     if desc == 1 | (desc == '' & pos('?', src) > 0) then
         call sqlExec 'describe input s'cx 'into :M.SQL.'cx'.I'
     else
         m.sql.cx.i.sqlD = 0
     return
endProcedure

sqlExecute: procedure expose m.
parse arg cx
    do ix=1 to arg()-1
        val = arg(ix+1)
        if val ^== m.sql.null then do
             m.sql.cx.i.ix.sqlInd = 0
             m.sql.cx.i.ix.sqlData = val
             end
        else do
             m.sql.cx.i.ix.sqlInd = -1
             end
        end
     if ^ m.noInsert then /* ??? wk test */
     call sqlExec 'execute s'cx 'using descriptor :M.SQL.'cx'.I'
     return
endProcedure

sqlExImm: procedure expose m.
parse arg src
     call sqlExec 'execute immediate :src'
     return
endProcedure sqlExImm

sql2Cursor: procedure expose m.
parse arg cx, src, ty, fo
     call sqlExec 'prepare s'cx 'into :M.SQL.'cx'.D from :src'
     call sqlExec 'declare c'cx 'cursor for s'cx
     if ty ^= '*' & abbrev(ty, '*') then
         if oIsCla(substr(ty, 2)) then
             ty = substr(ty, 2)
     if abbrev(ty, '*') | ty = '' then do
         flds = 'SQL.'cx'.FLD'
         ff = ''
         do ix=1 to m.sql.cx.d.sqlD
             f1 = word(m.sql.cx.d.ix.sqlName, 1)
             if f1 == '' then
                 f1 = 'COL'ix
             ff = ff f1
             end
         if length(ty) > 1 then
             ty = oFldOnly(ff, 'e', substr(ty, 2))
         else
             ty = oFldOnly(ff, 'e')
         end
     call oSetTypePara 'SQL.TY.'cx, ty, 'noCall'
     flds = oFlds(ty)
     m.Sql.cx.FMT.0 = 0
     m.Sql.cx.FMT = sqlGenFmt('SQL.'cx'.FMT', cx, fo)
     ff = m.Sql.cx.FMT
     call assert 'm.'m.sql.cx.fmt'.0 >= m.sql.cx.d.sqlD', 'too few fmt'
     return
endProcedure sql2Cursor

sqlType: procedure expose m.
parse arg cx
    return oGetTypePara('SQL.TY.'cx)

sqlOpen: procedure expose m.
parse arg cx
     return sqlExec('open c'cx)

sqlClose: procedure expose m.
parse arg cx, src
     return sqlExec('close c'cx)

sqlFetchInto:
parse arg ggCx, ggVars
    return sqlExec('fetch c'ggCx 'into' ggVars, 0 100) <> 100

sqlFetch: procedure expose m.
parse arg cx, dst
    if ^ sqlFetchInto(cx, 'descriptor :M.SQL.'cx'.D') then
        return 0
    if dst == '' then
        return 1
    fi = oFlds(sqlType(cx))
    fo = m.sql.cx.fmt
    do ix=1 to m.sql.cx.d.SQLD
        f = m.fi.ix
        if m.sql.cx.d.ix.sqlInd = 0 then
            m.dst.f = fmt(m.sql.cx.d.ix.sqlData, m.fo.ix)
        else
            m.dst.f = fmtS(m.sql.null, m.fo.ix)
        end
    return 1
endProcedure sqlFetch

sqlFetchLn: procedure expose m.
parse arg cx, var
    st = 'SQL.'cx'.FET'
    if ^ sqlFetch(cx, st) then
        return 0
    m.var = oFldCat(sqlType(cx), st)
    return 1
endProcedure sqlFetchLn

sqlGenFmt: procedure expose m.
parse arg ff, cx, sp
    if abbrev(sp, '=') then
        return substr(sp, 2)
    if sp = '' then
        sp = '*st'
    m.ff.0 = m.sql.cx.d.sqlD
    m.ff.flds = oFlds(sqlType(cx))
    if abbrev(sp, '*') then do
        do ix=1 to m.ff.0
            m.ff.ix = substr(sp, 2)
            end
        return ff
        end
    if abbrev(fmts, '=') then
        m.Sql.cx.FMT = substr(fmts, 2)
    defs = 'ir7 fr9 sl12 Tl26' sp
    do wx = 1 to words(defs)
        parse value word(defs, wx) with ty 2 fo
        select
            when ty = 'd' then      t.384 = fo
            when ty = 'f' then      t.480 = fo'/f'
            when ty = 'i' then      t.496 = fo'/i'
            when ty = 'n' then      t.484 = fo'/n'
            when ty = 's' then      t.448 = fo
            when ty = 't' then      t.388 = fo
            when ty = 'T' then      t.392 = fo
            otherwise          call err 'bad type' ty 'for format' fo
            end
        end
    if symbol('t.496') == 'VAR' then
        t.500 = t.496
    if symbol('t.448') == 'VAR' then do
        t.452 = t.448
        t.456 = t.448
        t.464 = t.448
        end
    do wx = 1 to m.ff.0
        ty = m.sql.cx.d.wx.sqlType
        le = m.sql.cx.d.wx.sqlLen
        withNulls = ty // 2
        ty = ty - withNulls
        if symbol('t.ty') <> 'VAR' then
            call err 'sqlType' ty 'not supported'
        parse var t.ty fo 2 fl '/' op
        if op = 'i' then
             if le = 2 then le = 6
             else           le = 12
        else if op <> '' then
            call err 'length for sqlType' ty 'op' op 'not implemented'
        if fl = '=' then
            fl = le
        else if abbrev(fl, '<') then
            fl = min(le, substr(fl, 2))
        m.ff.wx = fo || fl
        end
    return ff
endProcedure sqlGenFmt
/*--- execute sql thru the dsnRexx interface -------------------------*/
sqlExec: /* no procedure, to keep variables sql... */
    parse arg ggSqlStmt, ggRet, ggNo
    if ggNo <> '1' then
        ggSqlStmt = 'execSql' ggSqlStmt
    address dsnRexx ggSqlStmt
    /* say 'sqlCode' sqlCode 'rc' rc 'for' ggSqlStmt ggNo */
    if rc = 0 then
        return 0
    else if ggRet = '*' | wordPos(sqlCode, ggRet) > 0 then
        return sqlCode
    else if rc < 0 then
        call err sqlmsg()
    else if sqlWarn.0 ^== ' ' | sqlCode <> 0 then
        call errSay sqlMsg(), ,'w'
    return sqlCode
endSubroutine sqlExec

sqlConnect: procedure
parse arg ggSys, ggRetCon
    if adrTSO("SUBCOM DSNREXX", '*') <> 0 then do
       sRc = RXSUBCOM('ADD','DSNREXX','DSNREXX') /* ADD HOST CMD ENV  */
       if sRc <> 0 then
           call err 'rc' sRc 'from RXSUBCOM(ADD, DSNREXX, DSNREXX)'
       end
    call sqlExec "connect" ggSys, ggRetCon ,1
    return
endProcedure sqlConnect

sqlDisconnect: procedure
parse arg ggRet
    call sqlExec "disconnect ", ggRet, 1
    return
endProcedure sqlDisconnect

sqlMsg: /* no procedure, to keep variables sql... */
    ggRes = 'sqlCode' sqlCodeText(sqlCode, sqlErrMc),
          '\nstate' sqlState 'warn'
    do ggX=0 to 10
        if sqlWarn.ggx <> '' then
            ggRes = ggRes ggx'='sqlWarn.ggx
        end
    ggRes = ggRes'\nstmt = ' ggSqlStmt
    ggPref = '\nwith\n '
    ggXX = pos(':', ggSqlStmt)+1
    do 12 while ggXX > 1
        ggYY = verify(ggSqlStmt, ' ,:+-*/&%?|()[]', 'm', ggXX)
        if ggYY < 1 then
            ggYY = length(ggSqlStmt) + 1
        ggVar = substr(ggSqlStmt, ggXX, ggYY - ggXX)
        if ggVar <> '' then do
            ggRes = ggRes || ggPref ggVar '=' value(ggVar)
            ggPref = '\n '
            end
        ggXX = pos(':', ggSqlStmt, ggYY+1) + 1
        end
    return  ggRes
endSubroutine sqlMsg

sqlCodeText: procedure expose m.
parse arg co, mc
    expEq = 0
    if symbol('m.sql.code.0') <> 'VAR' then do
        dsn = "'A540769.wk.texv(sql)'"
        dsn = "'ORG.U0009.B0106.KIUT23.TEXV(SQLCODES)'"
        m.sql.code.0 = 0
        if sysDsn(dsn) <> 'OK' then
            say 'sqlCode dsn' dsn':' sysDsn(dsn)
        else
            call readDsn dsn, 'M.SQL.CODE.'
        end
    co = co + 0
    if length(co) < 3 then
        co = left(co, 3, 0)
    if co > 0 then
        co = '+'co
    co = co' '
    do cx=1 to m.sql.code.0 until abbrev(m.sql.code.cx, co)
        end
    if cx > m.sql.code.0 then
        li = "<<text for sqlCode" co "not found>>"
    else
        li = m.sql.code.cx
    cx = 1
    px = 1
    res = ''
    do forever
        nx = pos('${', li, cx)
        if nx < 1 then
            leave
        ex = pos('}', li,  nx)
        if ex < cx then
            call err 'closing } missing in' li
        res = res || substr(li, cx, nx - cx)
        if expEq then
            res = res || '<<' || substr(li, nx+2, ex-nx-2) || ' = '
        cx = ex+1
        if px > length(mc) then do
            res = res || '<<missing>>'
            end
        else do
            qx = pos('FF'x, mc, px)
            if qx < 1 then
                qx = length(mc)+1
            res = res || substr(mc, px, qx-px)
            if expEq then
                res = res'>>'
            px = qx + 1
            end
        end
    res = res || substr(li, cx)
    do while px <= length(mc)
        qx = pos('FF'x, mc, px)
        if qx < 1 then
            qx = length(mc)+1
        res = res  '<<extraMsgArg =' substr(mc, px, qx-px)'>>'
        px = qx + 1
        end
    return res
endProcedure sqlCodeText

/*--- send a command to db2 through the TSO dsn processor ------------*/
sqlDsn: procedure
parse arg sys, cmd, rcOk
    call adrTso 'alloc dd(sysprint) new reuse'
    push 'END'
    push cmd
    rr = adrTso('DSN SYSTEM('sys')', '*')
    say 'rc' rr 'adrTso DSN SYSTEM('sys') cmd' cmd
    if wordPos(rr, rcOk) < 1 then do
        say 'error rc' rr 'in adrTso DSN SYSTEM('sys') cmd' cmd

        call adrTso 'execio * diskr sysprint (finis stem pr.)'
        say 'sysprint' pr.0 'lines'
        do x=1 to pr.0
            say strip(pr.x, 't')
            end
        call adrTso 'free dd(sysprint) '
        call err 'rc' rr 'in adrTso DSN SYSTEM('sys') cmd' cmd
        end
    call adrTso 'free dd(sysprint)'
    return rr
endProcedure sqlDsn

/*--- sql o interface ------------------------------------------------*/
sql2Obj: procedure expose m.
parse arg cx, src, ty, fo
    if ty = '' then
        ty = '*'
    if src == '' then
        src = envCatStr(' ', 'sb')
    call sql2Cursor cx, src, ty, substr(fo, 1+abbrev(fo, '~'))
    call oMutate 'SQL.TY.'cx, 'SqlType'
    return 'SQL.TY.'cx
endProcedure sql2obj

sql: procedure expose m.
parse arg cx, src, ty, fo
    call jWriteAll m.j.jOut, "r£", sql2Obj(cx, src, ty, fo)
    return
endProcedure sql

sqlLn: procedure expose m.
parse arg cx, src, ty, fo
    if fo = '' then
        fo = '~'
    squash = abbrev(fo, '~')
    if ^ abbrev(fo, '=') then
        fo = left(fo, squash) 'sl=' substr(fo, squash+1)
    t = sql2Obj(cx, src, ty, fo)
    if squash then do
        call fmtFldSquashRw t, 'opCl'
        return
        end
    m = 'SQL.LN.'cx
    call oMutate m, 'SqlLn'
    call jWrite m.j.jOut, fmtFldTitle(m.sql.cx.fmt)
    call jWriteAll m.j.jOut, "r£", m
    return
endProcedure sqlLn

sqlOini: procedure expose m.
    if m.sqlO.ini == 1 then
        return
    m.sqlO.ini = 1
    call sqlIni
    call envIni
    call oDecMethods oNewClass("SqlType"),
        , "jOpen  call sqlOpen substr(m, 8)",
        , "jClose call sqlClose substr(m, 8)",
        , "jRead  return sqlFetch(substr(m, 8), var)"
    call oDecMethods oNewClass("SqlLn", "SqlType"),
        , "jRead  return sqlFetchLn(substr(m, 8), var)"
    return
endProcedure sqlOini
/* copy sql    end   **************************************************/
/* copy oFld begin ****************************************************/
oFldIni: procedure expose m.
    if m.oFld.ini = 1 then
        return
    m.oFld.ini = 1
    call mIni
    m.o.cla.0 = 0
    call oFldNew 'Class', '=', , ,
              'INST = MET .<>= FLD .<>=Class STEM =Class'
    return
endProcedure oFldIni

oFldNew: procedure expose m.
parse arg name, va, st, flds, dup
     if pos('*', name) > 0 then
         name = oPut('O.CLA', name, , '*')
     else
         call oPut 'O.CLA', name, , 'n'
     m.o.cla.name.inst = 0
     m.o.cla.name.val  = va
     m.o.cla.name.stem = st
     m.o.cla.name.FLD.0 = 0
     m.o.cla.name.MET.0 = 0
     return oFldAdd(name, flds, dup)
endProcedure oFldNew

oIsCla: procedure expose m.
parse arg nm
    return symbol('m.o.cla.nm') == 'VAR'

oFldOnly: procedure expose m.
parse arg fs, dup, nm
    if nm <> '' then do
        nn = oFldNew(nm)
        end
    else do
        kk = space(fs, 1) dup
        if symbol('m.o.fldOnly.kk') = 'VAR' then
            return m.o.fldOnly.kk
        nn = oFldNew('FldType*')
        end
    st = 'O.CLA.'nn'.FLD'
    ll = ''
    do wx=1 to words(fs)
        ll = ll oPut(st, word(fs, wx), '=', dup)
        end
    if nm = '' then do
        m.o.fldOnly.kk = nn
        m.o.fldOnly.ll = nn
        end
    return nn
endProcedure oFldOnly

oFlds: procedure expose m.
parse arg type
    return 'O.CLA.'type'.FLD'

oFldCat: procedure expose m.
parse arg ty, st, fmt
    flds = oFlds(ty)
    res = ''
    do ix=1 to m.flds.0
        f = m.flds.ix
        if fmt == '' then
            res = res m.st.f
        else
            res = res fmt(m.st.f, m.fmt.ix)
        end
    return substr(res, 2)
endProcedure oFldCat

oFldAdd: procedure expose m.
parse arg cla, fs, dup
    st = 'O.CLA.'cla'.FLD'
    do ix=1 by 2 to words(fs)
         call oPut st, word(fs, ix), word(fs, ix+1), dup
         end
    return cla
endProcedure oFldAdd

oPut: procedure expose m.
parse arg st, k, v, dup
    if dup = '*' then do
        cx = pos('*', k)
        if cx < 1 then
            call err 'no * in key' k
        k = oPutDupResolve(st, left(k, cx-1), 1)
        end
    else if symbol('m.st.k') = 'VAR' then do
        if dup = '' | dup = 'o' then do
            m.st.k = v
            return k
            end
        if dup = '=' then do
            if v <> m.st.k | ^ (v = '' & datatype(m.st.k, 'n')) then
                call err 'not =: m.'st'.'k '=>' m.st.k '<>' v
            return k
            end
        if dup = 'e' then
            k = oPutDupResolve(st, k, 2)
        else
            call err 'key' k 'exists in' st 'but dup' dup
        end
    else if dup = 'o' then
        call err 'old key' k 'does not exist in' st
    if datatype(k, 'n') | words(k) <> 1 | pos('.', k) > 0 then
        call err 'illegal name' name
    x = m.st.0 + 1
    m.st.0 = x
    m.st.x = k
    if v == '' then
        m.st.k = x
    else
        m.st.k = v
    return k
endProcedure oPut

oPutDupResolve: procedure expose m.
parse arg st, k, b
     do ix=b
         a = k || ix
         if symbol('m.st.a') <> 'VAR' then
             return a
         end
endProcedure oPutDupResolve

/*--- parameterized types --------------------------------------------*/
oGetTypePara: procedure expose m.
parse arg m
    if symbol('m.o.tyPa.m') == 'VAR' then
        return m.o.tyPa.m
    else
        return '='
endProcedure oGetTypePara

oSetTypePara: procedure expose m.
parse arg m, type, noCall
    if type = '' then
        type = '='
    if oGetTypePara(m) == type then
        return
    if noCall ^== 'noCall' then
        interpret oObjMethod(m, 'oSetTypePara')
    m.o.tyPa.m = type
    return
endProcedure oSetTypePara
/* copy oFld end   ****************************************************/
/* copy fmt    begin **************************************************/
fmt: procedure
parse arg v, f 2 l
    if abbrev('-', f) then
        return v
    else if f = 'l' then
        return left(v, l)
    else if f = 'r' then
        return right(v, l)
    else if f = 's' then
        if l = '' then
            return strip(v, 't')
        else
            return strip(v, l)
    else if f = 'w' then do
        parse var l st ',' le
        return substr(v, st, le)
        end
    else
        call err 'bad format' f l 'for' v
endProcedure fmt

/*--- format special (title, null Value also for numbers) ------------*/
fmtS: procedure expose m.
parse arg v, f
    return fmt(v, f)
endProcedure fmtS   $
/* copy fmt    end   **************************************************/
/* copy adrTso begin *************************************************/
/*--- send ggTsoCmd to tso, fail if rc <> 0 or not listed in ggRet ---*/
adrTso:
    parse arg ggTsoCmd, ggRet
    address tso ggTsoCmd
    if rc == 0                     then return 0
    else if ggRet == '*'           then return rc
    else if wordPos(rc, ggRet) > 0 then return rc
    else
        call err 'adrTso rc' rc 'for' ggTsoCmd
return /* end adrTso */

/*--- format dsn from tso format to jcl format -----------------------*/
dsn2jcl: procedure
parse arg dsn ., addPrefix
    if left(dsn,1) = "'" then
        return strip(dsn, 'b', "'")
    sp = sysvar('SYSPREF')
    if sp == '' then
        sp = userid()
    cx = pos('~', dsn)
    if cx < 1 & addPrefix == 1 then
        return sp'.'dsn
    do while cx ^== 0
        le = left(dsn, cx-1)
        ri = substr(dsn, cx+1)
        if right(le, 1) == '.' | left(ri, 1) == '.' then
            dsn = le || sp || ri
        else
            dsn = le || left('.', le ^== '') || sp ,
                     || left('.', ri ^== '') || ri
        cx = pos('~', spec, cx)
        end
    return dsn
endProcedure dsn2Jcl

/*--- format dsn from jcl format to tso format -----------------------*/
jcl2dsn: procedure
parse arg dsn .
    return "'"dsn"'"
endProcedure jcl2dsn

dsnSetMbr: procedure
parse arg dsn, mbr
     bx = pos('(', dsn)
     if bx > 0 then
         dsn = strip(left(dsn, bx-1))
     if mbr <> '' then
         dsn = dsn'('strip(mbr)')'
     return dsn
endProcedure dsnSetMbr

dsnGetMbr: procedure
parse arg dsn
     lx = pos('(', dsn)
     rx = pos(')', dsn, lx+1)
     if lx < 1 then
         return ''
     else if lx < rx then
         return substr(dsn, lx+1, rx-lx-1)
     else
         return strip(substr(dsn,lx+1))
endProcedure dsnGetMbr
/**********************************************************************
    io: read or write a dataset with the following callsequences:
        read:  readDDBegin, readDD*,  readDDEnd
        write: writeBegin,  writeDD*, writeEnd

        readDD returns true if data read, false at eof
***********************************************************************/

/*--- prepare reading from a DD --------------------------------------*/
readDDBegin: procedure
return /* end readDDBegin */

/*--- read from DD ggDD into ggSt, return false at eof ---------------*/
readDD:
    parse arg ggDD, ggSt, ggCnt
    if ggCnt = '' then
        ggCnt = 100
    call adrTso 'execio' ggCnt 'diskr' ggDD '(stem' ggSt')', 2
    return (value(ggSt'0') > 0)
return /* end readDD */

/*--- finish reading DD  ggDD ----------------------------------------*/
readDDEnd: procedure
    parse arg ggDD
    call adrTso 'execio 0 diskr' ggDD '(finis)'
return /* end readDDEnd */

/*--- prepare writing to DD ggDD -------------------------------------*/
writeDDBegin: procedure
    parse arg ggDD
                  /* ensure file is erased, if no records are written */
    call adrTso 'execio' 0 'diskw' ggDD '(open)'
return /* end writeDDBegin */

/*--- write to gg ggDD from stem ggSt, ggCnt records -----------------*/
writeDD:
    parse arg ggDD, ggSt, ggCnt
    if ggCnt == '' then
        ggCnt = value(ggst'0')
    call adrTso 'execio' ggCnt 'diskw' ggDD '(stem' ggSt')'
    return
endSubroutine writeDD

/*--- end writing to dd ggDD (close) --------------------------------*/
writeDDEnd: procedure
    parse arg ggDD
    call adrTso 'execio 0 diskw' ggDD '(finis)'
return /* end writeDDEnd */

/*--- alloc a dsn or a dd -------------------------------------------*/
dsnAlloc: procedure expose m.
parse upper arg spec, disp, dd
    ds = ''
    m.dsnAlloc.dsn = ds
    if left(spec, 1) = '-' then
        return strip(substr(spec, 2))
    if left(spec, 1) = '&' then /* external spec is handled ok */
        spec = strip(substr(spec, 2))
    rest = ''
    do wx=1 by 1
        w = word(spec, wx)
        if w = '' then
            leave
        if abbrev(w, '.') then do
            rest = substr(subword(spec, wx),  2)
            leave
            end
        if abbrev(w, ':') then do
            nn = substr(subword(spec, wx),  2)
            leave
            end
        if wordPos(w, 'OLD SHR MOD NEW')>0 | abbrev(w, 'SYSO') then
            disp = w
        else if w = 'CATALOG' then
            disp = disp w
        else if abbrev(w, 'DD(') then
            dd = substr(w, 4, length(w)-4)
        else if abbrev(w, 'DSN(') then
            ds = strip(substr(w, 5, length(w)-5))
        else if ds = '' then
            ds = dsn2jcl(w)
        else
            leave
        end
    if dd <> '' & ds = '' & rest = '' & ^ abbrev(disp, 'SYSO') then
        return dd
    if dd = '' then do
        if symbol('m.adrTso.ddNum') = 'VAR' then
            dd = m.adrTso.ddNum + 1
        else
            dd = 1
        m.adrTso.ddNum = dd
        dd = 'DD' || dd
        end
    if disp = '' then
        disp = 'SHR'
    else if pos('(', ds) < 1 then
        nop
    else if disp = 'MOD' then
        call err 'disp mod for' ds
    else
        disp = 'SHR'
    m.dsnAlloc.dsn = ds
    if ds <> '' then
        ds = "DSN('"ds"')"
    alRc = adrTso('alloc dd('dd')' disp ds rest, '*')
    if alRc ^== 0 then do
        if nn = 'NN' | wordPos(disp, 'OLD SHR') < 1 ,
            | sysDsn("'"m.dsnAlloc.dsn"'") ^== 'DATASET NOT FOUND' then
          call err 'tsoAlloc rc' alRc 'for alloc dd('dd')' disp ds rest
        say 'tsoAlloc rc' alRc 'for alloc dd('dd')' disp ds rest ,
            '...trying to create'
        call dsnAllocCreate m.dsnAlloc.dsn, nn
        call adrTso 'alloc dd('dd')' disp ds rest
        end
    return dd 'call adrTso "free dd('dd')";'
endProcedure dsnAlloc

dsnAllocCreate: procedure expose m.
parse arg dsn, atts
    if abbrev(atts, ':') then do
        rl = substr(atts, 3)
        if abbrev(atts, ':F') then do
            if rl = '' then
                rl = 80
             atts = 'recfm(f b) lrecl('rl')' ,
                       'block(' (32760 - 32760 // rl)')'
            end
        else do
            if rl = '' then
                rl = 32756
            atts = 'recfm('substr(atts, 2, 1) 'b) lrecl('rl')' ,
                   'block(32760)'
            end
        if pos('(', dsn) > 0 then
            atts = atts 'dsntype(library) dsorg(po)' ,
                   "dsn('"dsnSetMbr(dsn)"')"
        else
            atts = atts "dsn('"dsn"')"
        atts = 'new catalog' atts 'mgmtclas(s005y000) space(10, 1000)'
        end
    call adrTso 'alloc dd(dsnAlloc)' atts
    call adrTso 'free  dd(dsnAlloc)'
    return
endProcedure dsnAllocCreate

readDSN:
parse arg ggDsnSpec, ggSt
    ggAlloc = dsnAlloc(ggDsnSpec, 'SHR', 'readDsN')
    call adrTso 'execio * diskr' word(ggAlloc, 1) '(stem' ggSt' finis)'
    interpret subword(ggAlloc, 2)
    return
endSubroutine readDsn

writeDSN:
parse arg ggDsnSpec, ggSt, ggCnt, ggSay
    if ggCnt == '' then
        ggCnt = value(ggst'0')
    ggAlloc = dsnAlloc(ggDsnSpec, 'OLD', 'readDsN')
    call adrTso 'execio' ggCnt 'diskw' word(ggAlloc, 1) ,
            '(stem' ggSt 'finis)'
    interpret subword(ggAlloc, 2)
    if ggSay == 1 | m.debug == 1 then
       say ggCnt 'records written to' ggDsnSpec
    return
endSubroutine writeDsn
/* copy adrTso end ****************************************************/
/* copy map begin ******************************************************
    a map stores values at keys
    it may also maintain a list of keys
***********************************************************************/
/*--- create a new map ----------------------------------------------*/
mapNew: procedure expose m.
parse arg opt
    return mapReset('MAP.'mInc('MAP.0') , opt)
endProcedure mapNew

/*--- make an empty map, opt=K means maintain a stem of keys ---------*/
mapReset: procedure expose m.
    parse arg a, opt
    if symbol('m.map.keys.a') == 'VAR' then
        call mapClear a
    upper opt
    if opt = '=' then
        st = a
    else if opt = 'K' then
        st = 'MAP.KEYS.'a
    else
        st = ''
    m.map.keys.a = st
    if st ^== '' then
        m.st.0 = 0
    return a
endProcedure

mapClear: procedure expose m.
parse arg a
    st = mapKeys(a)
    do kx=1 to m.st.0
        k = m.st.kx
        drop m.a.k m.st.kx
        end
    m.st.0 = 0
    return a
endProcedure mapClear

mapKeys: procedure expose m.
parse arg a
    if m.map.keys.a == '' then
        call err 'mapKeys('a') with no keys'
     return m.map.keys.a
endProcedure mapKeys

mapAdd: procedure expose m.
parse arg a, ky, val
    if symbol('m.a.ky') == 'VAR' then
        call err 'duplicate key in mAdd('a',' ky',' val')'
    m.a.ky = val
    if m.map.keys.a ^== '' then
        return mAdd(m.map.keys.a, ky)
    return
endProcedure mapAdd

mapPut: procedure expose m.
parse arg a, ky, val
    if m.map.keys.a ^== '' then
        if symbol('m.a.ky') ^== 'VAR' then
            call mAdd m.map.keys.a, ky
    m.a.ky = val
    return val
endProcedure mapPut

mapRemove: procedure expose m.
parse arg a, ky
    val = m.a.ky
    drop m.a.ky
    return val
endProcedure mapRemove

mapHasKey: procedure expose m.
parse arg a, ky
    return symbol('m.a.ky') == 'VAR'
endProcedure mapHasKey

mapGet: procedure expose m.
parse arg a, ky
    if symbol('m.a.ky') ^== 'VAR' then
        call err 'missing key in mapGet('a',' ky')'
    return m.a.ky
endProcedure mapGet

mapIni: procedure expose m.
    if m.map.ini = 1 then
        return
    m.map.ini = 1
    call mIni
    m.map.0 = 0
    return
endProcedure mapIni
/* copy map end *******************************************************/
/* copy m begin ********************************************************
    stem handling
***********************************************************************/
/*--- increase m.a and return it (fail if undefined) -----------------*/
mInc: procedure expose m.
    parse arg a
    m.a = m.a + 1
    return m.a
endProcedure mInc

/*--- cut stem a to length len ---------------------------------------*/
mCut: procedure expose m.
parse arg a, len
    m.a.0 = len
    return a
endProcedure mCut

/*--- add one or several arguments to stem m.a -----------------------*/
mAdd: procedure expose m.
parse arg a
    ix = m.a.0
    do ax = 2 to arg()
        ix = ix + 1
        m.a.ix = arg(ax)
        end
    m.a.0 = ix
    return a'.'ix
endProcedure mAdd

/*--- add to m.dst.* a (sub)sequence of m.src.* ----------------------*/
mAddSt: procedure expose m.
    parse arg dst, src
    dx = m.dst.0
    do sx = 1 to m.src.0
        dx = dx + 1
        m.dst.dx = m.src.sx
        end
    m.dst.0 = dx
    return
endProcedure mAddAt

/*--- strip all elements of a stem -----------------------------------*/
mStrip: procedure expose m.
parse arg st, opt
    if opt == '' then
        opt = 'b'
    do x=1 to m.st.0
        m.st.x = strip(m.st.x, opt)
        end
    return st
endProcedure mStrip

/*--- cat all elements of a stem together ----------------------------*/
mCat: procedure expose m.
parse arg st, mid
    if m.st.0 < 1 then
        return ''
    res = m.st.1
    do x=2 to m.st.0
        res = res || mid || m.st.x
        end
    return res
endProcedure mCat

mIni: procedure expose m.
    if m.m.ini = 1 then
        return
    m.m.ini = 1
    m.mAlfLC  = 'abcdefghijklmnopqrstuvwxyz'
    m.mAlfUC  = translate(m.mAlfLC)
    m.mAlfa   = m.mAlfLC || m.mAlfUC
    m.mAlfNum = m.mAlfa || '0123456789'
    m.mAlfDot = m.mAlfNum || '.'
    return
endProcedure mIni
/* copy m end *********************************************************/
/* copy err begin ******************************************************
    messages, errorhandling,help
***********************************************************************/
/* configure err -----------------------------------------------------*/
errReset: procedure expose m.
parse arg oo, ha
    if pos('I', translate(oo)) > 0 then
        call adrIsp 'control errors return'
    m.err.opt = translate(oo, 'h', 'H')
    if ha == '' then
        drop m.err.handler
    else
        m.err.handler = ha
    return
endSubroutine errReset

/*--- error routine: abend with message ------------------------------*/
err:
    parse arg ggTxt, ggOpt
    drop err handler opt
    if ggOpt == '' & symbol('m.err.handler') == 'VAR' then
        interpret m.err.handler
    call errSay ggTxt
    parse source . . ggS3 .                           /* current rexx */
    if ggOpt == '' | ggOpt == '*' then
        ggOpt = translate(value('m.err.opt'), 'ht', 'HT')
    if pos('h', ggOpt) > 0  then do
        say 'fatal error in' ggS3': divide by zero to show stackHistory'
        x = 1 / 0
        end
    say 'fatal error in' ggS3': exit(12)'
    exit errSetRc(12)
endSubroutine err

assert:
    interpret 'assertRes =' arg(1)
    if ^ assertRes then
        call err 'assert failed' arg(1)':' arg(2)
    return
endProcedure assert

errSay: procedure expose m.
parse arg msg, st, pref
    parse source . . ggS3 .                           /* current rexx */
    if pref == 'e' | (pref == '' & st == '') then
        msg = 'fatal error:' msg
    else if pref == 'w' then
        msgf = 'warning:' msg
    else if pref == 0 then
        nop
    else if right(pref, 1) ^== ' ' then
        msg = pref':' msg
    else
        msg = pref || msg
    sx = 0
    bx = -1
    do lx=1 until bx >= length(msg)
        ex = pos('\n', msg, bx+2)
        if ex < 1 then
            ex = length(msg)+1
        if st == '' then do
            say substr(msg, bx+2, ex-bx-2)
            end
        else do
            sx = sx+1
            m.st.sx = substr(msg, bx+2, ex-bx-2)
            m.st.0 = sx
            end
        bx = ex
        end
    return
endProcedure errSay

/*--- abend with Message after displaying help -----------------------*/
errHelp: procedure expose m.
parse arg msg, op
    say 'fatal error:' msg
    call help
    call err msg, op
endProcedure errHelp

/*--- set rc for ispf: -------------------------------------------------
    if a cmd is run by ispStart, its RC is ignored,
         but ISPF passes the value of the shared varible 3IspfRc
         back as return code
----------------------------------------------------------------------*/
errSetRc: procedure
parse arg zIspfRc
    if sysVar('sysISPF') = 'ACTIVE' then do
        address ispExec vput 'zIspfRc' shared
        end
    return zIspfRc
endProcedure errSetRc

/*--- output a trace message if m.trace is set -----------------------*/
trc: procedure expose m.
parse arg msg
    if m.trace == 1 then
        say 'trc:' msg
    return
endProcedure trc

/*--- quote string txt using quoteChar qu ("""" ==> ") ---------------*/
quote: procedure
parse arg txt, qu
    if qu = '' then
        qu = '"'
    res = qu
    ix = 1
    do forever
        qx = pos(qu, txt, ix)
        if qx = 0 then
            return res || substr(txt, ix) || qu
        res = res || substr(txt, ix, qx-ix) || qu || qu
        ix = qx + length(qu)
        end
endProcedure quote

debug: procedure expose m.
parse arg msg
    if m.debug == 1 then
        say 'debug' msg
    return
endProcedure debug

/*--- return current time and cpu usage ------------------------------*/
timing: procedure
return time() time('E') sysvar('syscpu') /* sysvar('syssrv') */

/--- display the first comment block of the source as help -----------*/
help: procedure
    parse source . . s3 .
    say right(' help for rexx' s3, 79, '*')
    do lx=1 by 1
        if pos('/*', sourceLine(lx)) > 0 then
            leave
        else if lx > 10 then do
            say 'initial commentblock not found for help'
            return
            end
        end
    do lx=lx+1 by 1
        li = strip(sourceLine(lx), 't', ' ')
        if pos('*/', li) > 0 then
            leave
        say li
        end
    say right(' end help for rexx' s3, 79, '*')
    return 4
endProcedure help
/* copy err end   *****************************************************/