zOs/REXX/CONSUM

/* rexx ****************************************************************
     control summary summary
         write one line for each control summary
         optionally delete empty members
***********************************************************************/
dsnMsk = 'A540769.TMP.RZ2.**.CONSUM'
dsnMsk = 'DSN.ABUB.TECSV.RZZ'
libOut = 'A540769.TMP.TEXV'
libOut = 'DSN.ABUB.TECSV.CONSUM'

call errReset 'h'
call csiOpen dsl, dsnMsk
oldPd = ''
do while csiNext(dsl, dsl)
   parse value substr(m.dsl, 15, 18) ,
         with '.' rz '.' dbSys '.' . '.' dt '.'
   if dt << 'D1404' then do
       say 'too old, skipping' m.dsl
       iterate
       end
   pd = iirz2P(rz)iidbSys2c(dbSys)substr(dt, 2, 2)
   if pd \== oldPd then do
       if oldPd \== '' then
           call writeDsn pdSum, m.su., ,1
       oldPd = pd
       pdSum = libOut'('pd')'
       if dsnExists(pdSum) then
           call readDsn pdSum, m.su.
       else
           m.su.0 = 0
       end
   call oneLib m.dsl
   end
   if oldPd \== '' then
       call writeDsn pdSum, m.su., ,1
exit

oneLib: procedure expose m.
parse arg lib
    m.nn.0 = 0
    sx = 1
    matchOld = 0
    do mx=mbrList(mbl, lib) by -1
        dsn = lib'('m.mbl.mx')'
        say mx dsn
        call readDsn dsn, m.ii.
        if m.ii.0 = 0 then do
            call adrTso "delete '"dsn"'"
            say 'deleted empty' dsn
            end
        else do
            if subword(m.ii.1, 2, 2) <> 'Control Summary' then
                call err 'bad line' dsn'.1:' m.ii.1
            res = word(m.ii.1, 1) word(m.ii.1, 4)
            do y = 5 to min(25, m.ii.0) ,
                       while word(m.ii.y, 2) <> 'sqls,'
                end
            if word(m.ii.y, 2) <> 'sqls,' ,
                | word(m.ii.y, 8) <> 'errors' then
                call err 'bad sqls line' dsn'.'y':' m.ii.y
            res = res left(strip(m.ii.y), 49)
            y = y + 1
            if word(m.ii.y, 5) <> 'SOX' ,
               &  word(m.ii.y, 5) <> 'Recoverability/sx' then
                call err 'bad SOX line' dsn'.'y':' m.ii.y
            res = res left(strip(m.ii.y), 49)
            y = y + 1
            if word(m.ii.y, 5) <> 'Recoverability' then
                call err 'bad Recoverability line' dsn'.'y':' m.ii.y
            res = res left(strip(m.ii.y), 49)
            y = y + 1
            if word(m.ii.y, 5) <> 'other' then
                call err 'bad other line' dsn'.'y':' m.ii.y
            res = res left(strip(m.ii.y), 49)
            y = y + 1
            note = copies(m.ii.y,
                   , abbrev(translate(word(m.ii.y, 1)), 'NOTE'))
            rCo = res
            res = res left(strip(note), 72) dsn m.ii.0 'lines'
            call mAdd nn, res
            if m.su.0 = 0 then
                nop
            else if sx = 1 & word(res, 1) == word(m.su.1, 1) ,
                           & word(res, 2) >> word(m.su.1, 2) then
                nop
            else if abbrev(m.su.sx, rCo) & pos(dsn, m.su.sx) > 0 then
                sx = sx+1
            else do
                if sx <> 1 | m.nn.0 <> 1 then
                    call err 'result of' dsn '=' res ,
                        '\n <> old result' m.su.sx
                do sx=1 to m.su.0 ,
                       while word(res, 1) == word(m.su.sx, 1) ,
                           & word(res, 2) << word(m.su.sx, 2)
                    end
                if \abbrev(m.su.sx, rCo) | pos(dsn, m.su.sx) < 1 then
                    call err 'result of' dsn '=' res ,
                        '\n not found in old result' sx m.su.sx
                sx = sx+1
                matchOld = 1
                end
            end
        if mx <= 1 | ( sx > 7 & \ matchOld) then do
            if matchOld then do
                say 'matchOld ok' dsn
                return
                end
            call mMove su, sx, m.nn.0+1
            do nx=1 to m.nn.0
                m.su.nx = m.nn.nx
                end
            return
            end
        end
endProcedure oneLib
/* copy m begin ********************************************************
  we use variables as follows
    m. stem m: all global data and object data that must survive
               a procedure call  (m for memory in Memoria of B5000)
    m.<mbr>.** and m.<mbr>_**: every rexx Module (copy) should only
               allocate these addresses to avoid address conficts
               with <mbr> the name of therexx module
        we pass parameters around (e.g. a=address, m=memory, st=stem)
            and the called function may use m.a or m.a.subField etc.
    gg*: local variable in subroutines without procedure
    everything else: temporary data within procedure

  every subroutine is declared as procedure expose m.
        (also if no m. variable is used, because e.g. of error handling)
  the few subroutines that cannot use procedure, should use only
        variables starting with gg
***********************************************************************/
/*---make an area -----*/

mNewArea: procedure expose m.
parse arg nm, adr
    ax = m.m_area.0 + 1
    m.m_area.0 = ax
    m.m_area.ax = nm
    if adr == '=' then
        adr = nm
    else if adr == '' then
        adr = 'M.'ax
    if symbol('m.m_2a.nm') == 'VAR' then
        call err 'area name' nm 'already used'
    if symbol('m.m_2a.adr') == 'VAR' then
        call err 'adr' adr 'for area' nm 'already used'
    m.m_2a.adr = adr
    m.m_2a.nm  = adr
    m.adr.0 = 0
    m.m_free.adr.0 = 0
    return nm
endProcedure mNewArea

mNew: procedure expose m. ggArea
parse arg name
    if symbol('m.m_2a.name') \== 'VAR' then
        call err 'area' name 'does not exists'
    adr = m.m_2a.name
    if m.m_free.adr.0 > 0 then do
        fx = m.m_free.adr.0
        m.m_free.adr.0 = fx-1
        return m.m_free.adr.fx
        end
    m.adr.0 = m.adr.0 + 1
    return adr'.'m.adr.0
endProcedure mNew

mFree: procedure expose m.
parse arg m
    adr = left(m, lastPos('.', m)-1)
    fx = m.m_free.adr.0 + 1
    m.m_free.adr.0  = fx
    m.m_free.adr.fx = m
    return ''
endProcedure mFree
/*--- iterate over all allocate elements of an area ------------------*/
mIterBegin: procedure expose m.
parse arg nm
    return m.m_2a.nm'.0'
endProcedure mIterBegin

