zOs/REXX/F

/* copy f begin ******************************************************/
/*--- format with the given format ggA1, ggA2, etc. -----------------*/
f: procedure expose m.
parse arg ggFmt, ggA1, ggA2
    if symbol('M.f_gen.ggFmt') \== 'VAR' then
        call fGen ggFmt, ggFmt
    interpret m.f_gen.ggFmt
endProcedure f

fImm: procedure expose m.
parse arg ggFmt, ggA1
    interpret m.f_gen.ggFmt
endProcedure fImm

fCache: procedure expose m.
parse arg a, fmt
    if a \== '%>' then do
        if symbol('M.f_gen.a') == 'VAR' then
            if m.f_gen.a \== fmt then
                call err 'fCache('a',' fmt') already' m.f_gen.a
        end
    else do
        if symbol('m.f_gen0') == 'VAR' then
            m.f_gen0 = m.f_gen0 + 1
        else
            m.f_gen0 = 1
        a =  '%>'m.f_gen0
        end
    m.f_gen.a = fmt
    return a
endProcedure fCache

/*--- compile format fmt put in the cache with address a
          this procedure handles precompile and calls fGenF ---------*/
fGen: procedure expose m.
parse arg a, fmt
    if a \== '%>' then
        if symbol('M.f_gen.a') == 'VAR' then
            return a
    r3 = right(fmt, 3)
    if abbrev(r3, '%#') then do
        if substr(r3, 3) = '' then
            call err 'fGen bad suffix' fmt
        if right(a, 3) \== r3 then
            call err 'adr fmt mismatch' a '<->' fmt
        fmt = left(fmt, length(fmt) - 3)
        a = left(a, length(a) - 3)
        if symbol('m.a') == 'VAR' then
            call err 'base already defined' arg(2)
        end
    if \ abbrev(fmt, '%##') then
        return fCache(a, fGenF(fmt))
    parse var fmt '%##' fun ' ' rest
    interpret 'return' fun'(a, rest)'
endProcedure fGen

