zOs/REXX/TECSVUNL

/* rexx **************************************************************
   tecSvUnl <dbSys>
   for XC/XR -- EOS / eRet
       searches DatasetNames for XC and XR unloads and punches
       loads all partitions from db2Catalog / XC&XR Controltables
           into oa1p.tQZ005ecSvUnl
           with stage, staUpd, unl and Pun DatasetNames and timestamps
   for XB -- ELAR
       selects stage, unload from meta tables and computes punch

25. 1.16 wk Elar: cleanup sql, xba part=1 ok
 8.12.15 wk Elar: unload&punch for www tables / mig off
25. 9.15 wk ignore views for elar load (for RZZ)
20. 9.15 wk add punch for elar
 6. 7.15 wk neu
*********************************************************************/
call errReset 'h'
call timeIni
call mapIni
parse upper arg dbSys
if dbSys == '' then dbSys = dp4g
if length(dbSys) <> 4 | \ abbrev(dbSys, 'D') then
    call errHelp 'bad dbSys:' dbSys
m.uTb = OA1P.tQZ005TecSvUnload
m.strt = timestampNow()
say m.strt 'start tecSvUnl v260116' dbSys 'refresh tecSvUnload'
call sqlConnect dbSys
if wordPos(dbSys, 'DBOF DE0G') > 0 then do
    say 'search xc/xr unloads'
    call loadCtrl
    m.sg_errX = m.sg.0
    m.infoErr = ''
    call recPun 'XC.XC01A1P.A2*.**'
    call recPun 'XC.XC01A1P.A5*.**'
    call recPun 'XR.XR01A1P.A2*.**'
    call delInsert
    do ex = 1 to words(m.infoErr)
        e1 = word(m.infoErr, ex)
        say e1 m.infoErr.e1
        end
    end
else if wordPos(dbSys, 'DVBP DEVG') > 0 then do
    say 'collect ELAR metaInfo'
    call elarDelIns
    end
call sqlDisconnect
say  timestampNow() 'tecSvUnl end'
exit

/*--- find sysRec und sysPun for each partition
          from datasetList
      solves the following problems
          1) dsn of sysRec and sysPun are not stored in stage tables
          2) there is only one pun for several partitions
          3) &uniq for pun and rec does NOT agree
      we look for the newest punch of this TS after the unload
---------------------------------------------------------------------*/
recPun: procedure expose m.
parse arg msk
    call csiOpen cq, msk
    pp = 0
    cRec = 0
    cDup = 0
    cPun = 0
    do cx=0
        if \ csiNext(cq, cr) then
            m.cr = '?end'
        parse var m.cr p '.' db '.' ts '.' pa '.' ty '.' ti
        if \ abbrev(m.cr, pr) then do   /* change of table space */
            if  cx \== 0 then do        /* handle old TS */
                cRec = cRec + m.lr.0
                cPun = cPun + m.lp.0
                call sort lp, lq, '>>=' /* sort punch by timestamp */
                do lx=1 to m.lr.0       /* for each unl search pun */
                    rt = word(m.lr.lx, 1)
                    do ly=1 to m.lq.0 while rt << m.lq.ly
                        end
                    if ly > 1 & (ly > m.lq.0 | rt >>= m.lq.ly) then
                        ly = ly - 1
                    if ly > 0 then
                        call unlPunPut  m.lr.lx, m.lq.ly
                    else
                        call unlPunPut  m.lr.lx
                    end
                end
            if m.cr == '?end' then do
                say timeStampNow() 'recPun' msk':' cx 'dsns,' ,
                       cPun 'pun,' cRec 'rec,' cDup 'dups'
                return
                end
            pr = p'.'db'.'ts'.'           /* ini fields for new TS */
            m.lp.0 = 0
            m.lr.0 = 0
            end
                                          /* analyze fields in dsn */
        if verify(pa, '0123456789', 'n',2) >0 | \abbrev(pa,'P') then do
             call addErr db, ts, , '-' m.cr, , ', badPart' pa
             iterate
             end
        if ti == '' then
            iterate
        err = ''
        if length(ti) == 8 then
            tf = timeLrsn2LZT(timeUniq2Lrsn(ti))
        else if \(translate(ti, 000000000, 123456789)='D000000')then do
            tf = m.timestamp_01
            err = err', badDate' ti
            end
        else do
            tf = '20'translate('12-34-56', substr(ti, 2), '123456'),
                  ||    '-00.00.00'
            e1 = timestampCheck(tf)
            if e1 <> '' then do
                err = err', badDate' ti'>'e1
                tf = m.timestamp_01
                end
            end
        if ty == 'SYSPCH' then
            call mAdd lp, tf m.cr err
        else if ty == 'SYSREC' then do
            ly = m.lr.0
            lz = word(m.lr.ly, 2)
            if \ abbrev(lz, pr || pa) then
                call mAdd lr, tf m.cr err
            else do        /* use newest Rec and put old in error */
                cDup = cDup + 1
                if tf << m.lr.ly then
                    m.lr.ly = m.lr.ly err', dupRec' ti
                else
                    m.lr.ly = tf m.cr subWord(m.lr.ly, 3) err,
                         ', dupRec' substr(word(m.lr.ly, 2),
                             ,  lastPos('.', word(m.lr.ly, 2))+1)
                end
            end
        else
            call errAdd db, ts, pa, tf m.cr, , ', badType' ty
        end
    return
endProcedure recPun

/*--- put rec and pun in StaGetable stem ----------------------------*/
unlPunPut: procedure expose m.
parse arg unTs aUn e1, puTs aPu e2, e3
    parse value aUn with p '.' db '.' ts '.P' pa '.' ty '.' ti
    ee = e1 e2 e3
    if aPu = '' then do
        ee = ',noPunch:' ee
        puTs = m.timestamp_01
        end
    else do
        diff = timestampDiff(puTs, unTs)
        if (diff < 0 | diff > 0.4) ,
            /* ??? & ( diff > 4 | m.lq.ly >> '2015-01-10' ) */ then
            ee = ee', punNotSoon' diff
        end
    if 0 & ee <> '' then
        say db ts substr(pa, 2) 'unl' unTs aUn 'pun' puTs aPu ee
    ky = db'.'ts'.'format(pa)
    if symbol('m.sg.ky') <> 'VAR' then do
        call addErr db, ts, format(pa), unTs aUn, puTs aPu,
            , ', notInCtrlTb'ee
        return
        end
    else if m.done.ky = 1 then
        call err', alreadyDone:' k
    m.done.k = 1
    o = m.sg.ky
    if m.o.unl <> '' then
        call err ky 'unl already set' m.o.unl
    m.o.unlTst = unTs
    m.o.unl = aUn
    m.o.punTst = puTs
    m.o.pun = aPu
    call putInfoErr o, ee
    return
endProcedure unlPunPut

putInfoErr: procedure expose m.
parse arg gg, aErr
    ee = m.gg.err',' aErr',' m.gg.info
    rE = ''
    RI = ''
    do while ee <> ''
        parse var ee a ',' ee
        if a = '' then
           iterate
        parse var a a1 a2
        if wordPos(a1, m.infoErr) > 0 then
            m.infoErr.a1 = m.infoErr.a1 + 1
        else do
            m.infoErr = m.infoErr a1
            m.infoErr.a1 = 1
            end
        if a1 = 'dupRec' then
            rI = rI',' a
        else
            rE = rE',' a
        end
    m.gg.info = space(substr(rI, 3), 1)
    m.gg.err  = space(substr(rE, 3), 1)
    return
endProcedure putInfoErr

addErr: procedure expose m.
    m.sg_errX = m.sg_errX + 1
    gg = 'SG.'m.sg_errX
    parse arg m.gg.db, m.gg.ts, m.gg.pa,
        , m.gg.unlTst m.gg.unl e1, m.gg.punTst m.gg.pun e2, e3
    ee = e1 e2 e3
    ky = m.gg.db'.'m.gg.ts'.'m.gg.pa
    if m.done.ky = 1 | \ datatype(m.gg.pa, 'n') ,
           | verify(m.gg.pa, '+-0123456789') <> 0 ,
           | length(m.gg.pa) > 4 then do
        ee = ',pa='m.gg.pa ee
        m.gg.pa =  m.sg.0 - m.sg_errX
        if m.gg.pa <= -3000 then do
            ee = ',db='m.gg.db ee
            m.gg.db = 'e'm.gg.pa
            m.gg.pa = -30000
            end
        end
    m.gg.stage = 'er'
    m.gg.staUpd = m.timestamp_01
    m.gg.staTb  = ''
    if timestampCheck(m.gg.unlTst) <> '' then
        m.gg.unlTst = m.timestamp_01
    if timestampCheck(m.gg.punTs) <> '' then
        m.gg.punTs = m.timestamp_01
    m.gg.info = ''
    m.gg.err  = ''
    call putInfoErr gg, ee
    return