mIter: procedure expose m.
parse arg cur
    if cur == '' then
        return ''
    lx = lastPos('.', cur)
    adr = left(cur, lx-1)
    ix = substr(cur, lx+1)
    do ix=ix+1 to m.adr.0
        n = adr'.'ix
        do fx=1 to m.m_free.adr.0 while m.m_free.adr.fx \== n
            end
        if fx > m.m_free.adr.0 then
            return n
        end
    return ''
endProcedure mIter

/*--- get m.a --------------------------------------------------------*/
mGet: procedure expose m.
parse arg a
    return m.a
endProcedure mGet

/*--- put value v into m.a -------------------------------------------*/
mPut: procedure expose m.
parse arg a, v
    m.a = v
    return v
endProcedure mPut

/*--- 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

/*--- pop last element from stem m.a ---------------------------------*/
mPop: procedure expose m.
parse arg a
    ix = m.a.0
    if ix < 1 then
        call err 'pop from empty stem' a
    m.a.0 = ix-1
    return m.a.ix
endProcedure mPop

/*--- create the inverse map of a stem -------------------------------*/
mInverse: procedure expose m.
parse arg a, i
    do x=1 to m.a.0
        v = m.a.x
        m.i.v = x
        end
    return m.a.0
endProcedure inverse

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

/*--- find position of first occurrence of ele in stem m,
        return 0 if nonemove a part of a stem -----------------------*/
mPos: procedure expose m.
parse arg m, ele, sx
    if sx == '' then
        sx = 1
    do x=sx to m.m.0
        if m.m.x = ele then
            return x
        end
    return 0
endProcedure mPos

/*--- move a part of a stem ------------------------------------------*/
mMove: procedure expose m.
parse arg m, sx, dx
    if dx < sx then do
        y = dx
        do x=sx to m.m.0
            m.m.y = m.m.x
            y = y + 1
            end
        end
    else if dx > sx then do
        y = m.m.0 + dx - sx
        do x=m.m.0 by -1 to sx
            m.m.y = m.m.x
            y = y - 1
            end
        end
    m.m.0 = m.m.0 + dx - sx
    return
endProcedure mMove

/*--- insert a stem into another ------------------------------------*/
mInsert: procedure expose m.
parse arg m, tx, st
    call mMove m, tx, tx+m.st.0
    do sx=1 to m.st.0
        dx = tx-1+sx
            m.m.dx = m.st.sx
            end
    return
endProcedure mInsert

/*--- 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 the lines of a stem, possibly repeated --------------------------
       args: stem, fmt see fGen: -------------------------------------*/
mCat: procedure expose m.
parse arg st, fmt
    return mCatFT(st, 1, m.st.0, fmt)

mCatFT: procedure expose m.
parse arg st, fx, tx, fmt
    f2 = fmt'%#?a%c%#? %#?e%# %& %&a'
    if tx < fx then
        return f(f2'%##e')
    res = f(f2'%##a', m.st.fx)
    do sx=fx+1 to tx
        res = res || f(f2, m.st.sx)
        end
    return res
endProcedure mCatFT

fGenCat: procedure expose m.
parse arg s, ax
    do fx=1 ??????? until \ scanLit(s, '%,')
        f.fx = fGen(s)
        end
    if \ scanLit(s, '%)') then
        call scanErr s, 'no %) after @fGenCat%('
    if \ scanEnd(s) then
        call scanErr s, 'mGenCat not at end'
    if fx < 2 | f.2 == "''" then
        f.2 = fGen(scanSrc(f_u, '%c'))
    if fx < 3 then
        f.3 = "''"
    if fx < 4 then
        f.4 = "''"
    adr = m.s.src'%'
    if f.1 == "''" then
        m.f_gen.adr.1 = 'return' f.2
    else
        m.f_gen.adr.1 = 'return' f.1 '||' f.2
    m.f_gen.adr.2 = 'return' f.4
    if f.3 == "''" then
        return f.2
    else
        return f.3 '||' f.2
endProcedure fGenCat

mIni: procedure expose m.
    if m.m_ini == 1 then
        return
    m.m_ini = 1
    call utIni
    m.mBase64 = m.ut_alfUC || m.ut_alfLc || m.ut_digits'+-'
    m.m_area.0 = 0
    call mNewArea
    return
endProcedure mIni

/* copy m end *********************************************************/
/* copy ii begin ********* Installation Info *************************/
iiini: procedure expose m.
    if m.ii_ini == 1 then
        return
    m.ii_ini = 1
    m.ii_rz = ''
    i = 'RZ0 0 T S0 RZ1 1 1 S1'  ,
        'RZX X X X2 RZY Y Y Y2 RZZ Z Z Z2'  ,
        'RQ2 Q Q Q2 RR2 R R R2 RZ2 2 B S2 RZ4 4 D S4'
    do while i <> ''
        parse var i rz m.ii_rz2c.rz m.ii_rz2plex.rz sys i
        if rz <> RZ0 & rz <> RZ1 then
            m.ii_rz = strip(m.ii_rz rz)
        m.ii_rz2Sys.rz  = sys
        m.ii_sys2rz.sys = rz
        end
    i = 'DBTF T DTF DVTB V DTB DBOC C DOC DBTC B DBT DBIA A DIA' ,
        'DE0G E DE0 DEVG M DEV DPXG I DPX DX0G X DX0' ,
        'DPYG Y DPY DPZG N DPZ' ,
        'DBOF F DOF DVBP P DBP DP2G Q DP2 DBOL O DOL DP4G U DP4'
    do while i <> ''
        parse var i db m.ii_db2c.db mbr i
        m.ii_mbr2db.mbr = db
        m.ii_db2Elar.db = wordPos(db, 'DVTB DVBP DEVG') > 0
        end
    m.ii_rz2db.rz0 = 'DBTC DBIA'
    m.ii_rz2db.rz1 = 'DBTF DVTB DBOC'
    m.ii_rz2db.rzx = 'DE0G DEVG DX0G DPXG'
    m.ii_rz2db.rzy = 'DE0G DEVG DPYG'
    m.ii_rz2db.rzz = 'DE0G DEVG DPZG'
    m.ii_rz2db.rq2 = 'DBOF DVBP DP2G'
    m.ii_rz2db.rr2 = 'DBOF DVBP DP2G'
    m.ii_rz2db.rz2 = 'DBOF DVBP DP2G'
    m.ii_rz2db.rz4 = 'DBOL DP4G'
    i = ''
    do rx=1 to words(m.ii_rz)
        rz = word(m.ii_rz, rx)
        i = i repAll(' 'space(m.ii_rz2db.rz, 1), ' ', ' 'rz'/')
        end
    m.ii_rzDb = space(i, 1)
    return
endProcedure iiIni

iiMbr2DbSys: procedure expose m.
parse upper arg mbr
    return iiLazy(ii_mbr2db, left(mbr, 3), 'member')

iiRz2C: procedure expose m.
parse upper arg rz
    return iiLazy(ii_rz2c, rz, 'rz')

iiRz2P: procedure expose m.
parse upper arg rz
    return iiLazy(ii_rz2plex, rz, 'rz')