/*--------------------------------------------------------------------
 Format generator    should be compatible with fPrint|
 <<<< + extension of fPrint, - in fPrint but not implemented

   %%  %@ the escaped char
   ('@' argN? '.'? field)?'%' flags? width? ('.' precision)? specifier

 specifier: is the most significant one and defines the type

 - c  Character rigPad or lefPad, prec ==> substr(..., prec)
 -  C Cut %-nC = left(v,n), %nC = right(v,n) %n.mC = substr(m, n)
 - hH Characters in hex
 - iI Signed decimal integer (padded or cut)
 - eE Scientific notation (mantissa/exponent) using e character 3.92e+2
 - S  Strip (both)
 - txy time date formatting from format x to format y see fTstGen
 - kx  units Formatting x = t=time d=decimal b=binary (k) B=binary (kB)
 Flags:
 - -  Left-justify within the given field width; Right is default
 - +  print '+' before non negative numbers
 -' ' print ' ' before non negative numbers
 - /  cut to length

 preprocessor implemented in fGen
   %##fun fmt  format by function fun
   %>          address only
---------------------------------------------------------------------*/
fGenF: procedure expose m.
parse arg fmt
    if symbol('m.f_s_0') \== 'VAR' then
        m.f_s_0 = 1
    else
        m.f_s_0 = m.f_s_0 + 1
    f_s = 'F_S_'m.f_s_0
    call scanSrc f_s, fmt
    ax = 0
    cd = ''
    cp = ''
    do forever
        txt = fText(f_s)
        if txt \== '' then
            cd = cd '||' quote(txt, "'")
        if scanEnd(f_s) then
            leave
        if \ scanLit(f_s, '@') then do
            ax = ax + 1
            af = ''
            hasDot = 0
            end
        else do
            if scanWhile(f_s, '0123456789') then
                ax = m.f_s.tok
            else if ax < 1 then
                ax = 1
            hasDot = scanLit(f_s, '.')
            af = fText(f_s)
            end
        if \ scanLit(f_s, '%') then
            call scanErr f_s, 'missing %'
        call scanWhile f_s, '-+ /'
        flags = m.f_s.tok
        call scanWhile f_s, '0123456789'
        len = m.f_s.tok
        if \ scanLit(f_s, '.') then
            prec  = ''
        else do
            call scanWhile f_s, '0123456789'
            prec = m.f_s.tok
            end
        call scanChar f_s, 1
        sp = m.f_s.tok
        if ax < 3 | ass.ax == 1 then
            aa = 'ggA'ax
        else do
            aa = 'arg(' || (ax+1) || ')'
            if af \== '' then do
                 cp = cp 'ggA'ax '=' aa';'
                 aa = 'ggA'ax
                 ass.ax = 1
                 end
            end
        if af \== '' | hasDot then
            aa = rxMGet(aa, af)
        if sp == 'c' then do
            if prec \== '' then
                aa = 'substr('aa',' prec')'
            if len == '' then
                cd = cd '||' aa
            else if pos('-', flags) > 0 then
                cd = cd '|| lefPad('aa',' len')'
            else
                cd = cd '|| rigPad('aa',' len')'
            end
        else if sp == 'C' then do
            if prec \== '' then do
                cd = cd '|| substr('aa',' prec
                if len == '' then
                    cd = cd')'
                else
                    cd = cd',' len')'
                end
            else if len == '' then
                cd = cd '||' aa
            else if pos('-', flags) > 0 then
                cd = cd '|| left('aa',' len')'
            else
                cd = cd '|| right('aa',' len')'
            end
        else if sp == 'H' then
            cd = cd "|| fH("aa"," len',' (pos('-', flags) > 0)')'
        else if sp == 'h' then
            cd = cd "|| translate(fH("aa", '"siL"'),'abcdef','ABCDEF')"
        else if sp == 'i' then
            cd = cd "|| fI("aa"," len", '"flags"'," firstNS(prec, 0)")"
        else if sp == 'I' then
            cd = cd "|| fI("aa"," len", '/"flags"'," firstNS(prec, 0)")"
        else if sp == 'E' | sp == 'e' then do
            if len == '' then
                len = 8
            if prec = '' then
                prec = len - 6
            cd = cd "|| fE("aa"," len"," prec", '"sp"', '"flags"')"
            end
        else if sp = 'S' then
            cd = cd '|| strip('aa')'
        else if sp = 't' then do
            call scanChar f_s, 2
            cd = cd '||' fTstGen(m.f_s.tok, aa)
            end
        else if sp = 'k' then do
            call scanChar f_s, 1
            if pos(m.f_s.tok, 'tdbBiI') < 1 then
                call scanErr f_s, "bad unit type" m.f_s.tok
            if pos('+', flags) > 0 then
                pl = ", '+'"
            else if pos(' ', flags) > 0 then
                pl = ", ' '"
            else
                pl = ''
            cd = cd "|| fUnit('"m.f_s.tok || len"."prec"["pl"'," aa")"
            end
        else if sp == '(' then do
            c1 = aa
            do until m.f_s.tok = '%)'
                sx = m.f_s.pos
                do until m.f_s.tok == '%,' | m.f_s.tok == '%)'
                    call scanUntil f_s, '%'
                    if \ scanLit(f_s, '%,', '%)', '%') then
                         call scanErr f_s, '%( not closed'
                    end
                c1 = "fImm('"fGen('%>', substr(m.f_s.src, sx,
                              , m.f_s.pos - sx - 2))"'," c1")"
                end
            cd = cd '||' c1
            end
        else do
            call scanErr f_s, 'bad % clause'
            call scanBack f_s, '%'sp
            leave
            end
        end
    if \ scanEnd(f_s) then
        call scanErr f_s, "bad specifier '"m.f_s.tok"'"
    m.f_s_0 = m.f_s_0 - 1
    if cd \== '' then
        return strip(cp 'return' substr(cd, 5))
    else
        return "return ''"
endProcedure fGenF

fText: procedure expose m.
parse arg f_s
    res = ''
    do forever
        if scanUntil(f_s, '@%') then
            res = res || m.f_s.tok
        if scanLit(f_s, '%%', '%@') then
            res = res || substr(m.f_s.tok, 2)
        else if scanLit(f_s, '%>', '%##') then
            res = res || m.f_s.tok
        else
            return res
        end
endProcedure fText

fAll: procedure expose m.
parse arg fmt, rdr
    i = jOpen(in2File(rdr), '<')
    do while jRead(i)
        call out f(fmt, m.i)
        end
    call jClose i
    return
endProcedure fAll

/*--- format character2hex (if not sql null) ------------------------*/
fH: procedure expose m.
parse arg v, l, leftJ
    if v \== m.sqlNull then
        v = c2x(v)
    if length(v) > l then
        return v
    else if leftJ \== 1 then
        return right(v, l)
    else
        return left(v, l)
endProcedure fH