endProcedure addErr
/*                                      select from stage Control tables
$</loadCtrl/
   select t.dbname db, t.tsname ts, p.partition pa
          , value(XC106_DOC_STATE, XC406_PART_STATUS, xr106_DOC_STATE
              , '-m' ) stage
          , value(XC106_TS_UPDATE, XC406_UPDATE_TS  , xr106_TS_UPDATE
              , '1111-11-11-11.11.11.111111') staUpd
          , case when XC106_DOC_STATE   is not null then '1'
                 when XC406_PART_STATUS is not null then '4'
                 when Xr106_doc_state   is not null then 'r'
                 else ''
            end  staTb
          , '1111-11-11-11.11.11.111111' unlTst, '' unl
          , '1111-11-11-11.11.11.111111' punTst, '' pun
          ,  '' info
          , '' err
     from sysibm.systables t
       join sysibm.sysTablePart p
         on t.dbName = p.dbName and t.tsName = p.tsName
       left join OA1P.TXC106A1
         on t.name = 'TXC200A1'
            and t.creator
                 = 'OA1P' || substr(xc106_doc_tabColId, 3, 2)
            and xc106_doc_tabColId
                 = 'XC' || substr(t.creator, 5, 2)
            and smallInt(xc106_doc_part_no) = p.partition
            and xc106_doc_part_no = right('0000' || p.partition, 4)
       left join OA1P.TXC406A1
         on t.name like 'TXC5%'
            and t.name = xc406_table_name
            and smallInt(xc406_part_number) = p.partition
            and xc406_part_number = right('000' || p.partition, 3)
       left join OA1P.Txr106A1
         on t.name like 'TXR2%'
            and t.name = xr106_doc_tb_name
            and smallInt(xr106_doc_part_no) = p.partition
            and xr106_doc_part_no = right('000' || p.partition, 3)
       where (t.dbName = 'XC01A1P'
                  AND (t.tsName LIKE 'A2%' or t.tsName LIKE 'A5%' )
                  AND NOT (t.tsName = 'A500A'))
           or (t.dbName = 'XR01A1P' and t.tsName LIKE 'A2%')
       order by t.dbName, t.tsName, p.partition
$/loadCtrl/
*/
/*--- load partition stage info from table into stem SG -------------*/
loadCtrl: procedure expose m.
    sql = sqlCat(mapInline('loadCtrl'), ' ')
    call sql2st sql, sg
    say  timestampNow() m.sg.0 'rows from stage tables'
    do sx=1 to m.sg.0
        k = strip(m.sg.sx.db)'.'strip(m.sg.sx.TS)'.'format(m.sg.sx.PA)
        if symbol('m.sg.k') == 'VAR' then
            call err 'duplicate' k
        m.sg.k = 'SG.'sx
        end
    return
endProcedure loadCtrl