iiRz2Dsn: procedure expose m.
parse upper arg rz
    return overlay('Z', rz, 2)

iiDBSys2C: procedure expose m.
parse upper arg db
    return iiLazy(ii_db2c, db, 'dbSys')

iiSys2RZ: procedure expose m.
parse upper arg sys
    return iiLazy(ii_sys2rz, left(sys, 2), 'sys')

iiLazy: procedure expose m.
parse arg st, key, txt
    if symbol('m.st.key') == 'VAR' then
        return m.st.key
    if m.ii_ini == 1 then
       return err('no' txt'='key 'in ii' st)
    call iiIni
    return iiLazy(st, key, txt)
endProcedure iiLazy

iiVPut:procedure expose m.
parse upper arg rz '/' db .
    call vPut 'rz', rz
    call vPut 'rzC', iiRz2C(rz)
    call vPut 'rzP', iiRz2P(rz)
    call vPut 'rzD', iiRz2Dsn(rz)
    call vPut 'dbSys', db
    call vPut 'dbSysC', iidbSys2C(db)
    call vPut 'dbSysElar', iiLazy(ii_db2Elar, db)
    return 1
endProcedure iiVPut

iiIxVPut:procedure expose m.
parse arg ix
    if ix > words(m.ii_rzDb) then
        return 0
    else
        return iiVPut(word(m.ii_rzDb, ix))
endProcedure iiIxVPut
/* copy ii end   ********* Installation Info *************************/
/* copy dsnList begin **************************************************
     csi interface: see dfs managing catalogs chapt. 11
         returncode/reason see message IDC3009I
**********************************************************************/
/*--- specify dsn mask and fields to start a csi catalog search --------
      arguments:
          m       objectPointer
          dsnMask specifies the dsns with wildcards:
              %  1 character
              *  0 - n character in one level
              ** 0 - n levels
          fields a (space separated) list of field names -------------*/
csiOpen: procedure expose m.
parse arg m, dsnMask, fields
    m.m.fld.0 = words(fields)
    ffix = d2c(m.m.fld.0, 2)
    do x=1 to m.m.fld.0
        m.m.fld.x = translate(word(fields, x))
        ffix = ffix || left(m.m.fld.x, 8)
        end
    if dsnMask \== '' & right(dsnMask, 1) \== ' ' ,
          & pos('*', dsnMask) < 1 & length(dsnMask) < 42 then
        dsnMask = dsnMask'.**'
    m.m.filt = left(dsnMask, 149) ,
             || left('Y', 3) ,        /* resume offset 149      */
             || ffix                  /* csiNumEn offset 152    */

    WORKLEN = 1024 * 64
    m.m.work = D2C(WORKLEN,4) || COPIES('00'X,WORKLEN-4)
    m.m.pos = workLen + 1
    return
endProcedure csiOpen

/*--- put the next dsn into m.o and m.o.* (for other fields)
      return 1 if next dsn exists 0 otherwise ------------------------*/
csiNext: procedure expose m.
parse arg m, o
    usedL = C2D(SUBSTR(m.m.work,9,4)) /* GET AMOUNT OF WORK AREA USED */
    px = m.m.pos
    do forever
        if px > usedL then do
            if substr(m.m.filt, 150, 1) \== 'Y' then do
                m.m.pos = px
                m.o = ''
                return 0
                end
            reason = left('', 4)
            ADDRESS LINKPGM 'IGGCSI00  reason m.'m'.filt  m.'m'.work'
            if rc == 0 & substr(reason, 3, 2) == '0000'x then
                nop
            else if rc == 4 & substr(reason, 3, 2) == '0464'x then
                say 'data set entry with error'
            else
                call err 'call csi returns' rc,
                             'rc' c2d(substr(reason, 4,1)),
                             'reason' c2d(substr(reason, 3,1)),
                             'module' substr(reason, 1,2)
            usedL = C2D(SUBSTR(m.m.work,9,4)) /* GET WORK AREA USED */
            numFD = C2D(SUBSTR(m.m.work,13,2)) /* no flds + 1 */
            if numFd <> m.m.fld.0 + 1 then
                call err 'csiNumFd' numFd 'not' m.m.fld.0 '+' 1
            px = 15
            iterate
            end
        eType =  substr(m.m.work, px+1, 1)
        m.o = strip(substr(m.m.work, px+2, 44), 't')
        flag = substr(m.m.work, px, 1)
        /* say 'eType' eType m.o 'flag' c2x(flag) */
        if eType == '0' then do
            if flag \== '00'x & flag \== '40'x then
                call err 'flag' c2x(flag) 'for catalog' m.o
            px = px + 50    /* length of catalog entry  */
            iterate
            end
        else do
            if \ abbrev(x2b(c2x(flag)), '101') then
                call err 'call csi entry flag' x2b(c2x(flag)),
                             'rc' c2d(substr(m.m.work, px+49,1)),
                             'reason' c2d(substr(m.m.work, px+48,1)),
                             'module' substr(m.m.work, px+46, 2),
                             'for entry' m.o,
                             'see qw IDC3009I'
            py = px + 46
            tl = c2d(substr(m.m.work, py, 2))
            pl = py + 4
            pf = py + m.m.fld.0 * 2 + 4
            do fx = 1 to m.m.fld.0
                fi = m.m.fld.fx
                fl = c2d(substr(m.m.work, pl, 2))
                m.o.fi = substr(m.m.work, pf, fl)
                if fi = 'MGMTCLAS' then
                    m.o.fi = substr(m.o.fi, 3, c2d(left(m.o.fi ,2)))
                else if wordPos(fi, 'COMUDSIZ NOBYTTRK') > 0 then
                    m.o.fi = utc2d(m.o.fi)
                pf = pf + fl
                pl = pl + 2
                end
            if py + tl <> pf then
                call err 'length mismatch for entry' m.o
            m.m.pos = pf
            return 1
            end
        end
endProcedure csiNext
/*--- if dsn is arcived return 'arcive'
      if dsn is tape return 'tape'
      otherwise return managment class ------------------------------*/
csiArcTape: procedure expose m.
parse arg vo, cl, dt, dsn
        if vo = '' then
            say err '||| no volume for dsn' dsn
        else if vo = 'ARCIVE' then
            res = 'arcive'
        else if cl <> '' then
            res = cl
        else if abbrev(vo, 'SHR') then
            res = 'SHR'
        else
            res = 'tape'
        if   res = 'arcive' then
            return res
      /*if   abbrev(res, 'ar') \= abbrev(dt, '00'x) , */
        if   abbrev(res, 'ta') \= abbrev(c2x(left(dt, 1)), '7') ,
           | (left(res, 1) >= 'A') \= abbrev(dt, '30'x) then
           say '||| mismatch cl' cl 'vo' vo 'dt' c2x(dt) 'dsn' dsn
        return res