/*--- format integer or fixPoint Decimal ----------------------------*/
fI: procedure expose m.
parse arg v, l, flags, d
    if \ datatype(v, 'n') then
        return fRigLeft(strip(v), l, flags)
    v = format(v, , d, 0)
    if pos('+', flags) > 0 then
        if \ abbrev(v, '-') then
            v = '+'v
    if length(v) > l then
        if pos('/', flags) > 0 then
            return left('', l, '*')
        else
            return v
    return fRigLefPad(v, l, flags)
endProcedure fI

/*--- format with exponent l=total output len
                           d=number of digits after . in mantissa
                           c=exponent character
                           flags: - to ouput text left justified
    differences: exponent is always printed but without +
                 overflow ==> expand exponent, *****, 0e-999 --------*/
fE: procedure expose m.
parse arg v, l, d, c, flags
    if \ datatype(v, 'n') then
        return fRigLeft(v, l, flags)
    if pos(' ', flags) < 1 then
        if v >=  0 then
            if pos('+', flags) > 0 then
                return '+'substr(fE(v, l, d, c, ' 'flags), 2)
            else
                return substr(fE(v, l+1, d+1, c, ' 'flags), 2)
    x = format(v, 2, d, 7, 0)
    m = 2 + d + (d>0)
    call assert "length(x) == m+9", 'm x length(x)'
    if substr(x, m+1) = '' then
        return left(x, m)c || left('', l-m-1, 0)
    call assert "substr(x, m+1, 1) == 'E'","exponenent in x not at m"
    y = verify(x, '0', 'n',  m+3)
    call assert 'y>0'
    if substr(x, m+1, 2) == 'E+' then do
        if m+10-y <= l-m-1 then
             return left(x,m)c || right(x, l-m-1)
        z = l - 4 - (m+10-y)
        end
    else if substr(x, m+1, 2) == 'E-' then do
        if m+10-y <= l-m-2 then
             return left(x,m)c'-'right(x, l-m-2)
        z = l - 5 - (m+10-y)
        end
    else
        call err 'bad x' x
    if z >= -1 & max(0, z) < d then
        return fE(v, l, max(0, z), c, flags)
    else if substr(x, m+1, 2) == 'E-' then
        return left(x,1)'0'c'-'left('', l-4, 9)
    else
        return left('', l, '*')
endProcedure fE

/*--- right or left with truncation ---------------------------------*/
fRigLeft: procedure expose m
parse arg s, len, flags
    if length(s) = len then
        return s
    else if pos('-', flags) > 0 | length(s) > len then
        return left(s, len)
    else
        return right(s, len)
endProcedure fRigLefPad

/*--- right or left pad without truncation --------------------------*/
fRigLefPad: procedure expose m
parse arg s, len, flags
    if pos('-', flags) > 0 then
        if length(strip(s, 't')) >= len then
            return strip(s, 't')
        else
            return left(s, len)
    else
        if length(strip(s, 'l')) >= len then
            return strip(s, 'l')
        else
            return right(s, len)
endProcedure fRigLefPad

/*-- return char i+1 from codes cc ----------------------------------*/
fI2C: procedure expose m.
parse arg i, cc
    if i >= length(cc) then
        call err 'no code for fI2C('i',' cc')'
    return substr(cc, i+1, 1)

/*-- return pos-1 for char c in codes -------------------------------*/
fC2I: procedure expose m.
parse arg c, codes
    res = pos(c, codes)
    if res > 0 then
        return res - 1
    call err 'not  a code fI2C('c',' codes')'

/*--- generate timestamp format, derive from %t.. ------------------*/
fTstGen: procedure expose m.
parse arg ft, s
    fmt = '%t'ft
    if symbol('M.f_gen.fmt') \== 'VAR' then
        m.f_gen.fmt = 'return' fTstGe2(ft, 'ggA1')
    code =  m.f_gen.fmt
    if \ abbrev(code, 'return ') then
        call err 'fTstGen' ft 'bad code' code
    if pos('ggA1', code) == lastPos('ggA1', code) ,
              | verify(s, '()', 'm') < 1 then
        return repAll(substr(code, 8), 'ggA1', s)
    else
        return "fImm('"fmt"'," s")"
endProcedure fTstGen