/*
$</elarIns/
insert into oa1p.tqz005TecSvUnload
with sU (area, n, seg, pa, stage, sTb, hkTS, laIm) as
( --- union of segments and infos for new tables --------------------
  select storageArea, storageArea_N, segment, partNumber, stage
      , 'i', LASTHKTS, lastImport
    from  BUA.TXBI003 R
  union all select '?', enStorAr, right('000' || enSeg, 3), 1, '-a'
      , 'a', cast(null as timestamp), cast(null as timestamp)
    from bua.txba201
)
, sG (area, n, areaC, seg, pa, stage, sTb, hkTS, laIm, err) as
( --- group numeric Area, seg and pa
  ---       compute alpha area from numeric area -------------------
  select min(area), n
       , case when n <= 999 then right('000' || n, 3)
              when n <= 35657                /*  1296 = 36**2 */
                then substr('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                           , (n + 10998) / 1296 + 1, 1)
                  || substr('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                           , mod(n + 10998, 1296) / 36 + 1, 1)
                  || substr('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                           , mod(n + 10998, 36) + 1, 1)
         end
       , seg, pa
       , max(stage)
       , max(sTb)
       , max(hkTS)
       , max(laIm)
       , case when sum(case when sTb = 'i' then 1 else 0 end) > 1
                 then ' multiXbi003' else '' end
       ||case when sum(case when sTb = 'a' then 1 else 0 end) > 1
                 then ' multiXba201' else '' end
    from sU
    group by n, seg, pa
)
, seg(t8, pa, stage, sTb, hkTs, laIm, err) as
( --- compute t8 = eaDba = 'XB' || area || seg ----------------------
  select 'XB' || areaC || seg
      , smallInt(pa), stage, sTb, hkTs, laIm
      , case when area <> '?' and area <> areaC
                     then ' areaC<>' || area else '' end || err
    from sG
)
, uU (eaDba, db, ts, pa, unl, sTb) as
( --- union of both unload tables -----------------------------------
  select eaDba, substr(earess, 4, 8)
        , substr(earess, 13
            , min(8, locate('.', earess || '.', 13) - 13))
        , partNumber pa, eaRess, 'c'
    from BUA.TXBC021 t
    where EYRESS =  5000 and ESRESS =  0
  union all select eaDba, substr(earess, 4, 8)
        , substr(earess, 13
            , min(8, locate('.', earess || '.', 13) - 13))
        , partNumber pa, eaRess, 's'
    from BUA.TXBC021s t
    where EYRESS =  5000 and ESRESS =  0
)
, uG (eaDba, db, ts, pa, unl, sTb, err) as
( --- group uU -----------------------------------------------------
  select max(eaDba), db, ts, pa, max(unl), max(sTb)
      , case when count(*) <> 1
                 then ' multiUnl-' || min(sTb) || '-' || max(sTb)
             else '' end
    from uU
    group by db, ts, pa
    --- without fetch first or order by we get
    ---  SQLCODE = -171: THE DATA TYPE, LENGTH, OR VALUE OF ARGUMENT
    ---     1 OF INSTR OR LOCATE_IN_STRING IS INVALID   ------------
    fetch first 2147483647 rows only
)
, unl(eaDba, db, ts, pa, unl, pun, sTb, err) as
( --- check unl, derive pun ----------------------------------------
  select eaDba, db, ts, pa
      , value(unl, '')
      , case when unl is null or unl = '' then ''
             when locate_in_string(unl, '.', -1, 2) < 1 then ''
             when substr(unl, locate_in_string(unl, '.', -1, 2) + 1
                            , locate_in_string(unl, '.', -1, 1)
                            - locate_in_string(unl, '.', -1, 2) - 1)
                  <> 'SYSREC' then ''
             else left(unl, locate_in_string(unl, '.', -1, 2) )
                || 'SYSPCH'
             end
      , sTb
      , case when unl is null or unl = '' then ''
             when unl not like 'XB.XB%' then ' unlNotXB.XB%'
             when locate('.', unl, 4) <> 12 then ' unlDbLen'
             when locate('.', unl, 13) not between 14 and 21
                    then ' unlTsLen'
             when locate_in_string(unl, '.', -1, 2) <1 then ' unl<2q'
             when substr(unl, locate_in_string(unl, '.', -1, 2) +1
                            , locate_in_string(unl, '.', -1, 1)
                            - locate_in_string(unl, '.', -1, 2) - 1)
                  <> 'SYSREC' then ' sysrecNotInUnl'
            else ''
        end || err
    from uG
)
, tp (db, ts, pa, t8, err) as
( --- tablePart and tables from db2 catalog -------------------------
  select t.dbName, t.tsName, partition, max(left(t.name, 8))
      , case when max(left(t.name, 8)) <> min(left(t.name, 8))
           then ' multiTables' else '' end
    from sysibm.sysTables t
      join sysibm.sysTablePart p on
         t.dbName = p.dbName and t.tsName = p.tsName
    where t.dbName like 'XB%'
        and t.type not in ('A', 'V')
    group by t.dbName, t.tsName, partition
)
, j2 (db, ts, pa, t8, unl, pun, sTb, err) as
( --- join unl and tp ------------------------------------------------
  select value(tp.db, unl.db)
      , value(tp.ts, unl.ts)
      , value(tp.pa, unl.pa)
      , value(tp.t8, unl.eaDba) eaDba
      , unl.unl
      , unl.pun
      , unl.sTb
      , case when tp.db is null then ' notInDB2' else '' end
      || case when tp.t8 <> unl.eaDba then ' t8<>eaDba=' || unl.eaDba
              else '' end
      || value(unl.err, '') || value(tp.err, '')
    from tp
      full outer join unl
          on tp.db = unl.db and tp.ts = unl.ts and tp.pa = unl.pa
)
--- select count(*), err from j2 group by err;x;
, j3 (db, ts, pa, t8, stage, unl, pun, sTb, hkts, err) as
( --- join segments -------------------------------------------------
  select char(value(j2.db, '-noDB-'), 8)
      , char(value(j2.ts, j2.t8, seg.t8, '-noTsT8-'), 8) --- avoid dups
      , smallInt(value(j2.pa, seg.pa, -99))
      , char(value(j2.t8, seg.t8, '-noT8-'), 8)
      , case when seg.stage is null and j2.ts like '%WWW%' then '-w'
             else value(seg.stage, '-m') end
      , case when seg.stage is null and j2.ts like '%WWW%'
                 then 'XB.MIG.U.' || db || '.' || ts
                     || '.P' || right('0000'|| j2.pa, 5)||'.REC.D15338'
             else value(j2.unl, '') end
      , case when seg.stage is null and j2.ts like '%WWW%'
                 then 'XB.MIG.U.' || db || '.' || ts  || '.PUN.D15338'
             else value(j2.pun, '') end
      , value(seg.sTb, '') || value(j2.sTb, '')
      , value(seg.hkts, '1111-11-11-11.11.11.111111')
      , case when j2.db is null then ' notInDB2' else '' end
        || value(seg.err, '') || value(j2.err, '')
    from j2
      full outer join seg
          on j2.t8 = seg.t8 and j2.pa = seg.pa
)
, j (db, ts, pa, stage, unlTst, unl, pun, sTb, t8, err) as
( --- final values and errors ---------------------------------------
    select db, ts,pa, stage
      , case when stage = '-w' then '2015-12-04-16.00.00.000000'
                  else hkts end
      , unl, pun, sTb, t8
      , strip(case when stage = '-m' then ' notInXbi003,Xba201'
             when stage not in ('RW', 'CL', 'UL', 'DL', '-a', '-w')
                 then ' badStage=' || stage
             when stage in ('UL', 'DL', '-w') and unl = ''
                 then ' noUnload '
             when stage not in ('CL', 'DL', 'UL', '-w') and unl <> ''
                 then ' unloadInBadStage'
             else '' end
      ||case when left(unl, 21) <> left(pun, 21) then ' prefUnl<>Pun'
             else '' end || err)
    from j3
)
select db, ts, pa, stage
      , '1111-11-11-11.11.11.111111'
      , value(sTb, '')
      , unlTst
      , unl
      , '1111-11-11-11.11.11.111111'
      , pun
      , ''
      , err
    from j
$/elarIns/
 */
/*--- load partition stage info from table into stem SG -------------*/
elarDelIns: procedure expose m.
    call sqlUpdate , 'delete from' m.uTb
    call sqlUpdate 1, sqlCat(mapInline('elarIns'), ' ')
    call insRefreshCommit 'elar' m.sql.1.updateCount 'inserts'
    return
endProcedure elarIns

/*--- cat the lines of a stem after strip and -- commenting ---------*/
sqlCat: procedure expose m.
parse arg st, sep
    res = ''
    do sx=1 to m.st.0
        if pos('--', m.st.sx) < 1 then
            res = res || strip(m.st.sx)' '
        else
            res = res || strip(left(m.st.sx, pos('--', m.st.sx)-1))' '
        end
    return res
endProcedure sqlCat

/*--- delete and reload partition table -----------------------------*/
delInsert: procedure expose m.
    call sqlUpdate , 'delete from' m.uTb
    call sqlUpdatePrepare 7, 'insert into' m.uTb,
         'values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
    cUnl = 0
    cTru = 0
    cErr = 0
    do dx=1 to m.sg_errX
        o = 'SG.'dx
        ii = space(m.o.info, 1)
        ee = space(m.o.err , 1)
        cUnl = cUnl + (m.o.unl <> '')
        cErr = cErr + (ee <> '')
        if length(ii) > 70 then do
            ii = left(ii, 67)'...'
            ee = 'truncInfo' ee
            cTru = cTru + 1
            end
        if length(ee) > 70 then do
            ee = left('truncErr' ee, 67)'...'
            cTru = cTru + 1
            end
        call sqlUpdateExecute 7 , m.o.db, m.o.ts, m.o.pa ,
                  , m.o.stage, m.o.staUpd, m.o.staTb ,
                  , m.o.unlTst, m.o.unl, m.o.punTst, m.o.pun ,
                  , ii, ee
        end
    call insRefreshCommit m.sg.0 "parts," cUnl "unloads," ,
                       cErr "errors, "cTru "truncates"
    say now "reload:" m.sg.0 "parts," cUnl "unloads," ,
              cErr "errors," cTru "truncates"
    return
endProcedure delInsert

insRefreshCommit: procedure expose m.
parse arg info, err
    now = timestampNow()
    call sqlUpdate , "insert into" m.uTb ,
             "values('', '', -99, '-r', '"m.strt"', ''" ,
                 || ", '"m.strt"', 'refresh begin'" ,
                 || ", '"now"', 'refresh end'" ,
                 || ", '"info"', '"err"')"
    call sqlCommit
    return
endProcedure insRefreshCommit

/* 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 sort begin ****************************************************/
sortComparator: procedure expose m.
parse arg cmp, le, ri
    if cmp == '' then
        cmp = '<<='
    if length(cmp) < 6 then
        m.sort_comparator = 'cmp =' le cmp ri
    else if pos(';', cmp) < 1 then
        m.sort_comparator = "aLe =" le"; aRi =" ri"; cmp =" cmp
    else
        m.sort_comparator = "aLe =" le "; aRi =" ri";" cmp
    return
endProcedure sort

sortWords: procedure expose m.
parse arg wrds, cmp
    if words(wrds) <= 1 then
        return strip(wrds)
    m.sort_ii.0 = words(wrds)
    do sx=1 to m.sort_ii.0
        m.sort_ii.sx = word(wrds, sx)
        end
    call sort sort_ii, sort_oo, cmp
    r = m.sort_oo.1
    do sx=2 to m.sort_oo.0
        r = r m.sort_oo.sx
        end
    return r
endProcedure sortWords

sortWordsQ: procedure expose m.
parse arg wrds, cmp
    call sortComparator cmp, 'word(le, lx)', 'word(ri, rx)'
    return strip(sortWord1(wrds))
endProcedure sortWordsQ

sortWord1: procedure expose m.
parse arg wrds
    if words(wrds) <= 1 then
        return wrds
    h = words(wrds) % 2
    le = sortWord1(subWord(wrds, 1, h))
    ri = sortWord1(subWord(wrds, h+1))
    lx = 1
    rx = 1
    res = ''
    do forever
        interpret m.sort_comparator
        if cmp then do
            res = res word(le, lx)
            if lx >= words(le) then
                return res subword(ri, rx)
            lx = lx + 1
            end
        else do
            res = res word(ri, rx)
            if rx >= words(ri) then
                return res subword(le, lx)
            rx = rx + 1
            end
        end
endProcedure sortWord1

sort: procedure expose m.
parse arg i, o, cmp
    call sortComparator cmp, 'm.l.l0', 'm.r.r0'
    call sort1 i, 1, m.i.0, o, 1, sort_work, 1
    m.o.0 = m.i.0
    return
endProcedure sort

sort1: procedure expose m.
parse arg i, i0, le, o, o0, w, w0
    if le <= 3 then do
        if le = 3 then do
            call sortMerge i, i0, i0+1, i, i0+1, i0+2, w, w0
            call sortMerge i, i0+2, i0+3, w, w0, w0+2, o, o0
            end
        else if le = 2 then
            call sortMerge i, i0, i0+1, i, i0+1, i0+2, o, o0
        else if le = 1 then
            m.o.o0 = m.i.i0
        return
        end
    h = (le + 1) % 2
    call sort1 i, i0,   h,    o, o0+le-h, w, w0
    call sort1 i, i0+h, le-h, w, w0,      o, o0
    call sortMerge o, o0+le-h, o0+le, w, w0, w0+le-h, o, o0
    return
endProcedure sort1

sortMerge: procedure expose m.
parse arg l, l0, le, r, r0, re, o, o0
    do while l0 < le & r0 < re
        interpret m.sort_comparator
        if cmp then do
            m.o.o0 = m.l.l0
            l0 = l0 + 1
            end
        else do
            m.o.o0 = m.r.r0
            r0 = r0 + 1
            end
        o0 = o0 + 1
        end
    do while l0 < le
        m.o.o0 = m.l.l0
        l0 = l0 + 1
        o0 = o0 + 1
        end
    do while r0 < re
        m.o.o0 = m.r.r0
        r0 = r0 + 1
        o0 = o0 + 1
        end
    return
endProcedure sortMerge
/* copy sort end   ****************************************************/
/* copy map begin ******************************************************
    a map stores values at keys
    it may also maintain a list of keys
    the basic ideas are similar to the java Interface java.util.Map
    contrary to stems we also handle keys longer then 250 bytes
***********************************************************************/
/*--- initialize the module ------------------------------------------*/
mapIni: procedure expose m.
    if m.map_ini = 1 then
        return
    m.map_ini = 1
    call mIni
    m.map.0 = 0
    m.map_inlineSearch = 1
    call mapReset map_inlineName, map_inline
    return
endProcedure mapIni

mapInline: procedure expose m.
parse arg pName, opt
    if mapHasKey(map_inlineName, pName) then do
        im = mapGet(map_inlineName, pName)
        if pos('l', opt) < 1 & symbol('m.im.0') \== 'VAR' then do
            m.im.0 =  m.im.lEnd - m.im.lBegin - 1
            do ix=1 to m.im.0
                m.im.ix = strip(sourceline(ix+m.im.lBegin), 't')
                end
            end
        return im
        end
    name = '/'
    do lx = m.map_inlineSearch to sourceline()
        if \ abbrev(sourceline(lx), '$') then
            iterate
        li = sourceline(lx)
        s1 = pos('/', li)+ 1
        if s1 < 3 | s1 > 4 then
            iterate
        s2 = pos('/', li, s1)
        if s2 <= s1 then
            iterate
        if s1 == 3 then do
            if name \== substr(li, s1, s2-s1) then
                iterate
            im = 'map_inline.' || (m.map_inline.0+1)
            call mapAdd map_inlineName, name, im
            m.im.lBegin = lBeg
            m.im.lEnd = lx
            m.im.mark = mrk
            if name == pName then do
                m.map_inlineSearch = lx+1
                return mapInline(pName)
                end
            name = '/'
            end
        else if \ mapHasKey(map_inlineName,
                , substr(li, s1, s2-s1)) then do
            lBeg = lx
            mrk = substr(li, 2, s1-3)
            name = substr(li, s1, s2-s1)
            end
        else do
            name = '/'
            end
        end
    if pos('r', opt) > 0 then
        return ''
    return err('no inline data /'pName'/ found')
endProcedure mapInline

/*--- create a new map ----------------------------------------------*/
mapNew: procedure expose m.
parse arg opt
    m.map.0 = m.map.0 + 1
    return mapReset('MAP.'m.map.0 , opt)
endProcedure mapNew

/*--- make an empty map, if opt <> '' maintain stem of keys
                ('K' in map_keys, '=' in a else in opt) --------------*/
mapReset: procedure expose m.
    parse arg a, opt
    if symbol('m.map_keys.a') == 'VAR' then
        call mapClear a
    if opt = '=' then
        st = a
    else if translate(opt) = 'K' then
        st = 'MAP_KEYS.'a
    else
        st = opt
    m.map_keys.a = st
    if st \== '' then
        m.st.0 = 0
    return a
endProcedure

/*--- add a new key value pair to the map ----------------------------*/
mapAdd: procedure expose m.
parse arg a, ky, val
    vv = mapAdr(a, ky, 'a')
    if vv == '' then
        return err('duplicate in mapAdd('a',' ky',' val')')
    m.vv = val
    return val
endProcedure mapAdd

/*--- change the value at a key or add key value ---------------------*/
mapPut: procedure expose m.
parse arg a, ky, val
    vv = mapAdr(a, ky, 'p')
    m.vv = val
    return val
endProcedure mapPut

/*--- return 1 if key ky exists in map a, 0 otherwise ----------------*/
mapHasKey: procedure expose m.
parse arg a, ky
    return mapAdr(a, ky, 'g') \== ''
endProcedure mapHasKey

/*--- return the value of key ky in map a if it exists,
          else if called with a third argument return third argument
          else issue an error ----------------------------------------*/
mapGet: procedure expose m.
parse arg a, ky
    vv =  mapAdr(a, ky, 'g')
    if vv \== '' then
        return m.vv
    else if arg() > 2 then
        return arg(3)
    else
        return err('missing key in mapGet('a',' ky')')
endProcedure mapGet

/*--- return a stem of all keys --------------------------------------*/
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

/*--- remove a key from the map, do nothing if it is missing ---------*/
mapRemove: procedure expose m.
parse arg a, ky
    vv = mapAdr(a, ky, 'g')
    if vv == '' then
        return ''
    if m.map_keys.a \== '' then do
        trace ?R /* not tested yet ???wkTest */
        k = m.map_keys.a
        mx = m.k.0
        do i=1 to mx
            if m.k.i == ky then do
                m.k.i = m.k.mx
                m.k.0 = mx - 1
                return
                end
            end
        end
    val = m.vv
    drop m.a.ky
    return val
endProcedure mapRemove

/*--- remove all entries ---------------------------------------------*/
mapClear: procedure expose m.
parse arg a
    st = mapKeys(a)
    liLe = 247 - length(a)
    do kx=1 to m.st.0
        ky = m.st.kx
        drop m.st.kx
        if length(ky) < liLe then do
            drop m.a.ky
            end
        else do
            adr = mapAdr(a, ky, 'g')
            if adr \== '' then do
                ha = left(adr, length(adr) - 2)
                do i = 1 to m.ha.0
                     vv = ha'v'i
                     drop m.ha.i m.vv
                     end
                 drop m.ha.0
                 end
            end
        end
    m.st.0 = 0
    return a
endProcedure mapClear

/*--- return the value pointer for a key, '' if non existing
    f = 'g' return address if exists otherwise ''
        'p' return address if exists otherwise newly added address
        'a' return ''      if exists otherwise newly added address ---*/
mapAdr: procedure expose m.
parse arg a, ky, f
    if length(ky) + length(a) < 247 then do
        res = a'.'ky
        if symbol('m.res') == 'VAR' then
            return copies(res, f \== 'a')
        else if f == 'g' then
            return ''
        end
    else do
        len = length(ky)
        q = len % 2
        ha = a'.'len || left(ky, 80) || substr(ky,
            , len % 2 - 40, 85-length(len)-length(a)) || right(ky, 80)
        if symbol('M.ha.0') == 'VAR' then do
            do i=1 to m.ha.0
                if m.ha.i == ky then
                    return copies(ha'v'i, f \== 'a')
                end
            end
        else do
            i = 1
            end
        if f == 'g' then
            return ''
        if i > 9 then
            call err 'overflow long key' y 'in map' a 'hash' ha'.K.'i
        m.ha.0 = i
        m.ha.i = ky
        res = ha'v'i
        end
    if m.map_keys.a \== '' then
        call mAdd m.map_keys.a, ky
    return res
endProcedure mapAdr

/* copy map end *******************************************************/
/* 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 with separator ---------------------------*/
mCat: procedure expose m.
parse arg st, sep
    if m.st.0 < 1 then
        return ''
    res = m.st.1
    do sx=2 to m.st.0
        res = res || sep || m.st.sx
        end
    return res
endProcedure mCat

mIni: procedure expose m.
    if m.m_ini == 1 then
        return
    m.m_ini = 1
    call utIni
    m.m_area.0 = 0
    call mNewArea
    return
endProcedure mIni

/* copy m end *********************************************************/
/* copy time begin ****************************************************
 timestamp format yz34-56-78-hi.mn.st.abcdef
 11.12.14 wk: added lrsn2uniq
 11.05.13 wk: numeric digits transparent: in jeder Procedure drin
              time2jul, tst externalisiert
**********************************************************************/
/*--- timestamp to julian --------------------------------------------*/
time2jul: procedure expose m.
parse arg yyyy '-' mm '-' dd '-'
    if yyyy < 1100 then
        yyyy = 11 || right(yyyy, 2, 0)
        /* date function cannot convert to julian, only from julian
           use b (days since start of time epoch) instead     */
    return right(yyyy, 2) ,
         || right(date('b', yyyy || mm || dd, 's') ,
                - date('b', yyyy-1 || '1231', 's') , 3, 0)
endProcedure time2jul

/*--- current timestamp ---------------------------------------------*/
timestampNow: procedure expose m.
    parse value date('s') time('l') with y 5 m 7 d t
    return y'-'m'-'d'-'translate(t, '.', ':')

/*--- timestamp expand: expand to timestamp of 26 chars -------------*/
timestampExp: procedure expose m.
parse arg tst .
    if length(tst) < m.timeStamp_Len then
        return overlay(tst, m.timeStamp_01)
    else
        return left(tst, timeStamp_Len)
endProcedure tiemstampExp

/*--- timestamp check return '' if correct or error text ------------*/
timestampcheck: procedure expose m.
parse arg tst
    if length(tst) < m.timestamp_d0Len | \ abbrev(m.timestamp_11,
             , translate(tst, '111111111', '023456789')) then
        return 'bad timestamp' tst
    parse arg yyyy '-' mo '-' dd '-' hh '.' mm '.' ss '.' u
    if mo < 1 | mo > 12 then
        return 'bad month in timestamp' tst
    if dd < 1 | dd > 3 || substr('101010110101', mo, 1) then
        return 'bad day in timestamp' tst
    if mo = 2 then
        if dd > date('d', yyyy'0301', 's') - 32 then
            return 'bad day in timestamp' tst
    if hh > 23 & \ abbrev(24 00 00 000000, hh mm ss u) then
        return 'bad hour in timestamp' tst
    if mm > 59 then
        return 'bad minute in timestamp' tst
    if ss > 59 then
        return 'bad second in timestamp' tst
    return ''
endProcedure timestampCheck

/*--- timestamp to days since 1.1.0001 ------------------------------*/
timestamp2days: procedure expose m.
parse arg yyyy '-' mo '-' dd '-' hh '.' mm '.' ss
numeric digits 15
    return date('b', yyyy || mo || dd, 's') ,
                + (((hh * 60) + mm) * 60 + ss) / 86400

/*--- timestamp diff in days ----------------------------------------*/
timestampDiff: procedure expose m.
parse arg t1, t2
numeric digits 15
    return timestamp2days(t1) - timestamp2Days(t2)

/*--- convert 2 digit year to 4 digit -------------------------------*/
timeYear24: procedure expose m.
parse arg s
    y = left(date('S'), 4)
    s4 = left(y, 2)right(s, 2, 0)
    if s4 > y + 30 then
        return (left(y, 2) - 1)substr(s4, 3)
    else if s4 < y - 69 then
        return (left(y, 2) + 1)substr(s4, 3)
    else
        return s4
endProcedure timeYear24

/*--- convert 2 or 4 digit year Y (A=0...T=19) ----------------------*/
timeYear2Y: procedure expose m.
parse arg y
    return substr('ABCDEFGHIJKLMNOPQRST' , ((y + 10) // 20) + 1, 1)

/*--- convert 2 or 4 digit year Y (A=0...T=19) ----------------------*/
timeY2Year: procedure expose m.
parse arg i
    j = pos(i, 'ABCDEFGHIJKLMNOPQRST') - 1
    if j < 0 then
        call err 'timeYearY24 bad input' i
    y = left(date('S'), 4)
    r = y - (y+10) // 20 + j
    if r < y - 15 then
        return r + 20
    else if r > y + 4 then
        return r - 20
    else
        return r
endProcedure timeY2Year

/*--- convert numeric month to M (Jan=B=1, dec=12=M) ----------------*/
timeMonth2M: procedure expose m.
parse arg m
    return substr('BCDEFGHIJKLM', m, 1)

/*--- convert M to numeric Month (Jan=B=1, dec=12=M) ----------------*/
timeM2Month: procedure expose m.
parse arg m
    p = pos(m, 'BCDEFGHIJKLM')
    if p= 0 then
        call err 'bad M month' m
    return right(p, 2, 0)

/*--- convert numeric hour 78 to H8 (A=0..D=3) ----------------------*/
timeHour2H: procedure expose m.
parse arg h
    h = right(h, 2, 0)
    return substr('ABCD', left(h, 1)+1, 1)substr(h, 2)

/*--- convert H8 to numeric Hour 78 (A=0..D=3) ----------------------*/
timeH2Hour: procedure expose m.
parse arg h
    p = pos(left(h, 1), 'ABCD') - 1
    if p < 0 | length(h) \== 2 then
        call err 'bad H hour' h
    return p || substr(h, 2)

/*--- read timeZoneOffset and leapSeconds registers
        and set variables for uniq ----------------------------------*/
timeIni: procedure expose m.
parse arg debug
    numeric digits 25
    /* offsets documented in z/OS Data Areas  Vol.1 */
    cvtOH      = '00000010'          /* cvt control block Address */
    cvtext2O   = x2d('00000560') /* offset  to extension 2    */
    cvtldtoO   = x2d('00000038') /* offset to timezone    */
    cvtlsoO    = x2d('00000050') /* offset to leapSeconds */

    /* CVT CB        address               + extention2   */
    cvtExt2A       = C2D(STORAGE(cvtOH,4)) + cvtext2O
    /* cvtLdto timeZone              address +offset      */
    m.time_Zone    = c2d(STORAGE(d2x(cvtext2A+cvtldtoO, 8), 8))
    m.time_StckUnit = 1e-6 / 256 / 16
    /* cvtLso LeapSecs               address +offset      */
    m.time_Leap    = c2d(STORAGE(d2x(cvtext2A+cvtlsoO, 8), 8))
    m.time_LZT_TAI10_us16 = (m.time_zone - m.time_leap) % 256
    m.time_UQDigits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ012345678'
                         /* find lrsn of day 0, 0 out last 6 bits  */
    m.time_UQZero = x2d(left(timeTAI102Lrsn( ,
                 '2004-12-31-00.00.22.000000'), 14)) % 64 * 64
    m.timeStamp_01 = '0001-01-01-00.00.00.000000'
    m.timeStamp_11 = '1111-11-11-11.11.11.111111'
    m.timeStamp_99 = '9999-12-31-23.59.59.999999'
    m.timeStamp_len = length(m.timestamp_11)
    m.timeStamp_d0Llen = m.timestamp_len - 7
    m.time_ini = 1
    return
endSubroutine timeIni

/*--- TAI10 timestamp yyyy-mm.... -> stckE value char(16)
         BLSUETID is described in z/OS MVS IPCS Customization -------*/
timeTAI102stckE: procedure expose m.
                        /* timestamp must include microSeconds |||*/
    parse arg year'-'mo'-'da'-'hh'.'mm'.'secs
    tDate = mo'/'da'/'year hh':'mm'.'secs
    ACC=left('', 16, '00'x)
    ADDRESS LINKPGM "BLSUETID TDATE ACC"
    RETURN acc
endProcedure timeTAI102stckE

timeTAI102lrsn: procedure expose m.
parse arg tst
    return c2x(left(timeTAI102StckE(tst), 10))

timeLZT2stckE: procedure expose m.
parse arg tst
    numeric digits 23
    s =timeTAI102StckE(tst)
    return d2c(c2d(left(s, 8)) - m.time_lzt_TAI10_us16,8) || substr(s,9)
endProcedure timeLZT2stckE

timeLZT2lrsn: procedure expose m.
parse arg tst
    return c2x(left(timeLZT2StckE(tst), 10))

/*--- expand lrsn to 20 hexDigits -------------------------------*/
timeLrsnExp: procedure expose m.
parse arg s
    return left(copies('00', length(s) <= 12 & \abbrev(s, 0))s, 20,0)

/*--- expand stcK(e) to 16 Byte ---------------------------------*/
timeStckExp: procedure expose m.
parse arg s
    return left(copies('00'x, length(s) <= 8 & s >> '09'x)s, 16,'00'x)

/*--- conversion from StckE Clock Value to TAI10 Timestamp
        BLSUETOD is described in z/OS MVS IPCS Customization --------*/
timeStckE2TAI10: PROCEDURE expose m.
parse arg stck      /* must be 16 characters ||||| */
  TDATE = left('' , 26)
  ADDRESS LINKPGM "BLSUETOD stck TDATE"
  /* return format   : mo/dd/yyyy hh:mm:ss.uuuuuu */
  /* Timestamp format: yyyy-mm-dd-hh.mm.ss.uuuuuu */
  parse var TDATE mo '/' dd '/' yyyy hh ':' mm ':' secs
RETURN yyyy'-'mo'-'dd'-'hh'.'mm'.'secs
endProcedure timeStckE2TAI10

/*--- conversion from Lrsn Clock Value to TAI10 Timestamp ----------*/
timeLrsn2TAI10:
    return timeStckE2TAI10(x2c(arg(1))'000000000000'x)

/*--- conversion from Lrsn Clock Value to LZT Local Zurich Time -----*/
timeStckE2LZT: procedure expose m.
parse arg s
    numeric digits 23
    return timeStckE2TAI10(d2c(c2d(left(s, 8)) ,
                + m.time_LZT_TAI10_us16, 8) || substr(s, 9))
endProcedure timeStckE2LZT

/*--- conversion from Lrsn Clock Value to LZT Local Zurich Time -----*/
timeLrsn2LZT: procedure expose m.
parse arg lrsn
    return timeStckE2LZT(x2c(lrsn) || '000000000000'x)

/* convert a lrsn to the uniq variable ********************************/
timeLrsn2uniq: procedure expose m.
parse arg lrsn
    /* unique are bits 0:41 of the TodClock value
              minus 31.12.2004 represented
              by base 35 by 'ABC...YZ01..8'
    */
    lrsn = left(timeLrsnExp(lrsn), 14)
    numeric digits 20
    diff = x2d(lrsn) - m.time_UQZero
    if diff < 0 then
        return'< 2005'
    return right(i2q(diff % 64, m.time_UQDigits), 8, 'A')
endProcedure timeLrsn2Uniq

/* convert a uniq variable to lrsn ************************************/
timeUniq2lrsn: procedure expose m.
parse arg uniq
    numeric digits 15
    u1 = q2i(left(uniq, 8, 'A'), m.time_UQDigits) * 64
    lrsn = '00'right(d2x(u1 + m.time_UQZero), 12, 0)'000000'
    return lrsn
endProcedure uniq2lrsn

/*--- translate a number in q-system to decimal
       arg digits givs the digits corresponding to 012.. in the q sysem
       q = length(digits) --------------------------------------------*/
q2i: procedure expose m.
parse arg v, digits
    b = length(digits)
    i = 0
    do x = 1 to length(v)
        q = substr(v, x, 1)
        r = pos(q, digits)
        if r < 1 then
            call err 'bad digit' q 'in' v 'valid digits' digits
        i = i * b + r - 1
        end
    return i
endProcedure q2i
/*--- translate a decimal number to q-system - inverse of q2i --------*/
i2q: procedure expose m.
parse arg i, digits
    if i = 0 then
        return left(digits, 1)
    b = length(digits)
    v = ''
    do while i > 0
        v = substr(digits, 1 + (i // b), 1) || v
        i = i % b
        end
    return v
endProcedure i2q
/* copy time end -----------------------------------------------------*/
/* copy SQL begin ***************************************************
       Achtung: inc generiert sql aus sqlRx, Aenderungen nur in sqlRx|
    sql interface
***********************************************************************/
/*--- initialize sql -----------------------------------------------*/
sqlIni: procedure expose m.
    if m.sql_ini == 1 then
        return
    m.sql_ini = 1
    call utIni
    m.sqlNull = '---'
    m.sqlInd = 'sqlInd'
    m.sql_defCurs= 49
    m.sqlCAMsg = 0
    m.sqlSuMsg = 2
    m.sql_dbSys = ''
    m.sql_csmhost = ''
    isInProd = wordPos(sysvar(sysNode), 'RZ2') > 0
    m.sql_retOk   = 'dne' copies('rod', \ isInProd)
    return 0
endProcedure sqlIni

/*--- get default db2System ------------------------------------------*/
sqlDefaultSys: procedure expose m.
parse arg
    if sysvar(sysnode) == 'RZ1' then
        return 'DBAF'
    else if sysvar(sysnode) == 'RZ4' then
        return 'DP4G'
    else if sysvar(sysnode) == 'RZX' then
        return 'DX0G'
    else
        call err 'no default subsys for' sysvar(sysnode)
endProcedure sqlDefaultSys

/*--- connect to the db2 subsystem sys -----------------------------*/
sqlConnect: procedure expose m.
parse upper arg sys, ggRetOk
    call sqlIni
    address tso "SUBCOM DSNREXX"
    if rc <> 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
    if sys = '-' then
        return 0
    if sys == '' then
        sys = sqlDefaultSys()
    m.sql_dbSys = sys
    ggSqlStmt =  'connect' sys
    address dsnRexx ggSqlStmt
    if rc = 0 then
        return 0
    interpret sqlErrorHandler(rc, ggRetOk, ggSqlStmt)
    return sqlCode
endProcedure sqlConnect

/*--- diconnect from db2 ---------------------------------------------*/
sqlDisconnect: procedure expose m.
parse arg retOk
    if m.sql_dbSys == '' then
        return 0
    ggSqlStmt =  'disconnect'
    m.sql_dbSys = ''
    m.sql_csmHost = ''
    address dsnRexx ggSqlStmt
    if rc = 0 then
        return 0
    interpret sqlErrorHandler(rc, retOk, ggSqlStmt)
    return sqlCode
endProcedure sqlDisconnect

/*--- reset sql cursor 'c'cx fields ----------------------------------*/
sqlReset: procedure expose m.
parse arg cx
     m.sql.cx.updateCount = ''
     m.sql.cx.resultSet   = ''
     m.sql.cx.d.sqlD = 'noSqlDA'
     m.sql.cx.i.sqlD = 'noDescInp'
     m.sql.cx.fetchVars = ''
     m.sql.cx.fetchFlds = ''
     m.sql.cx.fetchClass = ''
     m.sql.cx.type  = ''
     return
endProcedue sqlReset

/*--- execute a query from sql, with one resultset -------------------*/
sqlQuery: procedure expose m.
parse arg cx, src, feVa, retOk
     call sqlReset cx
     s1 = ''
     if pos(left(feVa, 1), '?:') < 1 then
         s1 = 'into :M.SQL.'cx'.D'
     res = sqlExec('prepare s'cx s1 'from :src', retOk)
     if res < 0 then
         return res
     call sqlFetchVars cx, feVa
     call sqlExec 'declare c'cx 'cursor for s'cx
     res = sqlExec('open c'cx, retOk)
     if res < 0 then
         return res
     m.sql.cx.updateCount = sqlErrd.3
     m.sql.cx.resultSet = cx
     return res
endProcedure sqlQuery

/*--- prepare a query from sql, with one resultset -------------------*/
sqlQueryPrepare: procedure expose m.
parse arg cx, src, feVa, retOk
     call sqlReset cx
     s1 = ''
     if feVa == '' | feVa = 'd' then
         s1 = 'into :M.SQL.'cx'.D'
     res = sqlExec('prepare s'cx s1 'from :src', retOk)
     if res < 0 then
         return res
     call sqlFetchVars cx, feVa
     call sqlExec 'declare c'cx 'cursor for s'cx
     return sqlExec('describe input s'cx 'into :M.SQL.'cx'.I')
endProcedure sqlQueryPrepare

sqlQueryExecute: procedure expose m.
parse arg cx retOk
    do ix=1 to arg()-1
        call sqlDASet cx , 'I', ix, arg(ix+1)
        end
     res = sqlExec('open c'cx 'using descriptor :M.SQL.'cx'.I', retOk)
     if res < 0 then
         return res
     m.sql.cx.updateCount = sqlErrd.3
     m.sql.cx.resultSet = cx
     return res
endProcedure sqlQueryExecute

/*--- fetch next row to m.dst.* at end return false ------------------*/
sqlFetch: procedure expose m.
parse arg cx, dst, retOk
    fetCode = sqlExec('fetch c'cx 'into' m.sql.cx.fetchVars, 100 retOk)
    if fetCode == 100 then
        return 0
    if fetCode < 0 then
        return fetCode
    interpret m.sql.cx.fetchCode
    return 1
endProcedure sqlFetch

/*--- close cursor 'c'cx ---------------------------------------------*/
sqlClose: procedure expose m.
parse arg cx, retOk
     return sqlExec('close c'cx, retOk)
endProcedure sqlClose

/*-- execute an sql with no resultset, but possibly outParms ---------*/
sqlUpdate: procedure expose m.
parse arg cx, src, retOk
    m.sql.cx.updateCount = ''
    m.sql.cx.resultSet   = ''
    bx = verify(src, '( ')
    if bx > 0 then
        fun = translate(word(substr(src, bx), 1))
    if  fun = 'SET' then do
        w2 = translate(word(substr(src, bx), 2))
        if \ abbrev(w2, ':') then
            return sqlExec('execute immediate :src', retOk)
        trace ?r
        ex = pos('=', w2)
        if ex = 0 then
            ex = length(w2)+1
        var = strip(substr(w2, 2, ex-2))
        if var = '' then
            call err 'bad hostVar in' src
        m.sql.outVar = var
        src2 = 'set :M.sql.out.'var substr(w, ex) subword(src, 3)
        return sqlExec(src2, retOk)
        end
    if fun == 'DECLARE'  then do
        if 'GLOBAL' == translate(word(substr(src, bx), 2)) then
            return sqlExec('execute immediate :src', retOk)
        end
    res = sqlExec(src, retOk)
    if wordPos(fun, 'DELETE INSERT UPDATE') > 0 then
        m.sql.cx.updateCount = sqlErrd.3
    return res
endProcedure sqlUpdate

/*-- prepare an update -----------------------------------------------*/
sqlUpdatePrepare: procedure expose m.
parse arg cx, src, retOk
    res = sqlExec('prepare s'cx 'from :src', retOk)
    return sqlExec('describe input s'cx 'into :M.SQL.'cx'.I')
endProcedure sqlUpdatePrepare

/*-- execute a prepared update with the given arguments --------------*/
sqlUpdateExecute: procedure expose m.
parse arg cx retOk
    do ix=1 to arg()-1
        call sqlDASet cx , 'I', ix, arg(ix+1)
        end
    res = sqlExec('execute s'cx 'using descriptor :M.SQL.'cx'.I',
                  , retOk)
    m.sql.cx.updateCount = sqlErrd.3
    return res
endProcedure sqlUpdateExecute

/*-- execute a query, update or call ---------------------------------*/
sqlExecute: procedure expose m.
parse arg cx, src, retOk
    f = translate(word(src, 1))
    bx = pos('(', f)
    if bx > 0 then
        f = left(f, max(1, bx-1))
    m.sql.cx.fun = f
    if f == 'SELECT' | f == 'WITH' | f == '(' then
        return sqlQuery(cx, src, , retOk)
    else if f == 'CALL' then
        call err 'implement sql call for:' src
    else
        return sqlUpdate(cx, src, retOk)
endProcedure sqlExecute

/*-- fetch all rows to stem and close --------------------------------*/
sqlFetch2St: procedure expose m.
parse arg cx, dst, retOK
    do sx=1 while sqlFetch(cx, dst'.'sx)
       end
    res = sx-1
    m.dst.0 = sx-1
    call sqlClose cx
    return m.dst.0
endProcedure sqlFetch2St

/*-- execute a query, copy result to stem ----------------------------*/
sql2St: procedure expose m.
parse arg src, dst, feVa, retOK
    cx = m.sql_defCurs
    res = sqlQuery(cx, src, feVa, retOk)
    return sqlFetch2St(cx, dst, retOk)
endProcedure sql2St

/*-- return first column of the only row and close -------------------*/
sqlFetch2One: procedure expose m.
parse arg cx, dst, retNone
    f1 = sqlFetch(cx, dst)
    if f1 then
        f2 = sqlFetch(cx, dst'.2')
    call sqlClose cx
    if \ f1 then
        if retNone \== '' then
            return substr(retNone, 2)
        else
            call err 'sqlFetch2One: no row returned'
    else if f2 then
        call err 'sqlFetch2One: more than 1 row'
    if m.sql.cx.fetchFlds == '' then do
        c1 = substr(word(m.sql.cx.fetchVars, 1), 2)
        res = value(c1)
        return res
        end
    c1 = word(m.sql.cx.fetchFlds, 1)
    return m.dst.c1
endProcedure sqlFetch2One

/*-- execute a query and return first column of the only row
           if > 1 row fail, if 0 rows return arg(3) or fail ----------*/
sql2One: procedure expose m.
parse arg src, dst, feVa, retOk, retNone
    cx = m.sql_defCurs
    call sqlQuery cx, src, feVa, retOk
    return sqlFetch2One(cx, dst, retNone)
endProcedure sql2One

/*--- describe table and return sqlDA --------------------------------*/
sqlDescribeTable: procedure expose m.
parse upper arg tb, force
    if force == 1 | \ datatype(m.sql.table.tb.sqlD, 'n') then
         call sqlExec 'describe table :tb into :M.SQL.TABLE.'tb
    return 'SQL.TABLE.'tb
endProcedure sqlDescribeTable

/*--- return select column list for table tb
      omit Blobs (blobMax < 0) or substr(blob, 1, blobMax) ----------*/
sqlColList: procedure expose m.
parse arg tb al, blobMax
    sd = sqlDescribeTable(tb)
    bs = ''
    lst = ''
    if al \== '' & right(al, 1) \== '.' then
        al = al'.'
    do sx=1 to m.sd.sqld
        if wordPos(m.sd.sx.sqlType, '404 405 408 409 412 413') < 1 then
            lst = lst',' al || m.sd.sx.sqlName
        else do
            bs = bs m.sd.sx.sqlName
            if blobMax >= 0 then
                lst = lst', length('al || m.sd.sx.sqlName')' ,
                                          m.sd.sx.sqlName'Len' ,
                     || ', substr('al  || m.sd.sx.sqlName ,
                     || ', 1,' blobMax')' m.sd.sx.sqlName
            end
        end
    m.sd.colList = substr(lst, 3)
    m.sd.blobs = strip(bs)
    return substr(lst, 3)
endProcedure sqlColList

/*--- use describe output to generate column names,
        ''         use names from ca (rexxified)
        nms+       use names, check ca for null values
        ?('?'?nm)+ use names, check for null if preceeded by ?
        :...       use string as is
                fetchVariables and sqlNull names ---------------------*/
sqlFetchVars: procedure expose m.
parse arg cx, src, cd
    st = 'SQL.'cx'.COL'
    if abbrev(src, ':') then do
        m.sql.cx.fetchVars = src
        m.sql.cx.fetchCode = cd
        m.sql.cx.fetchFlds = ''
        return
        end
    m.sql.cx.fetchVars = ''
    if abbrev(src, '?') then do
        call err  implement + rxFetchVars ?????? /*
        r = substr(src, 2)
        do wx=1 to words(src)
            cn = word(src, wx)
            if abbrev(cn, '?') then
                call sqlRexxAddVar substr(cn, 2), 0, 1
            else
                call sqlRexxAddVar cn, 0, 0
            end                              ????????????? */
        end
    else if src <> '' then do
        ff = src
        end
    else do
        ff = ''
        do kx=1 to m.sql.cx.d.sqlD
             ff = sqlNiceVarsAdd(ff, m.sql.cx.d.kx.sqlName)
             end
        end
    m.sql.cx.fetchFlds = ff
    if m.sql.cx.d.sqlD <> words(ff) then
        call err 'sqlFetchVars sqlD='m.sql.cx.d.sqlD ,
                '<>' words(ff) 'fields of' ff
    sNu = ''
    sFe = ''
    do kx=1 to m.sql.cx.d.sqlD
        nm = word(ff, kx)
        sFe = sFe', :m.dst.'nm
        if m.sql.cx.d.kx.sqlType // 2 then do
            sFe = sFe' :m.dst.'nm'.sqlInd'
            sNu = sNu 'if m.dst.'nm'.sqlInd < 0 then' ,
                   'm.dst.'nm '= m.sqlNull;'
            end
        end
    m.sql.cx.fetchVars = substr(sFe, 3)
    m.sql.cx.fetchCode = sNu cd
    return
endProcedure sqlFetchVars
/* ????????????
sqlRexxAddVar:
parse arg nm, nicify, hasNulls
     nm = sqlAddVar(st, nm, nicify)
     if \ hasNulls then
          vrs = vrs', :m.dst.'nm
     else do
         vrs = vrs', :m.dst.'nm ':m.dst.'nm'.sqlInd'
         sNu = sNu 'if m.dst.'nm'.sqlInd < 0 then' ,
                    'm.dst.'nm '= m.sqlNull;'
         end
    return
endSubroutine sqlRexxAddVar   ?????? */

sqlNiceVarsAdd: procedure expose m.
parse arg old, nx rest
    upper nx
    cx = verifId(nx)
    if cx > 0 then /* avoid bad characters for classNew| */
        nx = left(nx, cx-1)
    if nx <> '' & wordPos(nx, old) < 1 0 then
        return old nx
    else
        return old  'COL' || (words(old) + 1)
endProcedure sqlNiceVarsAdd

/*--- set one value in a DA, handle nulls ----------------------------*/
sqlDASet: procedure expose m.
parse arg cx, da, ix, val
    m.sql.cx.da.ix.sqlData = val
    m.sql.cx.da.ix.sqlInd = - (val == m.sqlNull)
    /* data types schienen einmal nicht zu funktionieren .......
    if wordPos(m.da.ix.sqlType, '384 385 388 389 392 393') > 0 then
        m.da.ix.sqlType = 448 + (m.da.ix.sqlType // 2) */
    return
endProcedure sqlDASet

sqlCommit: procedure expose m.
parse arg src
     return sqlUpdate(, 'commit')
endProcedure sqlCommit

/*--- execute sql thru the dsnRexx interface -------------------------*/
sqlExec: /* no procedure, to keep variables sql... */
    parse arg ggSqlStmt, ggRetOk
    m.sql_HaHi = ''
    address dsnRexx 'execSql' ggSqlStmt
    /* say 'sqlCode' sqlCode 'rc' rc 'for' ggSqlStmt ggNo */
    if rc = 0 then
        return 0
    interpret sqlErrorHandler(rc, ggRetOk, ggSqlStmt)
    return sqlCode
endProcedure sqlExec

sqlExecMsg: procedure expose m.
parse arg sql
    sc = sqlExec(sql, '*')
    return sqlMsgLine(sc, , sql)

sqlErrorHandler: procedure expose m. ,
                   sqlCode sqlErrMc sqlErrP sqlErrD. sqlWarn. sqlState
parse arg drC, retOk, verb rest
    if drC == 0 then
        return 'return 0'
    if pos('-', retOK) < 1 then
        retOK = retOk m.sql_retOk
    if wordPos(drC, '1 -1') < 1 then do
        eMsg = "'dsnRexx rc="drC"' sqlmsg()"
        end
    else if pos('*', retOK) > 0 | wordPos(sqlCode, retOK) > 0 then do
        if sqlCode < 0 & pos('say', retOK) > 0 then
            return "call outNl errMsg(' }'sqlMsg())"
        else
            return ''
        end
    else do
        upper verb
        if verb == 'DROP' then do
            if (sqlCode == -204 | sqlCode == -458) ,
                           & wordPos('dne', retok) > 0 then
                return 'return' sqlCode
            if sqlCode = -672 & wordPos('rod', retok) > 0 then do
                hahi = m.sql_HaHi || sqlMsgLine(sqlCode,
                                   , 'tb='sqlErrMc ,verb rest)'\n'
                haHi = haHi || sqlExecMsg('alter table' SqlErrMc ,
                        'drop restrict on drop')
                call sqlExec verb rest
                m.sql_HaHi = hahi
                return ''
                end
            end
        if drC < 0 then
            eMsg = "sqlmsg()"
        else if (sqlCode<>0 | sqlWarn.0 ^==' ') & pos('w',retOK)<1 then
            return "call outNl errMsg(' }'sqlMsg()); return" sqlCode
        else
            return ''
        end
    if wordPos('rb', retok) > 0 then
        eMsg = eMsg " || '\n"sqlExecMsg('rollback')"'"
    if wordPos('ret', retok) < 1 then
        return "call err" eMsg
    m.sql_errRet = 1
    return 'call outNl' eMsg
endProcedure sqlErrorHandler

sqlMsgLine: procedure expose m. sqlErrD.
parse arg res, cnt, src, plus
    verb = translate(word(src, 1))
    if datatype(res, 'n') then
        res = 'sqlCode' res
    if cnt \== '' then do
        res = res',' cnt
        vx = wordPos(translate(word(src,1)), 'DELETE INSERT UPDATE')
        if datatype(cnt, 'n') then
            if vx > 0 then
               res = res 'rows' word('deleted inserted updated', vx)
            else if cnt <> 0 then
                res = res 'rows updated'
        end
    if plus \== '' then
        res = res',' plus
    if abbrev(res, ', ') then
        res = substr(res, 3)
    if src \== '' then do
        ll = 75 - length(res)
        aa = strip(src)
        if length(aa) > ll then
            aa = space(aa, 1)
        if length(aa) > ll then
           aa = left(aa,  ll-3)'...'
        res = res':' aa
        end
    return res
endProcedure sqlMsgLine

/*--- send a command to db2 through the TSO dsn processor ------------*/
sqlDsn: procedure expose m.
parse arg st, sys, cmd, rcOk
    x = outtrap('M.'st'.')
    push 'END'
    push cmd
    address tso 'DSN SYSTEM('sys')'
    rr = rc
    x = outtrap(off)
    if rr = 0 | rcOk = '*' | wordPos(rr, rcOk) > 0 then
        return rr
    fl = max(1, m.st.0 - 10)
    em = 'rc' rr 'for DSN SYSTEM('sys') cmd' cmd,
          '\nOuputlines' fl '-' m.st.0':'
    do lx=fl to m.st.0
        em = em '\n' strip(m.st.lx, 't')
        end
    call err em
endProcedure sqlDsn

/*--- issue an sql error message -------------------------------------*/
sqlMsg: /* no procedure, to keep variables sql... */
    if \ dataType(sqlCode, 'n') then do
        ggRes = 'sqlCode' sqlCode 'not numeric\n'sqlCaMsg()
        end
    else do
        ggRes = sqlDsntiar(sql2CA())
        ggWa = sqlMsgWarn()
        if ggWa \= '' then
            ggRes = ggRes'\nwarnings' ggWa
        if m.sqlCAMsg == 1 then
           ggRes = ggRes'\n'sqlCaMsg()'\n'sqlCaMsg(sqlCa2Rx(sqlCa))
        end
    ggSt = 'SQL_HOST'
    ggVa = 'SQL_HOST.VAR'
    ggBe = 'SQL_HOST.BEF'
    call sqlHostVars ggSqlStmt, 12, ggSt
    ggFrom = 'ggSqlStmt'
    ggW1 = translate(word(ggSqlStmt, 1))
    ggW2 = translate(word(ggSqlStmt, 2))
    if ggW1 == 'PREPARE' then
        ggFrom = sqlHostVarFind(ggSt, 'FROM')
    else if ggW1 ggW2 == 'EXECUTE IMMEDIATE' then
        ggFrom = sqlHostVarFind(ggSt, 1)
    ggPos = 0
    if datatype(sqlErrd.5, 'n') & sqlErrd.5 > 0 then do
        ggPos = sqlErrd.5
        ggRes = ggRes || sqlMsgSrcPos(value(ggFrom), sqlErrd.5)
        end
    if ggFrom == 'ggSqlStmt' then do
        ggRes = ggRes'\nsql =' sqlShorten(ggSqlStmt, 2000, ggPos)
        end
    else do
        ggRes = ggRes'\nsql =' sqlShorten(value(ggFrom), 2000, ggPos)
        ggRes = ggRes'\nstmt =' sqlShorten(ggSqlStmt, 2000)
        end
    ggPref = '\nwith'
    do ggXX=1 to m.ggSt.0
        if ggFrom = m.ggVa.ggXX then
            iterate
        ggRes = ggRes || ggPref m.ggBe.ggXX ':'m.ggVa.ggXX ,
                      '=' sqlShorten(value(m.ggVa.ggXX), 210)
        ggPref = '\n    '
        end
    if m.sqlSuMsg == 1 | (m.sqlSuMsg == 2 & m.sql_csmhost \== '') then
        ggRes = ggRes'\nsubsys =' m.sql_dbSys ,
             || ', host =' m.sql_csmhost
    return  ggRes
endSubroutine sqlMsg

sqlShorten: procedure expose m.
parse arg txt, maxL, pos
    if length(txt) <= maxL then
        return txt
    if \ datatype(pos, 'n') | pos < 1 then
        pos = 1
    ex = pos + min(60, maxL%7)
    if ex <= maxL - 4 then
        return left(txt, maxL-4) '...'
    if ex >= length(txt) then
        return left(txt, 67) '...\n'substr(txt, length(txt)-maxL+72)
    else
        return left(txt, 67) '...\n'substr(txt, ex-maxL+76, maxL-75) ,
                       '...'
endProcedure sqlShorten
/*--- use dsnTiar to translate sql Info to error text ----------------*/
sqlDsnTiar: procedure expose m.
parse arg ca
    if -438  = sqlCa2Rx(ca) then
        return '\nSQLCODE = -438:',
           'APPLICATION RAISED ERROR WITH sqlState' sqlState ,
           'and DIAGNOSTIC TEXT:' sqlErrMc
    liLe = 78
    msLe = liLe * 10
    msg = d2c(msLe,2) || left('', msLe)
    len = d2c(liLe, 4)
    ADDRESS LINKPGM "DSNTIAR ca msg len"
    if rc = 0      then nop
    else if rc = 4 then say 'warn linkPgm dsnTiar rc' rc 'sqlCa' ca
    else                call err 'linkPgm dsnTiar rc' rc 'sqlCa' ca
    res = strip(substr(msg, 13, liLe-10))
    cx = pos(', ERROR: ', res)
    if cx > 0 then
        res = left(res, cx-1)':' strip(substr(res, cx+9))
    do c=3+liLe by liLe to msLe while substr(msg, c, 10) = ''
            res = res'\n    'strip(substr(msg, c+10, liLe-10))
        end
    return res
endProcedure sqlDsnTiar

/*--- format all rexx sqlCa fields into a message --------------------*/
sqlCaMsg:
    return 'sqlCode' sqlCode 'sqlState='sqlState                    ,
           '\n    errMC='translate(sqlErrMc, ',', 'ff'x)            ,
           '\n    warnings='sqlWarnCat('+') 'erP='sqlErrP           ,
           '\n    errD.1='sqlErrD.1 '2='sqlErrD.2 '3='sqlErrD.3     ,
           '\n    errD.4='sqlErrD.4 '5='sqlErrD.5 '6='sqlErrD.6
endProcedure sqlCaMsg

/*--- format the sqlCA into the dsnTiar SQLCA ------------------------*/
sql2Ca: procedure expose m. ,
                   sqlCode sqlErrMc sqlErrP sqlErrD. sqlWarn. sqlState
    if \ (datatype(sqlcode, 'n') & datatype(sqlErrD.1, 'n') ,
                                 & datatype(sqlErrD.3, 'n')) then
        return err('sqlCode etc. not numeric\nsqlCa =' sqlCaMsg())
    if digits() < 10 then
        numeric digits 10
    sqlCa = 'SQLCA   ' || d2c(136, 4) || d2c(sqlCode, 4) ,
            || d2c(min(70, length(sqlErrMc)), 2)left(sqlErrMc, 70) ,
            || left(sqlErrP, 8) ,
            || d2c(sqlErrD.1, 4)d2c(sqlErrD.2, 4)d2c(sqlErrD.3, 4) ,
            || d2c(sqlErrD.4, 4)d2c(sqlErrD.5, 4)d2c(sqlErrD.6, 4) ,
            || sqlWarnCat() || sqlState
    if length(sqlCa) <> 136 then
        call err 'sqlCa length' length(sqlCa) 'not 136' ,
                 '\n'sqlCaMsg() '==>'  ca', hex='c2x(ca)
    return sqlCa
endProcedure sql2Ca

/*--- extract the fields from the SqlCA and put it to rexx vars ------*/
sqlCA2Rx: procedure expose m. ,
       sqlCode sqlErrMc sqlErrP sqlErrD. sqlWarn. sqlState
parse arg ca
    numeric digits 10
    if length(ca) < 136 | c2d(substr(ca, 9, 4), 4) <> 136 then
        call err 'bad sqlCa len' length(ca) 'not 136:' ca', hex='c2x(ca)
    sqlCode  = c2d(substr(ca, 13 ,4), 4)
    sqlErrMC = substr(ca, 19, c2d(substr(ca, 17, 2), 2))
    sqlErrP  = substr(ca, 89, 8)
    do ix=1 to 6
        sqlErrD.ix = c2d(substr(ca, 93 + 4 * ix, 4), 4)
        end
    do ix=0 to 10
        sqlWarn.ix = substr(ca, 121 + ix, 1)
        end
    sqlState = substr(ca, 132, 5)
    return sqlCode
endProcedure sqlCA2Rx

/*--- concat the sql warnings with Separator sep --------------------*/
sqlWarnCat: procedure expose m. sqlWarn.
parse arg sep
    return sqlWarn.0 || sep,
        || sqlWarn.1||sqlWarn.2||sqlWarn.3||sqlWarn.4||sqlWarn.5||sep ,
        || sqlWarn.6||sqlWarn.7||sqlWarn.8||sqlWarn.9||sqlWarn.10||sep
endProcedure sqlWarnCat

/*--- make the text for sqlWarnings ----------------------------------*/
sqlMsgWarn: procedure expose m. sqlWarn.
     r = ''
     text =' 1=W var truncated, 1=S scrollable, 1=N nonScrollable,'  ,
            '2=W nulls in aggregate,'                                ,
            '3=W more cols than vars,'                               ,
                             '3=Z more result sets than locators,'   ,
            '4=W no where, 4=D sensitive dynamic, 4=I insensitive,'  ,
                          '4=S sensitive static,'                    ,
            '5=W not valid sql, 5=1 readOnly, 5=2 readDelete,'       ,
                          '5=3 readDeleteUpdate,'                    ,
            '6=W day changed to month range,'                        ,
            '7=W dec digits truncated,'                              ,
            '8=W char substituted,'                                  ,
            '9=W arith excep in count, 9=Z multipe result sets,'     ,
            '10=W char conversion err in ca,'
     do wx = 1 to 10
         w = sqlWarn.wx
         if w = ' ' then
             iterate
         t = wx'='w
         cx = pos(' 'wx'='w' ', text)
         ex = pos(','         , text, cx + 1)
         if cx > 0 & ex > cx then
             r = r substr(text, cx+1, ex-cx)
         else
             r = r wx'='w '?,'
         end
     r = strip(r, 't', ',')
     if r = '' & sqlwarn.0 <> '' then
        call err 'sqlWarn.0='sqlWarn.0 'but all warns empty'
     return r
endProcedure sqlMsgWarn

/*--- show in the source src the point pos  (where error occured)
          a few lines from src around pos and arrow to pos ----------*/
sqlMsgSrcPos: procedure expose m.
parse arg src, pos
    liLe = 68
    liCn = 3
    afLe = 25
    t1 = space(left(src, pos), 1)
    t2 = left(' ', substr(src, pos, 1) == ' ' ,
                 | substr(src, pos+1, 1) == ' ') ,
         || space(substr(src, pos+1), 1)
    afLe = min(afLe, length(t2))
    if length(t1) + afLe > liLe * liCn then
        t1 = '...'right(t1, liLe * liCn - afLe -3)
    else if length(t1)+length(t2) > liLe then
        t1 = left(' ', (liCn * liLe - length(t1) -afLe) // liLe)||t1
    pL = length(t1) // liLe
    if length(t2) <= liLe-pL then
        tx = t1 || t2
    else
        tx = t1 || left(t2, liLe-pL-3)'...'
    res = '\nsrc' strip(substr(tx, 1, liLe), 't')
    do cx=1+liLe by liLe to length(tx)
        res = res || '\n  +' strip(substr(tx, cx, liLe), 't')
        end
    loc = 'pos' pos 'of' length(src)
    if length(loc)+6 < pL then
        return res'\n  >' right('>>>'loc'>>>', pL)
    else
        return res'\n  >' left('', pL-1)'<<<'loc'<<<'
endProcdedure sqlMsgSrcPos

/*--- get the hostVars in the sql in src and the word before ---------*/
sqlHostVars: procedure expose m.
parse arg src, cnt, st
    cx = 1
    sx = 1
    do cnt
        cx = pos(':', src, cx) + 1
        if cx < 2 then
           leave
        if pos(substr(src, cx, 1), m.ut_rxN1) > 0 then
            iterate
        ex = verify(src, m.ut_rxDot, 'n', cx)
        if ex < 1 then
            m.st.var.sx = substr(src, cx)
        else
            m.st.var.sx = substr(src, cx, ex - cx)
        if m.st.var.sx == '' | length(m.st.var.sx) > 100 then
            iterate
                       /* search word before */
        do bE = cx-2 by -1 to 1 ,
                while substr(src, bE, 1) == ' '
            end
        do bB = bE by -1 to max(1, bE-20),
                while pos(substr(src, bB, 1), m.ut_alfa) > 0
            end
        if bB < bE & bB >= 0 then
            m.st.bef.sx = substr(src, bB+1, bE-bB)
        else
            m.st.bef.sx = ''
        sx = sx + 1
        end
    m.st.0 = sx-1
    return sx
endProcedure sqlHostVars

/*--- find the name of hostvar, by index or by before ----------------*/
sqlHostVarFind: procedure expose m.
parse arg st, fnd
    if datatype(fnd, 'n') & fnd <= m.st.0 then
        return m.st.var.fnd
    do ix=1 to m.st.0
        if translate(m.st.bef.ix) = fnd then
            return m.st.var.ix
        end
    return ''
endSubroutine sqlHostVarFind
/* copy SQL 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 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_cat == '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_cat = 'f'
    do while substr(msg, 2, 1) == '}'
        parse var msg m.err_cat '}' msg
        end
   if m.err_cat == ' ' | m.err_cat == 'o' then
        return msg
   pTxt = ',fatal error,error,input error,scanErr,warning,onlyMsg,'
   px = pos(','m.err_cat, pTxt)
   if px < 1 then do
       px = 1
       m.err_cat = 'f'
       end
   pre = substr(pTxt, px+1, pos(',', pTxt, px+2)-px-1)
   if m.err_cat == 's' then
       return pre msg
   parse source . . s3 .              /* current rexx */
   return pre 'in' s3':' msg
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'
                /* 012345678901234567890123456789 */
    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'+-*/=()[]{}<> .:,;?|''"%&#@$£\_'
    m.ut_base64 = m.ut_alfUC || m.ut_alfLc || m.ut_digits'+-'
    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 ********************************************************/       6
/* copy out begin ******************************************************
    out interface simple with say only
***********************************************************************/
/*--- output a message to the current destination -------------------*/
out: procedure expose m.
parse arg msg
    say msg
    return
endProcedure out
/* copy out end   *****************************************************/