endProcedure csiArcTape
/*--- mbrList with  listDS -----------------------------------------*/
mbrList: procedure expose m.
parse arg m, pds
    msk = strip(dsnGetMbr(pds))
    if msk == '*' then
        msk = ''
    parse value dsnCsmSys(dsnSetMbr(pds)) with sys '/' dsn
    if sys == '*' then do
        call adrTso listDS "'"dsn"'" members
        oy = m.tso_trap.0 + 99
        mFound = 0
        mx = 0
        do ox=1 to m.tso_trap.0
            if mFound then do
                if msk \== '' then
                    if \ match(strip(m.tso_trap.ox), msk) then
                        iterate
                mx = mx +1
                m.m.mx = strip(m.tso_trap.ox)
                end
            else
                mFound = m.tso_trap.ox == '--MEMBERS--'
            end
        end
    else do
        if msk <> '' then
            msk = 'member('translate(msk, '%', '?')')'
        mbr_name.0 = -99
        call adrCsm "mbrList system("sys") dataset('"dsn"')" msk ,
                "index(' ') short"
        do mx=1 to mbr_name.0
            m.m.mx = strip(mbr_name.mx)
            end
        mx = mbr_name.0
        end
    m.m.0 = mx
    return mx
endProcedure mbrList
/* copy dsnList end   ************************************************/
/* copy adrTso begin *************************************************/
/*--- send ggTsoCmd to tso, fail if rc <> 0 or not listed in ggRet ---*/
adrTso:
    parse arg m.tso_stmt, ggRet
    call outtrap m.tso_trap.
    address tso m.tso_stmt
    m.tso_rc = rc
    call outtrap off
    if m.tso_rc == 0 then
        return 0
    m.tso_trap = ''
    do ggXx=1 to min(7, m.tso_trap.0)
        m.tso_trap = m.tso_trap'\n'strip(m.tso_trap.ggXx)
        end
    if m.tso_trap.0 > 7 then do
        if m.tso_trap.0 > 14 then
            m.tso_trap = m.tso_trap'\n............'
        do ggXx=max(8, m.tso_trap.0-6) to m.tso_trap.0
            m.tso_trap = m.tso_trap'\n'strip(m.tso_trap.ggXx)
            end
        end
    if ggRet <> '*' & wordPos(m.tso_rc, ggRet) <= 0 then
        call err 'adrTso rc=' m.tso_rc 'stmt='m.tso_stmt m.tso_trap
    return m.tso_rc
endSubroutine adrTso

/*--- format dsn from tso format to jcl format -----------------------*/
dsn2jcl: procedure expose m.
parse upper 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 expose m.
parse arg dsn .
    return "'"dsn"'"
endProcedure jcl2dsn

dsnSetMbr: procedure expose m.
parse arg lib '(' . , mbr .
     bx = pos('(', dsn)
     if mbr = '' then
         return strip(lib)
     else
         return strip(lib)'('mbr')'
endProcedure dsnSetMbr

dsnGetMbr: procedure expose m.
parse arg dsn '(' mbr ')'
     if mbr = '' then
         return arg(2)
     else
         return strip(mbr)
endProcedure dsnGetMbr

dsnCsmSys: procedure expose m.
parse upper arg dsn, withStar
    if pos('/', dsn) < 1 then
        sys = '*'
    else
        parse var dsn sys '/' dsn
    if sys <> '' & sys <> '*' & sys <> sysvar(sysnode) then
        return sys'/'dsn
    else if withStar == 0 then
        return dsn
    else
        return '*/'dsn
endProcedure dsnCsmSys
/**********************************************************************
    io: read or write a dataset with the following callsequences:
        read:  tsoOpen...'R', readDD*,  tsoClose
        write: tsoOpen...'W', writeDD*, tsoClose

        readDD returns true if data read, false at eof
        do not forget that open is mandatory to write empty file|
***********************************************************************/

/*--- open dd for read (rw='R') or write (rw='W') --------------------*/
tsoOpen: procedure expose m.
parse upper arg dd, rw
    return adrTso('execio' 0 'disk'RW dd '(open)')
return /* end tsoOpen */

/*--- close dd -----------------------------------------------------*/
tsoClose: procedure expose m.
    parse upper arg dd
    return adrTso('execio 0 diskR' dd '(finis)')
endProcedure tsoClose

/*--- 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 */

/*--- 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

/*--- readNx: read next line, using buffer ---------------------------*/
/*--- begin: allocate dsnSpec and ini ------------------------ -------*/
readNxBegin: procedure expose m.
    parse arg m, m.m.dsn, m.m.dd, m.m.Cnt
    if m.m.dd = '' then
        m.m.dd = 'DDNX*'
    if m.m.cnt = '' then
        m.m.cnt = 1000
    m.m.cx = m.m.cnt + 999
    m.m.buf0x = 0
    m.m.0 = 0
    parse value dsnAlloc('dd('m.m.dd')' m.m.dsn) with m.m.dd m.m.free
    call tsoOpen m.m.dd, 'R'
    return m
endProcedure readDDNxBegin

/*--- return the stem of the next line, or '' at end -----------------*/
readNx: procedure expose m.
parse arg m
    m.m.cx = m.m.cx + 1
    if m.m.cx <= m.m.0 then
        return m'.'m.m.cx
    m.m.buf0x = m.m.buf0x + m.m.0
    if \ readDD(m.m.dd, 'M.'m'.', m.m.cnt)then
        return ''
    m.m.cx = 1
    return m'.1'
endProcedure readDDNx
/*--- return the stem of the curr line, '' at end --------------------*/
readNxCur: procedure expose m.
parse arg m
    if m.m.cx <= m.m.0 then
        return m'.'m.m.cx
    else
        return ''
endProcedure readNxCur

/*--- return the position (line number) of reader
           plus le characters of the current line (default 50) ------*/
readnxPos: procedure expose m.
parse arg m, le
    li = m'.'m.m.cx
    li = strip(m.li, 't')
    if arg() < 2 then
        le = 50
    if le < 1 then
        li = ''
    else if length(li) <= le then
        li = ':' li
    else
        li = ':' left(li, le-3)'...'
    return 'line' (m.m.buf0x + m.m.cx)li
endProcedure readnxPos

/*--- close and deallocate ------------------------------------------*/
readNxEnd: procedure expose m.
parse arg m
    call tsoClose m.m.dd
    call tsoFree m.m.free
    return
endProcedure readDDNxEnd