/*--- generate timestamp formats: from format c to format d ---------*/
fTstGe2: procedure expose m.
parse arg c 2 d, s
    if pos(c, ' jJLlu') > 0 then do /* special cases */
        if c == ' ' then do  /* get current timestamp */
            if pos(d, 'sMAnY ') > 0 then
                return fTstGen('n'd, "date('S') time()")
            else if pos(d, 'DdEeJj') > 0 then
                return fTstGen('D'd, "date('S')")
            else if pos(d, 'tH') > 0 then
                return ftstGen('t'd, "time()")
            else if pos(d, 'T') > 0 then
                return fTstGen('T'd, "time('L')")
            else
                return fTstGen('N'd, "date('S') time('L')")
            end
        if c == 'j' then           /* via date D */
            return fTstGen('D'd, "date('s'," s", 'J')")
        if c == 'J' then
            return fTstGen('D'd, "date('s'," s", 'B')")
        call timeIni               /* via db2 timestamp */
        if c == 'L' then
            return fTstGen('S'd, 'timeLRSN2LZT('s')')
        if c == 'l' then
            return fTstGen('S'd, 'timeLRSN2LZT(c2x('s'))')
        if c == 'u' then
            return fTstGen('S'd, 'timeLRSN2LZT(timeUniq2lrsn('s'))')
        end

    if symbol('m.f_tstFo.c')=='VAR' & symbol('m.f_tstFo.d')=='VAR' then
        return ftstGFF(m.f_tstFo.c, m.f_tstFo.d, s)
    if m.f_tstIni == 1 then
        call err "bad timestamp from or to format '"c || d"'"
        /*--- initialize f_tst --------------------------------------*/
    m.f_tstIni = 1
    call utIni
    m.f_tstScan = 0
    a = 'F_TSTFO.'
    m.f_tstN0   =   'yz345678 hi:mn:st'
    m.f_tstN    =   'yz345678 hi:mn:st.abcdef'
    m.f_tstS0   =   'yz34-56-78-hi.mn.st'
    m.f_tstS    =   'yz34-56-78-hi.mn.st.abcdef'
        /*---------- picture characters not in DB2 timestamp
                     Y: year//25 A = 2000 Y=2024
                     Z: year//20 A = 2010                to deimplement
                     M: month B=Januar ...,
                     A: first digit of day A=0, D=30
                     B: day 1=1 10=A 31=V                 deimplemented
                     H: hour first digit  A=0 B=10 C=20 D=30
                     I: hour 1=A, 10=K 23=X               deimplemented
                     jjjjj: Julian
                     JJJJJJ: base date (days since 1.1.0001)
                     llllllllll: 10 Byte LRSN
                     LL...: 10 Byte LRSN as 20 HexCharacters
                     uuuuuuuu: db2 Utility Unique
                     qr: minuten//10, sec ==> aa - xy  base 25 ------*/
    m.f_tstPics =   'yz345678himnstabcdefYZMAHIjJlLuqr'
    m.f_tstZero =   '00010101000000000000???AAA??00?AA'
    call mPut a'S',  m.f_tstS
    call mPut a's',  m.f_tstS0
    call mPut a' ',  m.f_tstS0
    call mPut a'D', 'yz345678'
    call mPut a'd',   '345678'
    call mPut a't',            'hi.mn.st'
    call mPut a'T',            'hi:mn:st.abcdef'
    call mPut a'E', '78.56.yz34'
    call mPut a'e', '78.56.34'
    call mPut a'Y',    'YM78Imqr'
    call mPut a'Z',      'ZM78'    /* deimplement */
    call mPut a'M',    'M78himns'
/*  call mPut a'I',    'M78Imnst'   */
    call mPut a'A',    'A8himnst'
/*  call mPut a'B',    'YMBImnst'   */
    call mPut a'H',           'Himnst'
    call mPut a'n',  m.f_tstN0
    call mPut a'N',  m.f_tstN
    call mPut a'j', 'jjjjj' /* julian date 34jjj        */
    call mPut a'J', 'JJJJJJ' /* day since 1.1.00: 6 digits     */
    call mPut a'l', copies('l', 10) /*LRSN out 10 Byte, input var*/
    call mPut a'L', copies('L', 20) /* LRSN in hex */
    call mPut a'u', 'uuuuuuuu' /* Unique */
    return fTstGe2(c || d, s) /* retry after initialisation */
endProcedure fTstGe2

/*--- nest source s into code (at $)
      if source is not simpe and used several times then
          use fImm to avoid muliple evaluations ---------------------*/
fTstNest: procedure expose m.
parse arg code, s
    if pos('$', code) == lastPos('$', code) ,
              | verify(s, '(). ', 'm') < 1 then
        return repAll(code, '$', s)
    a = fCache('%>', 'return' repAll(code, '$', 'ggA1'))
    return "fImm('"a"'," s")"
endProcedure fTstFi

/*--- return rexx code for timestamp conversion
      from pic f to pic aT for source s -----------------------------*/
fTstgFF: procedure expose m.
parse arg f, aT, s
    m.f_tstScan = m.f_tstScan + 1
    a = f_tstScan || m.f_tstScan
    call scanSrc a, aT
    cd = ''
    pc = '' /* permutations and constants */
    do until t == ''
        c1 = '' /* a rexx function / expression */
        p1 = '' /* permutations and constants */
        tPos = m.a.pos
        call scanChar a, 1
        t = m.a.tok
        if pos(t, f' .:-') > 0 then do
            call scanVerify a, f' .:-', 'n'
            p1 = t || m.a.tok         /* permutate pics or constants */
            end
        else if pos(t, m.f_tstPics) <= 0 then do
            p1 = m.a.tok                                /* constants */
            end
        else if t == 'y' then do                             /* year */
            if scanLit(a, 'z34') then do
                if pos('34', f) > 0 then
                    c1 = "timeYear24(substr("s "," pos('34', f)", 2))"
                else if pos('Y', f) > 0 then
                    c1 = "timeY2Year(substr("s "," pos('Y', f)", 1))"
                end
            end
        else if t == '3' then do
            if scanLit(a, '4') then
                if pos('Y', f) > 0 then
                    c1 = "substr(timeY2Year(substr("s,
                            "," pos('Y', f)", 1)), 3)"
            end
        else if t == 'Y' then do
            if pos('34', f) > 0 then
                c1 = "timeYear2Y(substr("s "," pos('34', f)", 2))"
            end
        else if t == 'Z' then do
            if pos('34', f) > 0 then
                c1 = "timeYear2Z(substr("s "," pos('34', f)", 2))"
            end
        else if t == '5' then do                            /* month */
            if scanLit(a, '6') then
                if pos('M', f) > 0 then
                    c1 = "timeM2Month(substr("s"," pos('M', f)", 1))"
            end
        else if t == 'M' then do
            if pos('56', f) > 0 then
                c1 = "timeMonth2M(substr("s"," pos('56', f)", 2))"
            end
        else if t == '7' then do                              /* day */
            if scanLit(a, '8') then
                c1 = fTstGetDay(f, s)
            end
        else if t == 'A' then do
            if scanLit(a, '8') then do
                c1 = fTstGetDay(f, s)
                if c1 \== '' then
                    c1 = fTstNest("fI2C(left($, 1), 'ABCD')" ,
                                 || "right($, 1)", c1)
                end
            end
        else if t == 'h' then do                             /* hour */
            if scanLit(a, 'i') then
                c1 = fTstGetHour(f, s)
            end
        else if t == 'n' then do                             /* hour */
            if scanLit(a, 'i') then
                c1 = fTstGetHour(f, s)
            else if pos('qr', f) > 0 then do
                call scanLit a, 'st', '.st', ':st', 's', '.s', ':s'
                c1 = "fqr2ms(substr("s"," pos('qr', f)", 2)" ,
                    || ", '"left(m.a.tok, abbrev(m.a.tok, '.') ,
                                        | abbrev(m.a.tok, ':'))"')"
                if right(m.a.tok, 1) \== 't' then
                    c1 = "left("c1"," 1 + length(m.a.tok)")"
                end
            end
        else if t == 'H' then do
            if scanLit(a, 'i') then do
                c1 = fTstGetHour(f, s)
                if c1 \== '' then
                    c1 = fTstNest("fI2C(left($, 1), 'ABC')" ,
                                 || "right($, 1)", c1)
                end
            end
        else if t == 'I' then do
            c1 = fTstGetHour(f, s)
            if c1 \== '' then
                c1 = "fI2C("c1", m.ut_uc25)"
            end
        else if t == 'j' then do                           /* julian */
            if scanLit(a, 'jjjj') then
                c1 = "time2jul(" fTstGFF(f, 'yz34-56-78', s)")"
            end
        else if t == 'J' then do                  /* day since 1.1.1 */
            if scanLit(a, 'JJJJJ') then
                c1 = "date('B'," fTstGFF(f, 'yz345678', s)", 's')"
            end
        else if t == 'l' then do                     /* 10 byte lrsn */
            if scanLit(a, copies('l', 9)) then
                c1 = "x2c(timeLZT2LRSN("fTstGFF(f, m.f_tstS, s)"))"
            end
        else if t == 'L' then do                   /* lrsn in 20 hex */
            if scanLit(a, copies('L', 19)) then
                c1 = "timeLZT2LRSN("fTstGFF(f, m.f_tstS, s)")"
            end
        else if t == 'u' then do            /* 8 byte utility unique */
            if scanLit(a, 'uuuuuuu') then
                c1 = "timeLrsn2Uniq(timeLZT2LRSN(",
                        || fTstGFF(f, m.f_tstS, s)"))"
            end
        else if t == 'q' then do            /* 8 byte utility unique */
            if scanLit(a, 'r') then
                if pos('n', f) > 0 then do
                    c1 = "fms2qr(substr("s"," pos('n', f)", 1),"
                    if pos('st', f) > 0 then
                        c1 = c1 "substr("s"," pos('st', f)", 2))"
                    else if pos('s', f) > 0 then
                        c1 = c1 "substr("s"," pos('s', f)", 1)'0')"
                    else
                        c1 = c1 "0)"
                    end
            end

        if pos(t, 'lLu') > 0 then
            call timeIni
        if c1 == '' & p1 == '' & t \== '' then /* nothing -> zero */
            p1 = translate(substr(m.a.src, tPos, m.a.pos-tPos),
                   , m.f_tstZero, m.f_tstPics)

        pc = pc || p1
        if (c1 \== '' | t == '') & pc \== '' then do/*append pc to cd*/
            if verify(pc, m.f_tstPics, 'm') == 0 then
                cd = cd '||' quote(pc, "'")
            else if pc == f then
                cd = cd '||' s
            else if pos(pc, f) > 0 then
                cd = cd "|| substr("s"," pos(pc, f)"," length(pc)")"
            else
                cd = cd "|| translate('"pc"'," s", '"f"')"
            pc = ''
            end
        if c1 \== '' then                         /* append pc to cd */
            cd = cd '||' c1
        end
    m.f_tstScan = m.f_tstScan - 1
    if cd == '' then
        return "''"
    else
        return substr(cd, 5)