/*--- standardise a dsn spec
       word1             dsName or -
       word2             dd     or -
       word3             disp   or -
       word4 to first :  attributes in tso format
       after first :     attributes for new allocation
----------------------------------------------------------------------*/
dsnSpec: procedure expose m.
parse upper arg spec
    rr = ''      /* put leading - in separate words */
    do sx=1 while words(rr) < 3 & wx \= ''
        wx = word(spec, sx)
        do while abbrev(wx, '-') & words(rr) < 3
            wx = substr(wx, 2)
            rr = rr '-'
            end
        rr = rr wx
        end
    spec = rr subWord(spec, sx)
    na = ''
    dd = ''
    di = ''
    if left(spec, 1) = '&' then /* external spec is handled ok */
        spec = strip(substr(spec, 2))
    do wx=1 by 1
        w = word(spec, wx)
        if w = '' | abbrev(w, '.') | abbrev(w, ':') then
            leave
        else if wordPos(w, 'OLD SHR MOD NEW')>0 | abbrev(w, 'SYSO') then
            di = w
        else if w == 'CATALOG' | w == 'CAT' then
            di = di 'CAT'
        else if abbrev(w, 'DD(') then
            dd = substr(w, 4, length(w)-4)
        else if abbrev(w, 'DSN(') then
            na = strip(substr(w, 5, length(w)-5))
        else if na == '' then
            na = dsn2jcl(w)
        else if pos('(', w) > 0 then
            leave
        else if dd == '' then
            dd = w
        else if di == '' then
            di = w
        else
            leave
        end
    if na == '' then
        na = '-'
    else if abbrev(na, "'") then
        na = substr(na, 2, length(na)-2)
    if dd == '' then dd = '-'
    if di == '' then di = '-'
    re = subword(spec, wx)
    if abbrev(re, '.') then
        re = substr(re, 2)
    return na dd di re
endProcedure dsnSpec

/*--- alloc a dsn with dsnAlloc
          if the dsn is inuse wait and retry
          until either the allocation is successfull
          or the timeout occurs --------------------------------------*/
dsnAllocWait: procedure expose m.
parse upper arg spec, pDi, pDD, timeOut
    x = max(1, arg() - 1)
    do rt=0
        m.tso_trap.1 = ''
        m.tso_trap.2 = ''
        m.tso_trap.3 = ''
        res = dsnAlloc(spec, pDi, pDD, '*')
        if \ datatype(res, 'n') then
            return res
        msg = m.tso_trap.1'\n'm.tso_trap.2'\n'm.tso_trap.3
        if rt > timeOut & timeOut \== '' then
            return err('timeout allocating' spec time() '\n'msg)
        if pos('DATA SET IS ALLOCATED TO ANOTHER', msg) < 1 then
            return err('allocating' spec'\n'msg)
        say time() 'sleep and retry alloc' spec
        call sleep 1, 0
        end
endProcedure dsnAllocWait

/*--- alloc a dsn or a dd
          spec '-'<ddName>
               datasetName? disposition? '.'? attributes? (':' newAtts)?
          disp default disposition
          dd   default dd name
          retRc   erlaubte ReturnCodes (leer = 0)
          returns if ok then ddName <rexx for free> otherwise rc -----*/
dsnAlloc: procedure expose m.
parse upper arg spec, pDi, pDD, retRc
    parse value dsnSpec(spec) with na dd di rest
    if dd == '-' & pDD \== '' then
        dd = pDD
    if dd == '-' then
        dd = 'DD*'
    dd = tsoDD(dd, 'a')
    m.tso_dsn.dd = ''
    if na == '-' & di == '-' & rest = '' then
        return dd
    if di = '-' then
        if pDi == '' then
            di = 'SHR'
        else
            di = pDi
    if pos('(', na) < 1 then
        nop
    else if di = 'MOD' then
        call err 'disp mod for' na
    else
        di = 'SHR'
    if lastPos('/', na, 6) > 0 then
        rx = csmAlloc(na dd di rest, retRc)
    else
        rx = tsoAlloc(na dd di rest, retRc)
    if rx = 0 then
        return dd dd
    else
        return rx
endProcedure dsnAlloc

/*--- find a free dd with prefix dd ----------------------------------*/
tsoDD: procedure expose m.
parse upper arg dd, f, noErr
    if symbol('m.tso_ddAll') \== 'VAR' then do
        call errIni
        m.tso_ddAll = ''
        end
    if f == '-' then do
        ax = wordPos(dd, m.tso_ddAll)
        if ax > 0 then
            m.tso_ddAll = delWord(m.tso_ddAll, ax, 1)
        else if noErr \== 1 then
            call err 'tsoDD dd' dd 'not used' m.tso_ddAll
        end
    else if f <> 'A' then
        call err 'tsoDD bad fun' f
    else do
        if right(dd, 1) = '*' then do
            d0 = left(dd, length(dd)-1) || m.err.screen
            dd = d0
            do dx=1 while wordPos(dd, m.tso_ddAll) > 0
                dd = d0 || dx
                end
            end
        else if pos('?', dd) > 0 then
            dd = repAll(dd, '?', m.err.screen)
        if wordPos(dd, m.tso_ddAll) < 1 then
            m.tso_ddAll = strip(m.tso_ddAll dd)
        m.tso_dsn.dd = ''
        m.tso_dsOrg.dd = ''
        end
    return dd
endProcedure tsoDD

tsoAlloc: procedure expose m.
parse arg na dd disp rest ':' nn, retRc
    dd = translate(dd)
    c = 'alloc dd('dd')' disp
    if na == '-' then
        m.tso_dsn.dd = ''
    else if na \== 'INTRDR' then do
        c = c "DSN('"na"')"
        m.tso_dsn.dd = na
        end
    else do
        c = c "sysout(*) writer(intRdr)"
        m.tso_dsn.dd = '*intRdr'
        end
    if wordPos(disp, 'NEW MOD') > 0 & nn \== '' then
        c = c dsnCreateAtts(,nn)
    if adrTso(c rest, '*') = 0 then
        return 0
    if pos('IKJ56246I', m.tso_trap) > 0 then
        if pos('TED, FILE IN USE', m.tso_trap) > 0 then do
     /* IKJ56246I DATA SET A540769.WK.REXX NOT ALLOCATED, FILE IN USE */
        say 'tsoAlloc dd' dd 'already in use:' substr(m.tso_trap, 3)
        say '.... trying to free'
        call tsoFree dd, 1
        say '.... retrying to allocate' c rest
        if adrTso(c rest, '*') = 0 then
            return 0
        end
    if nn \= '' & wordPos(disp, 'OLD SHR') > 0 ,
          & pos('IKJ56228I', m.tso_trap) > 0 ,
          & pos(' NOT IN CATALOG ', m.tso_trap) > 0 then do
       /* IKJ56228I DATA SET A540769.XY.REXX NOT IN CATALOG OR CATAL*/
        say 'tsoAlloc creating' c rest ':'nn
        call adrTso 'alloc dd('dd') new catalog' dsnCreateAtts(na, nn)
        call adrTso 'free  dd('dd')'
        return tsoAlloc(na dd disp rest, retRc)
        end
    if pos('*', retRc) < 1 & wordPos(m.tso_rc, retRc) < 1 then
        call err 'tsoAlloc rc='m.tso_rc 'tsoStmt='m.tso_stmt m.tso_trap
    call sayNl 'tsoAlloc rc='m.tso_rc 'tsoStmt='m.tso_stmt m.tso_trap
    return m.tso_rc