endProcedure fTstGFF

/*--- return code for day, d1Only = first digit only ----------------*/
fTstGetDay: procedure expose m.
parse arg f, s
    if pos('78', f) > 0 then
        return  "substr("s"," pos(78, f)", 2)"
    if pos('A', f) > 0 then
        if pos('8', f) > 0 then
            return "fc2i(substr("s"," pos('A', f)", 1), 'ABCD')",
                || "substr("s"," pos('8', f)", 1)"
    return ''
endProcedure fTstGetDay

/*--- return code for hour in 2 digits ------------------------------*/
fTstGetHour: procedure expose m.
parse arg f, s
    if pos('hi', f) > 0 then
        return "substr("s"," pos('hi', f)", 2)"
    if pos('Hi', f) > 0 then
        return "fC2I(substr("s"," pos('Hi', f)", 1), 'ABC')" ,
                 || "substr("s"," pos('Hi', f) + 1", 1)"
    if pos('I', f) > 0 then
        return "right(fC2I(substr("s"," pos('I', f)", 1)," ,
                     "m.ut_uc25), 2, 0)"
    return ''
endProcedure fTstGetHour

fms2qr: procedure expose m.
parse arg m, s
    t =  (m // 10) * 60 + s
    return substr(m.ut_uc25, t %  25 + 1,1),
        || substr(m.ut_uc25, t // 25 + 1,1)


fqr2ms: procedure expose m.
parse arg q, sep
    v = pos(left(q, 1), m.ut_uc25) * 25 ,
      + pos(substr(q, 2, 1), m.ut_uc25) - 26
    return (v % 60) || sep || right(v // 60, 2, 0)

fWords: procedure expose m.
parse arg fmt, wrds
    f2 = '%##fCatFmt' fmt
    if wrds = '' then
        return f(f2'%#0')
    res = f(f2'%#1', word(wrds, 1))
    do wx=2 to words(wrds)
        res = res || f(f2, word(wrds, wx))
        end
    return res || f(f2'%#r')
endProcedure fWords

fCat: procedure expose m.
parse arg fmt, st
    return fCatFT(fmt, st, 1, m.st.0)

fCatFT: procedure expose m.
parse arg fmt, st, fx, tx
    f2 = '%##fCatFmt' fmt
    if tx < fx then
        return f(f2'%#0')
    res = f(f2'%#1', m.st.fx)
    do sx=fx+1 to tx
        res = res || f(f2, m.st.sx)
        end
    return res || f(f2'%#r')
endProcedure fCatFT

fCatFmt: procedure expose m.
parse arg adr, fmt
    v.m = ''    /* middle */
    v.l = ''    /* left */
    v.r = ''    /* right */
    v.a = '%c'  /* all rows */
    nm = M
    cx = 1
    do forever        /* split clauses */
        cy = pos('#', fmt, cx)
        if cy < 1 then do
            v.nm = substr(fmt, cx)
            leave
            end
        v.nm = substr(fmt, cx, cy-cx)
        nm = translate(substr(fmt, cy+1, 1))
        cx = cy+2
        end
    if symbol('v.2') \== 'VAR' then  /* second and following */
        v.2 = v.M || v.a
    adr = fGen(adr, v.2)
    if symbol('v.0') \== 'VAR' then  /* empty */
        v.0 = v.l || v.r
    call fGen adr'%#0', v.0
    if symbol('v.1') \== 'VAR' then /* first row */
        v.1 = v.l || v.a
    call fGen adr'%#1', v.1
    call fGen adr'%#r', v.R
    return adr
endProcedure fCatFmt

/*--- format with units seconds ==> 3d13 oder 5.43e6 ==> 5M43 -------*/
fUnit: procedure expose m.
parse arg uFmt, v /* scale, aLen, aPrec, plus */
    uF = 'F_UNIT.'uFmt                 /* address of (global) format */
    if symbol('m.uF.0') \== 'VAR' then
         call fUnitGen uFmt
    if \ dataType(v, 'n') then
        return right(v, m.uF.len)
    uS = uF']' || (v >= 0)               /* address of signed format */
    v = abs(v)                /* always get rid also of sign of -0 | */


    do fx=11 to m.uF.0-1 while v >= m.uS.fx.lim1     /* search range */
        end
    if fx = 11 & v <> trunc(v) then do
        do fx=10 by -1 to m.uF.min while v < m.uS.fx.lim1
            end
        fx = fx + 1
        end

    do fx=fx to m.uF.0                              /* try to format */
        uU = uS'.'fx
        w = format(v * m.uU.fact, , m.uU.prec)    /* address of Unit */
        if pos('E-', w) > 0 then
            w = format(0, , m.uU.prec)
        if w < m.uU.lim2 then do
            if m.uU.kind == 'r' then
                x = m.uS.sign || w || m.uU.unit
            else if m.uU.kind == 'm' then
                x = m.uS.sign || (w % m.uU.mod) || m.uU.mUnit ,
                    || right(w // m.uU.mod, m.uF.len2, 0)
            else
                call err 'bad kind' m.uU.kind 'in uU' uU
            if length(x) <= m.uF.len then
                return right(x, m.uF.len)
            end
        end
    return left(m.uS.sign, m.uF.len, '+')
endProcedure fUnit

/*--- generate all format entries for given scale -------------------*/
     aLen = total len, pLen =len of +, sLen = len of current sign ---*/
fUnitGen: procedure expose m.
parse arg uFmt
parse arg scale 2 aMid '[' plus
parse var aMid aLen '.' aPrec
    if pos(']', uFmt) > 0 then
        call err 'bad fUnit format' uFmt
    sc = 'F_SCALE.'scale
    uF = 'F_UNIT.'uFmt                 /* address of (global) format */
    if symbol('m.sc.0') \== 'VAR' then do
        call fUnitIni
        if symbol('m.sc.0') \== 'VAR' then
            call err 'bad scale' sc 'for fUnitGen('uFmt')'
        end

    hasM = scale = 't'
    if aPrec == '' then
        if scale = 't' then
            aPrec = 2
        else
            aPrec = 0
    if aLen = '' then
        if scale = 't' then
            aLen = length(plus) + 3 + aPrec
        else
            aLen = aPrec + (aPrec >= 0) + 4 + length(plus)
    m.uF.len2  = aPrec
    if hasM then
        aPrec = 0
    m.uF.len = aLen
    m.uF.0   = m.sc.0
    m.uF.min = m.sc.min
    do geq0=0 to 1
        uS = uF']'geq0                   /* address of signed format */
        if geq0 then do
            m.uS.sign = plus
            end
        else do
            m.uS.sign = '-'
            end
        sLen = length(m.uS.sign)
        dLen = aLen - sLen - hasM
        limR = '1e' || (aLen -sLen - hasM - (aPrec > 0) - aPrec)
        limM = '1e' || (aLen - m.uF.len2 - 1 - sLen)
        do ix=m.sc.0 by -1 to m.sc.min
            uU = uS'.'ix                      /* address of one unit */
            m.uU.unit = m.sc.ix.unit
            m.uU.fact = m.sc.ix.fact
            m.uU.val  = m.sc.ix.val
            m.uU.kind = m.sc.ix.kind
            m.uU.Len  = aLen
            m.uU.prec = aPrec
            if m.uU.kind = 'r' then do
                m.uU.lim2 = limR
                m.uU.lim1 = limR * m.uU.val
                end
            else do
                iy = ix + 1
                iz = ix + 2
                m.uU.mUnit = m.sc.iy.unit
                m.uU.mod   = m.sc.iy.val % m.sc.ix.val
                m.uU.wid2  = aPrec
                if iz <= m.sc.0 & m.sc.iz.kind == 'm' then
                    m.uU.lim1  = m.sc.iz.val
                else
                    m.uU.lim1 = limM * m.sc.iy.val
                m.uU.lim2  = m.uU.lim1 % m.uU.val
                end
            end
        end
    return
endProcedure fUnitGen

fUnitIni: procedure expose m.
    if m.f_unit_ini == 1 then
        return
    m.f_unit_ini = 1
      /*  0    5   10    5   20 */
    iso = '    afpnum kMGTPE   '
    sB = f_Scale'.b'
    sD = f_Scale'.d'
    sT = f_Scale'.t'
    fB = 1
    fD = 1
    call fUnitIni2 sB, 11, ' ', 'r', fB
    m.sB.0   =  17
    m.sB.min =  11
    call fUnitIni2 sD, 11, ' ', 'r', fD
    m.sD.0   = 17
    m.sd.min =  5
    do x=1 to 6
        fB = fB * 1024
  /*    call fUnitIni2 sB, (11-x), substr(iso, 11-x, 1), 'r', fB*/
        call fUnitIni2 sB, (11+x), substr(iso, 11+x, 1), 'r', 1/fB
        fD = fD * 1000
        call fUnitIni2 sD, (11+x), substr(iso, 11+x, 1), 'r', 1/fD
        call fUnitIni2 sD, (11-x), substr(iso, 11-x, 1), 'r', fD
        end
    kilo = 'k'
    m.sB.u2v.k = m.sB.u2v.kilo
    m.sD.u2v.k = m.sD.u2v.kilo
    m.sT.0   =  16
    m.sT.min =  11
    call fUnitIni2 sT, 11, ' ', 'm', 100
    call fUnitIni2 sT, 12, 's', 'm',   1
    call fUnitIni2 sT, 13, 'm', 'm', 1/60
    call fUnitIni2 sT, 14, 'h', 'm', 1/3600
    call fUnitIni2 sT, 15, 'd', 'm', 1/3600/24
    call fUnitIni2 sT, 16, 'd', 'r', 1/3600/24
    return 0
endProcedure fUnitIni

fUnitIni2: procedure expose m.
parse arg sc, ix, u, ki, fa
    sb = sc'.'ix
    m.sb.kind = ki
    m.sb.fact = fa
    m.sb.unit = u
    m.sb.val     = 1 / fa
    if m.sb.fact > 1 then
        m.sb.fact = format(fa, , 0)
    else
        m.sb.val  = format(m.sb.val, , 0)
    m.sc.u2v.u = m.sb.val
    return
endProcedure fUnitIni2

fUnitsF1I0: procedure expose m.
parse arg sc, ix
    si = sc'.'ix
parse arg , , m.si.kind, aU, m.si.fact,
                , m.si.lim2, m.si.len,
                , m.si.mod, m.si.len2
    m.si.unit = aU
    m.sc.u2f.aU = ''
    if \ datatype(ix, 'n') then
        return si
    m.sc.u2f.aU = 1 / m.si.fact
    if symbol('m.sc.0') \== 'VAR' then do
        m.sc.0   = ix
        m.sc.min = ix
        end
    else do
        m.sc.0   = max(ix, m.sc.0)
        m.sc.min = min(ix, m.sc.min)
        end
    return si
endProcedure fUnitsF1I0

fUnit2I: procedure expose m.
parse arg b, v
    v = strip(v)
    if datatype(v, 'n') then
        return v
    u = right(v, 1)
    key = f_Scale'.' || b'.U2V.'u
    if symbol('m.key') == 'VAR' then
        return strip(left(v, length(v)-1)) * m.key
    if m.f_unit_ini \== 1 then
        return fUnit2I(b, v, fUnitIni())
    call err 'bad unit' u 'or base' b 'for' v
endProcedure fUnit2I
/* copy f end   ******************************************************/