endProcedure tsoAlloc

dsnExists: procedure expose m.
parse upper arg aDsn
    parse value dsnCsmSys(aDsn) with rz '/' dsn
    if rz == '*' then
        return sysDsn("'"dsn"'") == 'OK'
    else if dsnGetMbr(dsn) == '' then do
        lc = adrCsm('dslist system('rz') dsnMask('dsn') short', 4)
        if stemsize = 0 | stemSize = 1 then
            return stemSize
        call err 'csmExists stemSize='stemsize 'for dsn='aDsn
        end
    else do
        cc = adrCsm("mbrList system("rz") dataset('"dsnSetMbr(dsn)"')",
                    "member("dsnGetMbr(dsn)") index(' ') short", 8)
        if cc <> 0 then do
            if pos(' NOT IN CATALOG\', m.tso_trap) > 0 ,
              & pos('CSMSV29E DATA SET ', m.tso_trap) > 0 then
                return 0
            return err('error in csm mbrList' aDsn m.tso_trap)
            end
        if mbr_name.0 == 0 | mbr_name.0 == 1 then
            return mbr_name.0
        call err 'csmExists mbr_mem#='mbr_name.0 'for dsn='aDsn
        end
endProcedure dsnExists

dsnLikeAtts: procedure expose m.
parse upper arg dsn, isFile
     if isFile then do
         ddDsn = m.tso_dsn.dsn
         if lastPos('/', m.tso_dsn.dsn, 4) < 1 then
              return tsoLikeAtts(dsn, 1)
         dsn = m.tso_dsn.dsn
         end
     sx = lastPos('/', dsn, 4)
     if sx < 1 then
         return tsoLikeAtts(dsn, 0)
     else if abbrev(dsn, '*/') | abbrev(dsn, sysVar('SYSNODE')'/') then
         return tsoLikeAtts(substr(dsn, sx+1), 0)
     else
         return csmLikeAtts(dsn)
endProcedure dsnLikeAtts

tsoLikeAtts: procedure expose m.
parse arg dsn, isFile
    rc = listDsi("'"dsn"'" copies('FILE', isFile) "SMSINFO")
    if rc = 0 then
        r = ''
    else if rc = 4 & sysReason = 19 then do
        r = 'UCOUNT(30)' /* 7.8.13 corrected attribute */
        say 'creating' dsn 'with multi volume' mv
        end
    else if rc ^= 0 then
        call err 'listDsi rc' rc 'reason' sysReason,
                             sysMsgLvl1 sysMsgLvl2
    if right(sysDsSms, 7) == 'LIBRARY' | abbrev(sysDsSms, 'PDS') ,
                      | sysDsOrg = 'PO' then
         r = 'dsOrg(PO) DSNTYPE(LIBRARY)' r
    else
         r = "dsOrg("sysDSorg")" r
    if sysUnits = 'TRACK' then
        sysUnits = 'TRACKS'
    return r "MGMTCLAS("sysMgmtClass")",
        "DATACLAS("sysDataClass")" ,
        "RECFM("strip(translate('1 2 3', ' 'sysREcFM, '123'))")",
        "LRECL("SYSLRECL")",
        "SPACE("sysPrimary"," sysSeconds")" ,
            sysUnits || left('S', sysUnits == 'TRACK')
    /*  "blksize("sysBLkSIZE")" removed 3.4.13: let sms do the magic */
endProcedure tsoLikeAtts

tsoFree: procedure expose m.
parse arg ddList, tryClose
    do dx=1 to words(ddList)
        dd = word(ddList, dx)
        if adrTso('free dd('dd')', '*') <> 0 then do
            if pos('IKJ56861I', m.tso_trap) > 0 & tryClose == 1 then
              if pos('NOT FREED, DATA SET IS OPEN', m.tso_trap),
                    > 0 then do
                  /* IKJ56861I  FILE A1 NOT FREED, DATA SET IS OPEN */
                say 'dataset open:' substr(m.tso_trap, 3)
                say '.... trying to close'
                if adrTso('execio 0 diskR' dd '(finis)', '*') = 0 then
                   call adrTso 'free dd('dd')', '*'
                end
            if m.tso_rc \== 0 then
                call sayNl 'rc='m.tso_rc 'tsoStmt='m.tso_stmt m.tso_trap
            end
        call tsoDD dd, '-', 1
        end
    return
endProcedure tsoFree

dsnCreateAtts: procedure expose m.
parse arg dsn, atts
    res = ''
    if dsn \== '' & \ abbrev(dsn, '-') then
        res = "dataset('"dsnSetMbr(dsn)"')"
    if abbrev(atts, ':') then do
        parse var atts a1 atts
        rl = substr(a1, 3)
        if abbrev(a1, ':F') then do
            if rl = '' then
                rl = 80
            res =  res "recfm("space(f b)") lrecl("rl")"
            end
        else if abbrev(a1, ':V') then do
            if rl = '' then
                rl = 32756
            res =  res "recfm("space(v b)") lrecl("rl")"
            end
        else if abbrev(a1, ':L') then
            res = res dsnLikeAtts(rl, 0)
        else if abbrev(a1, ':D') then
            res = res dsnLikeAtts(rl, 1)
        else
            call err 'csnCreateAtt bad :' a1
        end
    aU = ' 'translate(atts)
    hasOrg = pos(' DSORG(', aU) > 0 | pos(' DSNTYPE(', aU) > 0
    hasMbr = pos('(', dsn) > 0
    if hasMbr & \ hasOrg then
        atts = atts 'dsorg(po) dsntype(library)'
    if hasOrg | hasMbr then do
        ww = DSORG DSNTYPE
        do wx=1 to words(ww)
            do forever
                cx = pos(' 'word(ww, wx)'(', ' 'translate(res))
                if cx == 0 then
                    leave
                cy = pos(')', res, cx)
                res = delstr(res, cx, cy+1-cx)
                end
            end
        end
    res = res atts
    aU = ' 'translate(res)
    if pos(' MGMTCLAS(', aU) < 1 then
        res = res 'mgmtclas(COM#A091)'
    if pos(' SPACE(', aU) < 1 then
        res = res 'space(1, 50) cylinders'
    return res
endProcedure dsnCreateAtts
/*--- check if a dataset is archive ------------------------------------
          returns 'ok'                    if dataset on disk
                  'not'                   if dataset is not catalogued
                  'arc'                   if dataset archived
                  listDsi errorMsg        otherwise ------------------*/
dsnArc: procedure expose m.
parse upper arg dsn
    lc = listDsi("'"strip(dsn)"' noRecall")
    if lc = 0 then
        return 'ok'
    else if lc=4 & sysReason = 19 then  /* multiple volumes */
        return 'ok'
    else if lc=16 & sysReason = 5 then
        return 'notCat'
    else if lc=16 & sysReason = 9 then
        return 'arc'
    else
        return 'listDsi cc='lc', sysReason='sysReason ,
                          'm2='sysMsgLvl2', m1='sysMsgLvl1
endProcedure dsnArc
/*--- read the dataset specified in ggDsnSpec to stem ggSt -----------*/
readDSN:
parse arg ggDsnSpec, ggSt
    ggAlloc = dsnAlloc(ggDsnSpec, 'SHR', 'readDsN')
    call adrTso 'execio * diskr' word(ggAlloc, 1) '(stem' ggSt' finis)'
    call tsoFree word(ggAlloc, 2)
    return
endSubroutine readDsn

/*--- write the dataset specified in ggDsnSpec from stem ggSt
          write ggCnt records if not empty otherwise ggst0
          if ggSay 1 then say ... records written to ... -------------*/
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 'open finis)'
    call tsoFree word(ggAlloc, 2)
    if ggSay == 1 | m.debug == 1 then
       say ggCnt 'records written to' ggDsnSpec
    return
endSubroutine writeDsn

copyDSN: procedure expose m.
parse arg frSpec, toSpec, ggSay
    parse value dsnAlloc(frSpec, 'SHR', 'FRDD') with frDD frFr
    parse value dsnAlloc(toSpec, 'OLD', 'TODD') with toDD toFr
    call tsoOpen frDD, 'R'
    call tsoOpen toDD, 'W'
    cnt = 0
    do while readDD(frDD, r.)
        call writeDD toDD, r.
        cnt = cnt + r.0
        end
    call tsoClose frDD
    call tsoClose toDD
    call tsoFree frFr toFr
    if ggSay == 1 | m.debug == 1 then
       say cnt 'records copied from' frSpec 'to' to toSpec
    return
endSubroutine writeDsn
/* copy adrTso end ****************************************************/
/* copy err begin *** errorhandling, messages, help    ****************/
errIni: procedure expose m.
    if m.err.ini == 1 then
        return
    m.err.ini     = 1
    m.err.handler = ''
    m.err.cleanup = ';'
    m.err.opt     = ''
    parse source m.err.os .
    m.err.ispf    = 0
    m.err.screen  = 0
    if m.err.os \== 'LINUX' then do
        address tso 'profile MsgId'   /* brauchen wir in tsoAlloc| */
        if sysVar('sysISPF') = 'ACTIVE' then do
            m.err.ispf = 1
            address ispExec 'vget zScreen shared'
            m.err.screen = zScreen
            end
        end
    return
endProcedure errIni

/* configure err -----------------------------------------------------*/
errReset: procedure expose m.
    call errIni
    parse arg m.err.opt, m.err.handler
    if pos('I', translate(m.err.opt)) > 0 & m.err.ispf then
        address ispExec 'control errors return'
    return
endSubroutine errReset

/*--- error routine: abend with message ------------------------------*/
err:
    parse arg ggTxt, ggOpt
    call errIni
    drop err handler cleanup opt call return stackHistory
    if ggOpt == '' & m.err.handler \== '' then
        interpret m.err.handler
    call errSay 'f}'ggTxt
    call errCleanup
    if ggOpt == '' then
        ggOpt = m.err.opt
    upper ggOpt
    if pos('T', ggOpt) > 0  then do
        trace ?r
        say 'trace ?r in err'
        end
    if pos('H', ggOpt) > 0 & m.err.eCat == 'f' then
        x = show + stackHistory + by + bad + arithmetic + conversion
    call errSay ' }errorhandler exiting with exit(12)'
    exit errSetRc(12)
endSubroutine err

/*--- set rc for ispf: -------------------------------------------------
    if a cmd is run by ispStart, its RC is ignored,
         but ISPF passes the value of the shared variable zIspfRc
         back as return code
----------------------------------------------------------------------*/
errSetRc: procedure expose m.
parse arg zIspfRc
    if m.err.ispf then
        address ispExec vput 'zIspfRc' shared
    return zIspfRc
endProcedure errSetRc

/*--- error routine: user message cleanup exit -----------------------*/
errAddCleanup: procedure expose m.
parse arg code
    call errIni
        /* concatenate backwards, then it is executed also backwards */
    m.err.cleanup = ';'code || m.err.cleanup
    return
endProcedure errAddCleanup

errRmCleanup: procedure expose m.
parse arg code
    call errIni
    cx = pos(';'code';', m.err.cleanup)
    if cx > 0 then
        m.err.cleanup = left(m.err.cleanup, cx) ,
                     || substr(m.err.cleanup, cx + length(code)+2)
    return
endProcedure errRmCleanup

errCleanup: procedure expose m.
    call errIni
    errCleanup = m.err.cleanup
    if errCleanup <> ';' then do
        m.err.cleanup = ';'
        say 'err cleanup begin' errCleanup
        interpret errCleanup
        say 'err cleanup end' errCleanup
        end
    if symbol('m.tso_ddAll') == 'VAR' then
        call tsoFree m.tso_ddAll, 1
    return
endProcedure errCleanup

/*--- say an errorMessage msg with pref and linesplits --------------*/
errSay: procedure expose m.
parse arg msg
    return sayNl(errMsg(msg))

/*--- prefix an errormessage with pref,
          split it into lines at \n to stem m.err -------------------*/
errMsg: procedure expose m.
parse arg msg
    m.err.eCat = 'f'
    do while substr(msg, 2, 1) == '}'
        parse var msg m.err.eCat '}' msg
        end
    res = msg
    if m.err.eCat <> '' then do
       pTxt = ',error,fatal error,input error,s-}scanErr,warning,'
 /*    pTxt = ',error,fatal error,input error,syntax error,warning,' */
       px = pos(','m.err.eCat, pTxt)
       if px < 1 then do
           m.err.eCat = 'f'
           px = pos(','m.err.eCat, pTxt)
           end
       res = substr(pTxt, px+1, pos(',', pTxt, px+2)-px-1)
       if substr(res, 3, 1) == '}' then
           parse var res 2 opt 3 br 4 res
       if opt == '-' then
           res = res msg
       else do
           parse source . . s3 .              /* current rexx */
           res = res 'in' s3':' msg
           end
       end
    return res
endProcedure errMsg

splitNL: procedure expose m.
parse arg st, msg
    bx = 1
    do lx=1 to 20
        ex = pos('\n', msg, bx)
        if ex < bx then
            leave
        m.st.lx = substr(msg, bx, ex-bx)
        bx = ex+2
        end
    m.st.lx = substr(msg, bx)
    m.st.0 = lx
    return st
endProcedure splitNL

outNL: procedure expose m.
parse arg msg
    bx = 1
    do lx=1 to 20
        ex = pos('\n', msg, bx)
        if ex < bx then
            leave
        call out substr(msg, bx, ex-bx)
        bx = ex+2
        end
    call out substr(msg, bx)
    return
endProcedure outNL

/*--- say (part of) the lines of a stem ----------------------------*/
sayNl: procedure expose m.
parse arg msg
    bx = 1
    do lx=1 to 20
        ex = pos('\n', msg, bx)
        if ex < bx then
            leave
        say strip(substr(msg, bx, ex-bx), 't')
        bx = ex+2
        end
    say strip(substr(msg, bx), 't')
    return 0
endProcedure sayNl

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

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

/*--- assert that the passed rexx expression evaluates to true -------*/
assert:
    interpret 'if ('arg(1)') == 1 then return 1'
    interpret 'call err' quote('assert failed' arg(1) ':' arg(2) '==>'),
                        arg(2)
endProcedure assert

/*--- abend with Message after displaying help -----------------------*/
errHelp: procedure expose m.
parse arg msg
    call errSay 'i}'msg
    call help 0
    call err 'i}'msg
endProcedure errHelp

/*--- display the first comment block of the source as help ----------*/
help: procedure expose m.
parse arg doClear
    if doClear \== 0 then
        address tso 'clear'
    parse source . . s3 .
    say right(' help for rexx' s3, 72, '*')
    do lx=1 while pos('/*', sourceLine(lx)) < 1
        if lx > 10 then
            return err('initial commentblock not found for help')
        end
    doInc = 1
    ho = m.err.helpOpt
    do lx=lx+1 to sourceline() while pos('*/', sourceline(lx)) = 0
        li = strip(sourceLine(lx), 't')
        cx = lastPos('{', li)
        if cx > 0 then do
            if length(ho) = 1 then
                doInc = cx = length(li) | pos(ho, li, cx+1) > 0
            li = left(li, cx-1)
            end
        if doInc then
            say li
        end
    say right(' end help for rexx' s3, 72, '*')
    return 4
endProcedure help

/* copy err end   *****************************************************/
/* copy ut begin  *****************************************************/
utIni: procedure expose m.
    if m.ut_ini == 1 then
        return
    m.ut_ini = 1
    m.ut_digits = '0123456789'
    m.ut_alfLC  = 'abcdefghijklmnopqrstuvwxyz'
    m.ut_alfUC  = translate(m.ut_alfLc)
    m.ut_Alfa   = m.ut_alfLc || m.ut_alfUC
    m.ut_alfNum = m.ut_alfa || m.ut_digits
    m.ut_alfDot = m.ut_alfNum || '.'
    m.ut_alfId  = m.ut_alfNum'_' /* avoid rexx allowed @ # $ ¬ . | ? */
    m.ut_alfIdN1 = m.ut_digits    /* not as first character */
    m.ut_rxId   = m.ut_AlfNum'@#$?' /* charset puff mit ¬*/
    m.ut_rxDot  = '.'m.ut_rxId
    m.ut_rxN1   = '.0123456789'
    m.ut_alfPrint = m.ut_alfNum'+-*/=()[]{}<> .:,;?|''"%&#@$£\_'
    return
endProcedure utIni
/*--- if function  warning all3 arguments get evaluated|
                   e.g if(x=0, 'infinity', 1/0) will fail| -----------*/
if: procedure expose m.
parse arg co, ifTrue, ifFalse
    if co then
        return ifTrue
    else
        return ifFalse
endProcedure if
/*--- embedded ASSignement:
      assign the second argument to the variable with name in first arg
      and return the value assigned ----------------------------------*/
ass:
    call value arg(1), arg(2)
    return arg(2)
/*--- embedded ASSignement return NotNull:
      assign the second argument to the variable with name in first arg
      and return 1 if value not null, 0 if null ----------------------*/
assNN:
    call value arg(1), arg(2)
    return arg(2) \== ''

/*--- return current time and cpu usage ------------------------------*/
utTime: procedure expose m.
    return time() 'ela='time('E') 'cpu='sysvar('syscpu'),
            'su='sysvar('syssrv')

/*--- sleep several seconds ------------------------------------------*/
sleep: procedure expose m.
parse arg secs, sayIt
    if sayit <> 0 then
        say 'sleeping' secs 'secs' time()
    CALL SYSCALLS 'ON'
    ADDRESS SYSCALL "sleep" secs
    CALL SYSCALLS 'OFF'
    if sayit <> 0 then
        say 'slept' secs 'secs' time()
    return
endProcedure sleep

/*--- left without truncation ----------------------------------------*/
lefPad: procedure expose m
parse arg s, len
    if length(strip(s, 't')) >= len then
        return strip(s, 't')
    return left(s, len)
endProcedure lefPad

/*--- right without truncation ---------------------------------------*/
rigPad: procedure expose m
parse arg s, len
    if length(strip(s, 'l')) >= len then
        return strip(s, 'l')
    return right(s, len)
endProcedure rigPad

/*--- quote string txt using quoteChar qu ("" ==> ") -----------------*/
quote: procedure expose m.
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

/*--- translate to lowercase -----------------------------------------*/
ut2lc: procedure expose m.
parse arg s
    return translate(s, m.ut_alfLc, m.ut_alfUc)

/*--- verify an id ---------------------------------------------------*/
verifId: procedure expose m.
    parse arg src, extra
    if pos(left(src, 1), m.ut_alfIdN1) > 0 then
        return 1
    else
        return verify(src, m.ut_alfId || extra, 'n')

/*--- return the count of occurrences of needle in heyStack ----------*/
posCount: procedure expose m.
parse arg needle, hayStack, start, fin
    if start = '' then
        start = 1
    if fin = '' then
        fin = length(hayStack) + 1 - length(needle)
    do cnt = 0 by 1
        start = pos(needle, haystack, start)
        if start < 1 | start > fin then
             return cnt
        start = start + length(needle)
        end
endProcedure posCount

repAll: procedure expose m.
parse arg src
    do ax = 2 by 2 to arg()
        src = repAl2(src, src, arg(ax), arg(ax+1))
        end
    return src
endProcedure repAll

repAl2: procedure expose m.
parse arg src, sPos, old, new
    res = ''
    cx = 1
    do forever
        nx = pos(old, sPos, cx)
        if nx < 1 then
            return res || substr(src, cx)
        res = res || substr(src, cx, nx-cx) || new
        cx = nx + length(old)
        end
endProcedure repAl2

repAllWords: procedure expose m.
parse arg src, w, new
    res = ''
    wx = 0
    cx = 1
    do forever
        wx = wordPos(w, src, wx+1)
        if wx < 1 then
            return res || substr(src, cx)
        nx = wordindex(src, wx)
        res = res || substr(src, cx, nx-cx) || new
        cx = nx + length(w)
        end
endProcedure repAllWords

utc2d: procedure expose m.
parse arg ch
    cx = length(ch) // 3
    if cx = 0 then
        cx = 3
    res = c2d(left(ch, cx))
    do cx=cx+1 by 3 to length(ch)
        res = res * 16777216 + c2d(substr(ch, cx, 3))
        end
    return res

utInter: procedure expose m.
    interpret arg(1)
    return
endProcedure utInter

/* copy ut end ********************************************************/