zOs/REXX.O13/COMP

/* copy comp begin *****************************************************
    the shell compiler
         syntax and semantics of the shell language see javaDoc
***********************************************************************/
/***** initialisation *************************************************/
/*--- module initialisation ------------------------------------------*/
compIni: procedure expose m.
    if m.compIni = 1 then
        return
    m.compIni = 1
    call pipeIni
    call scanReadIni
    cc = classNew('n Compiler u')
    call mNewArea 'COMP.AST', '='
    m.comp.stem.0 = 0
    m.comp.idChars = m.scan.alfNum'@_'
    call compIniKI '=', "skeleton", "expression or block"
    call compIniKI '.', "object", "expression or block"
    call compIniKI '-', "string", "expression or block"
    call compIniKI '@', "shell", "pipe or $;"
    call compIniKI ':', "assignAttributes", "assignment or statement"
    call compIniKI '|', "assignTable", "header, sfmt or expr"
    call compIniKI '#', "text", "literal data"
    return
endProcedure compIni

compReset: procedure expose m.
parse arg m
    m.m.scan = scanRead(,,'|0123456789')
    m.m.chDol = '$'
    m.m.chSpa = ' ' || x2c('09')
    m.m.chNotBlock = '${}='
    m.m.chNotWord = m.m.chNotBlock || m.m.chSpa
    m.m.chKind = '.-=#@:|'
    m.m.chKin2 = '.-=#;:|'
    m.m.chKinC = '.-=@'
    m.m.chOp = '.-<@|?'
    m.m.chOpNoFi = '.-@|?'
    return m
endProcedure compReset

compIniKI: procedure expose m.
parse arg ki, m.comp.kind.ki.name, m.comp.kind.ki.expec
return

/*--- constructor of Compiler ----------------------------------------*/
comp: procedure expose m.
parse arg src
    nn = oNew('Compiler')
    if src \== '' then
        m.nn.cmpRdr = o2File(src)
    else
        m.nn.cmpRdr = ''
    return nn
endProcedure comp

/**** user interface **************************************************/
/*--- compile and run ------------------------------------------------*/
compRun: procedure expose m.
parse arg spec, inO, ouO, infoA
    cmp = comp(inO)
    r = compile(cmp, spec)
    if infoA \== '' then
        m.infoA = 'run'
    if ouO \== '' then
        call pipeBeLa '>' ouO
    call oRun r
    if ouO \== '' then
        call pipeEnd
    return 0
endProcedure compRun

/*--- compile the source and return an oRunner with the code ---------*/
compile: procedure expose m.
parse arg m, spec
    call compReset m
    kind = '@'
    spec = strip(spec)
    do while pos(left(spec, 1), m.m.chKind) > 0
       kind = left(spec, 1)
       spec = strip(substr(spec, 2))
       end
    call scanSrc m.m.scan, spec
    m.m.compSpec = 1
    res = compCUnit(m, kind, 1)
    do while abbrev(m.m.dir, '$#')
        call envPutO substr(m.m.dir, 3, length(m.m.dir)-4),
            , compCUnit(m, right(m.m.dir, 1))
        end
    if \ m.m.compSpec then
        call jClose m.m.scan
    return res
endProcedure compile

/*--- cUnit = compilation Unit = separate compilations
              no nesting| --------------------------------------------*/
compCUnit: procedure expose m.
parse arg m, ki, isFirst
    s = m.m.scan
    code = ''
    do forever
        m.m.dir = ''
        src = compUnit(m, ki, '$#')
        if \ compDirective(m) then
            return scanErr(s, m.comp.kind.ki.expec "expected: compile",
                 m.comp.kind.ki.name "stopped before end of input")
        if \ compIsEmpty(m, src) then do
                /*wkTst??? allow assTb in separatly compiled units */
            if isFirst == 1 & m.src.type == ':' ,
              & pos(' ', src) < 1 &  abbrev(src, 'COMP.AST.') then
                call mAdd src, '', ''
            code = code || ';'compAst2code(m, src, ';')
            end
        if m.m.dir == 'eof' then do
            if \ m.m.compSpec | m.m.cmpRdr == '' then
                return oRunner(code)
            call scanReadReset s, m.m.cmpRdr
            call jOpen s, m.j.cRead
            m.m.compSpec = 0
            end
        else if length(m.m.dir) == 3 then
            ki = substr(m.m.dir, 3, 1)
        else
            return oRunner(code)
        end
endProcedure compCUnit

/*--- directives divide cUnits ---------------------------------------*/
compDirective: procedure expose m.
parse arg m
    m.m.dir = ''
    s = m.m.scan
    lk = scanLook(s)
    cx = pos('#', lk, 3)
    if \ abbrev(lk, '$#') then do
        if \ scanAtEnd(m.m.scan) then
            return 0
        m.m.dir = 'eof'
        return 1
        end
    else if scanLit(s, '$#end' , '$#out') then do
        m.m.dir = 'eof'
        return 1
        end
    else if pos(substr(lk, 3, 1), m.m.chKinD) > 0 then do
        m.m.dirKind = substr(lk, 3, 1)
        m.m.dir = left(lk, 3)
        end
    else if cx > 3 & pos(substr(lk, cx+1, 1), m.m.chKinD) > 0 then do
        m.m.dirKind = substr(lk, 3, 1)
        m.m.dir = left(lk, cx+1)
        end
    else
        call scanErr s, 'bad directive:' word(lk, 1)
    if \ scanLit(s, m.m.dir) then
            call scanErr m.m.scan, 'directive mismatch' m.m.dir
    return 1
endProcedure compDirective

/**** parse the whole syntax *******************************************
          currently, with the old code generation,
              parsing and code generation is intermixec
              migrating to AST should will separate these tasks
***********************************************************************/
compUnit: procedure expose m.
parse arg m, kind, stopper
    s = m.m.scan
    if pos(kind, m.m.chKind';') < 1 then
        return scanErr(s, 'bad kind' kind 'in compUnit(...'stopper')')
    if stopper == '}' then do
        if kind \== '#' then do
            one = compExpr(m, 'b', translate(kind, ';', '@'))
            if compisEmpty(m, one) then
                return compAST(m, 'block')
            else
                return compAST(m, 'block', one)
            end
        tx = '= '
        cb = 1
        do forever /* scan nested { ... } pairs */
            call scanVerify s, '{}', 'm'
            tx = tx || m.s.tok
            if scanLit(s, '{') then
                cb = cb + 1
            else if scanLook(s, 1) \== '}' then
                call scanErr s, 'closing } expected'
            else if cb <= 1 then
                leave
            else if scanLit(s, '}') then
                cb = cb - 1
            else
                call scanErr s, 'closing } programming error'
            tx = tx || m.s.tok
            end
        return compAst(m, 'block', tx)
        end
    else if pos(kind, '.-=') > 0 then do
        return compData(m, kind)
        end
    else if pos(kind, '@;') > 0 then do
        call compSpNlComment m
        return compShell(m)
        end
    else if kind == '|' | kind == ':' then do
        if kind == '|' then
            res = compAssTab(m)
        else
            res = compAssAtt(m)
        if abbrev(res, '#') then
            return compAst(m, ':', substr(res, 3))
        else
            return compAst(m, ';', substr(res, 3))
        end
    else if kind == '#' then do
        res = compAST(m, 'block')
        call compSpComment m
        if \ scanReadNl(s) then
            call scanErr s,
                , 'space nl expected in heredata until' stopper
        do while \ abbrev(m.s.src, stopper)
            call mAdd res, '=' strip(m.s.src, 't')
            if \ scanReadNl(s, 1) then do
                if stopper = '$#' then
                    leave
                call scanErr s, 'eof in heredata until' stopper
                end
            end
        return res
        end
endProcedure compUnit

/*--- compile data lines return stmts or expr ------------------------*/
compData: procedure expose m.
parse arg m, ki
    s = m.m.scan
    lines = compAST(m, 'block')
    do forever
        state = 'f'
        do forever
            l = compExpr(m, 'd', ki)
            if \ scanReadNL(s) then
                state = 'l'
            if \ compIsEmpty(m, l) | (state=='' &\abbrev(l, 'c')) then
                call mAdd lines, l
            if state == 'l' then
                leave
            call compComment m
            state = ''
            end
        one = compStmt(m)
        if one == '' then
            leave
        call mAdd lines, one
        call compComment m
        end
    return lines
endProcedure compData

/*--- compile shell and return code ----------------------------------*/
compShell: procedure expose m.
parse arg m
    a = compAst(m, ';')
    m.a.text = ''
    do forever
        one = compPipe(m)
        if one \== '' then
            m.a.text = m.a.text || one
        if \ scanLit(m.m.scan, '$;') then
            return a
        call compSpNlComment m
        end
endProcedure compShell

/*--- compile an expression, type d=data, b=block w=word, s=strip ---*/
compExpr: procedure expose m.
parse arg m, type, ki
    s = m.m.scan
    if length(type) \== 1 | pos(type, 'dsbw') < 1 then
        call scanErr s, 'bad type' type 'in compExpr(,' type','ki ')'
    if length(ki) \== 1 | pos(ki, m.m.chKind';') < 1 then
        call scanErr s, 'bad kind' ki 'in compExpr(,' type','ki ')'
    charsNot = if(type=='b', m.m.chNotBlock,
            , if(type=='w', m.m.chNotWord,m.m.chDol))
    laTx = 9e9
    st = compNewStem(m)
    gotCom = 0
    if pos(type, 'sb') > 0 then do
        call compSpComment m
        gotCom = gotCom | m.m.gotComment
        end
    ki2 = if(ki=='=', '-=', ki)
    do forever
        if scanVerify(s, charsNot, 'm') then do
            call mAdd st, ki2 m.s.tok
            laTx = min(laTx, m.st.0)
            end
        else do
            pr = compPrimary(m, ki, 1)
            if pr = '' then
                leave
            call mAdd st, pr
            laTx = 9e9
            end
        gotCom = gotCom | compComment(m)
        end
    do rx = m.st.0 by -1 to laTx while m.st.rx = ki2
        end
    if pos(type, 'bs') > 0 then do
       if rx >= laTx then
           m.st.rx = strip(m.st.rx, 't')
       m.st.0 = rx
       end
   if ki == '=' then
       if m.st.0 < 1 then
           return 'e='
       else
           ki = '-'
    return substr('ce', 2-gotCom, rx < 1)ki'0*' st
endProcedure compExpr

/*--- compile a primary and return code ------------------------------*/
compPrimary: procedure expose m.
parse arg m, ki, withChain
    s = m.m.scan
    if \ scanLit(s, '$') then
        return ''
    if scanString(s) then     /*wkTst??? brauchts beides? */
        return translate(ki, '.--', '@;=')'=' m.s.val
    if withChain then do
        if scanLit(s, '.', '-') then do
            op = m.s.tok
            return op'('compCheckNN(m, compObj(m, op),
                , 'objRef expected after $'op)
            end
        end
    if pos(ki, '.<') >= 1 then
        f = '. envGetO'
    else
        f = '- envGet'
    if scanLit(s, '{') then do
        if scanLit(s, '?') then
            f = '- envIsDefined'
        else if scanLit(s, '>') then
            f = '- envReadO'
        res = compCheckNE(m, compExpr(m, 'b', '='), 'var name')
        if \scanLit(s, '}') then
            call scanErr s, 'closing } missing after ${'
        return f'(' || comp2Code(m, '-'res)')'
        end
    if scanName(s) then
        return f"('"m.s.tok"')"
    call scanBack s, '$'
    return ''
endProcedure compPrimary

compObj: procedure expose m.
parse arg m, ki
    s = m.m.scan
    pk = compOpKi(m, '?')
    one = compBlock(m, ki pk)
    if one \== '' then
        return compAstAddOp(m, one, ki)
    pp = ''
    if pk \== '' then do
        ki = right(pk, 1)
        pp = left(pk, length(pk)-1)
        end
    one = compPrimary(m, translate(ki, '.', '@'), 0)
    if one \== '' then
        return pp || one
    if ki == '.' then do
        if scanLit(s, 'compile') then do
            if pos(scanLook(s, 1), m.m.chKinC) < 1 then
                call scanErr s, 'compile kind expected'
            call scanChar s, 1
            return pp'. compile(comp(j2Buf()), "'m.s.tok'")'
            end
        end
    call scanBack s, pk
    return ''
endProcedure compObj

compFile: procedure expose m.
parse arg m
    res = compCheckNE(m, compExprBlock(m, '='),
        , 'block or expr expected for file')
    if \ abbrev(res, '.') then do
        end
    else if substr(res, verify(res, '.', n), 3) == '0* ' then do
        st = word(res, 2)
        if m.st.0 = 1 & abbrev(m.st.1, '. envGetO(') then
                /* if undefined variable use new jbuf */
            if pos(')', m.st.1) == length(m.st.1) then
                m.st.1 = left(m.st.1, length(m.st.1)-1) ,
                         || ", '-b')"
        end
    return compASTAddOp(m, res, '<')
endProcedure compFile

/*--- scan an operator chain and a kind ------------------------------*/
compOpKi: procedure expose m.
parse arg m, opt
    s = m.m.scan
    op = ''
    if opt == '<' then do
        call scanVerify s, m.m.chOpNoFi
        op = m.s.tok
        if scanLit(s, '<') then
            return op'<'
        end
    call scanVerify s, m.m.chOp
    op = op || m.s.tok
    k1 = scanLook(s, 1)
    if k1 \== '' & pos(k1, m.m.chKind) > 0 then do
        call scanLit s, k1
        return op || k1
        end
    if opt == '?' | op == '' | pos(right(op, 1), m.m.chKind) > 0 then
        return op
    call scanErr s, 'no kind after ops' op
endProcedure compOpKi

/*--- block or expression --------------------------------------------*/
compExprBlock: procedure expose m.
parse arg m, ki
    s = m.m.scan
    pk = compOpKi(m, '<')
    if right(pk, 1) == '<' then
        return compAstAddOp(m, compFile(m), pk)
    res = compBlock(m, ki pk)
    if res \== '' then
        return res
    if pk \== '' then
        lk = right(pk, 1)
    else
        lk = translate(ki, '.', '@')
    res = compExpr(m, 's', lk)
    if res \== '' then
        return compASTAddOp(m, res, pk)
    call scanBack s, pk
    return res
endProcedure compExprBlock

/*--- compile a pipe and return code ---------------------------------*/
compPipe: procedure expose m.
parse arg m
    s = m.m.scan
    ios = ''
    stmts = ''
    stmtLast = ''
    do forever
        io1 = compRedirIO(m, 1)
        if io1 \== '' then do
            ios = ios',' io1
               call compSpNlComment m
            end
        else do
            if stmtLast \== '' then do
                if \ scanLit(s, '$|') then
                    leave
                call compSpNlComment m
                end
            one = comp2code(m, ';'compStmts(m))
            if one == '' then do
                if stmtLast \== '' then
                    call scanErr s, 'stmts expected after $|'
                if ios == '' then
                    return ''
                leave
                end
           if stmtLast \== '' then
                stmts = stmts'; call pipe' || stmtLast
            stmtLast = ';' one
            end
        end
    if stmts \== '' then
        stmtLast = insert('Begin', stmts, pos('pipe;', stmts)+3) ,
                   || '; call pipeLast' stmtLast'; call pipeEnd'
    if ios \== '' then do
        if stmtLast == '' then
            stmtLast = '; call pipeWriteAll'
        stmtLast = '; call pipeBeLa 'substr(ios, 3) || stmtLast';' ,
                   'call pipeEnd'
        end
    return stmtLast
endProcedure compPipe

/*--- compile an io redirection, return
        if makeExpr then "option", expr
                    else code write inut to standard out -------------*/
compRedirIO: procedure expose m.
parse arg m
    s = m.m.scan
    if \ scanLit(s, '$<', '$>>', '$>') then
        return ''
    opt = substr(m.s.tok, 2)
    return "'"opt"'" comp2Code(m, compFile(m))
endProcedure compRedirIO

/*--- compile stmts (stmt or java) -----------------------------------*/
compStmts: procedure expose m.
parse arg m
    lst = compNewStem(m)
    do forever
        one = compStmt(m)
        if one == '' then do
            do forever
                la = compExpr(m, 's', ';')
                if compIsEmpty(m, la) then
                    leave
                la = strip(comp2code(m, ';'la))
                if right(la, 1) \== ',' then do
                    one = one la
                    leave
                    end
                one = one strip(left(la, length(la)-1))
                call compSpNlComment m
                end
             if one = '' then
                 return 'l*' lst
             one = ';' one
             end
        call mAdd lst, one
        call compSpNlComment m
        end
endProcedure compStmts

/*--- compile a single statement -------------------------------------*/
compStmt: procedure expose m.
parse arg m
    s = m.m.scan
    if scanLit(s, "$=") then do
        res = compAss(m)
        if res == '' then
            call scanErr s, 'assignment expected after $='
        return res
        end
    if scanLit(s, '$@') then do
        if \ scanName(s) then
            return 'l;' comp2Code(m,
                , '@'compCheckNE(m, compExprBlock(m, '@'),
                , "block or expr expected after $@"))
        fu = m.s.tok
        if fu == 'for' | fu == 'with' | fu == 'forWith' then do
            v = comp2Code(m, '-'compCheckNE(m, compExpr(m, 'b', '='),
                   , "variable name after $@for"))
            call compSpComment m
            st = comp2Code(m, ';'compCheckNN(m, compStmt(m, 'with'),
                     , "statement after $@for" v))
            if fu == 'forWith' then
                st = 'call envSetWith envGetO('v');' st
            if abbrev(fu, 'for') then
                st = 'do while envReadO('v');' st'; end'
            if fu == 'forWith' then
                st = 'call envPushWith "";' st '; call envPopWith'
            else if fu == 'with' then
                st = 'call envPushName' v';' st '; call envPopWith'
            return ';' st
            end
        if fu == 'do' then do
            call compSpComment m
            var = if(scanName(s), m.s.tok, '')
            pre = var
            call compSpComment m
            if scanLook(s, 1) \== '=' then
                var = ''
            call compSpComment m
            suf = compExpr(m, 's', ';')
            if \ compIsEmpty(m, suf) then
                suf = comp2Code(m, ':'suf)
            else if var \== '' then
                call scanErr s, "$@do control construct expected"
            else
                suf = ''
            call compSpComment m
            st = comp2Code(m, ";"compCheckNN(m, compStmt(m),
                     , "$@do statement"))
            return "; do" pre suf";",
                if(var \== "", "call envPut '"var"'," var";") st"; end"
            end
        if fu == 'ct' then do
            call compSpComment m
            call compInter(comp2Code(m, ';'compCheckNN(m, compStmt(m),
                , 'ct statement')));
            return '; '
            end
        if fu == 'proc' then do
            nm = compCheckNE(m, compExpr(m, 'b', '='), "proc name")
            call compSpComment m
            st = oRunner(comp2Code(m, ';'compCheckNN(m, compStmt(m),
                , 'proc statement')));
            call envPutO compInter('return' comp2Code(m, '-'nm)), st
            return '; '
            end
        if scanLit(s, '(') then do
            call compSpComment m
            if \ scanLit(s, ')') then
                call scanErr s, 'closing ) expected after $@'fu'('
            return '; call oRun envGetO("'fu'")'
            end
        if scanLit(s, '{', '.{', '-{', '={') then do
            br = m.s.tok
            a = compExpr(m, 'b', if(br == '{', '-', left(br, 1)))
            if \ scanLit(s, '}') then
                call scanErr s, 'closing } expected after $@'fu || br
            res = '; call oRun envGetO("'fu'")'
            if pos(left(a, 1), 'ec') < 1 then
                res = res',' comp2code(m, a)
            return res
            end
        call scanErr s, 'procCall, for, do, ct, proc' ,
                 'or objRef expected after $@'
        end
    if scanLit(s, '$$') then
        return  compCheckNN(m, compExprBlock(m, '='),
            , 'block or expression expected after $$')
    return ''
endProcedure compStmt

compAss: procedure expose m.
parse arg m, aExt
    s = m.m.scan
    sla = scanLook(s)
    slx = verify(sla, m.m.chKind'/'m.m.chOp, 'n')
    if slx > 0 then
       sla = left(sla, slx-1)
    sla = pos('/', sla) > 0
    nm = ''
    if \ sla then do
        nm = compExpr(m, 'b', '=')
        if compIsEmpty(m, nm) then
            return ''
        nm = comp2Code(m, '-'nm)
        if \ scanLit(s, "=") then
            return scanErr(s, '= expected after $=' nm)
        end
    m.m.bName = ''
    vl = compCheckNE(m, compExprBlock(m, '='),
        , 'block or expression after $=' nm '=')
    if sla then
        if m.m.bName == '' then
            call scanErr s, 'missing blockName'
        else
            nm = "'"m.m.bName"'"
    va = compAstAftOp(m, vl)
    if va \== '' & m.va.type == ':' then do
        pu = "call envPushName" nm
        if abbrev(m.m.astOps, '<') then
            call mAdd va, pu ", 'asM'", "call envPopWith"
        else if abbrev(m.m.astOps, '<<') then
            call mAdd va, pu ", 'asM'", "call envPopWith"
        else
            call mAdd va, pu ", 'as1'", "call envPopWith"
        return va
        end
    if compAstKind(m, vl) == '-' then
        return '; call envPut' nm',' comp2Code(m, vl)aExt
    else
        return '; call envPutO' nm',' comp2Code(m, '.'vl)aExt
endProcedure compAss

/*--- block deals with the correct kind and operators
      the content is parsed by compUnit ------------------------------*/
compBlock: procedure expose m.
parse arg m, dKi ops
    s = m.m.scan
    if \ scanLit(s, '{', '[', '/') then
        return ''
    start = m.s.tok
    if (ops \== '' & pos(right(ops, 1), m.m.chKind) < 1) ,
        | pos(dKi, m.m.chKind) < 1 then
        return scanErr(s, 'bad kind' ops 'for block (def' dKi')')
    if ops == '' then do
        ki = dKi
        end
    else do
       ki = right(ops, 1)
       ops = left(ops, length(ops)-1)
       end
    starter = start
    if start == '{' then
        stopper = '}'
    else if start == '[' then
        stopper = '$]'
    else do
        call scanVerify s, '/', 'm'
        starter = '/'m.s.tok'/'
        stopper = '$'starter
        if \scanLit(s, '/') then
            call scanErr s, 'ending / after stopper' stopper 'expected'
        end
    res = compUnit(m, ki, stopper)
    if \ scanLit(s, stopper) then do
        if pos(ki, ':|') < 1 | \ abbrev(stopper, '$') then
            call scanErr s, 'ending' stopper 'expected after' starter
        else if \ scanLit(s, substr(stopper, 2)) then
            call scanErr s, 'ending' stopper 'or' substr(stopper, 2),
                    'expected after' starter
        end
    if abbrev(starter, '/') then
        m.m.bName = substr(starter, 2, length(starter)-2)
    else
        m.m.bName = ''
    if m.res.text == '' then
        m.res.text = ' '
    return compAstAddOp(m, res, ops)
endProcedure compBlock

compAssAtt: procedure expose m. aClass
parse arg m
    res = ''
    aClass = ''
    s = m.m.scan
    last = ''
    do forever
        if compSpNlComment(m, '*') then do
            end
        else if pos(scanLook(s, 1), '/]}') > 0 then do
            leave
            end
        else if scanLit(s, ';', '$;') then do
            if last = ';' then
                res = res'; call envWithNext'
            last = ';'
            end
        else do
            s1 = compAss(m, ", 1")
            if s1 == '' then do
                s1 = compStmt(m)
                if s1 == '' then
                    leave
                end
            else do
                if last == ';' then
                    res = res'; call envWithNext'
                last = 'a'
                end
            res = res';' comp2code(m, ';'s1)
            end
        if res ==  '' then
            res = ';'
        end
    if last == '' then
        return res
    else
        return '# call envWithNext "b";' res ,
               '; call envWithNext "e";'
endProcedure compAssAtt

compAssTab: procedure expose m. aClass
parse arg m
    s = m.m.scan
    call compSpNlComment m, '*'
    hy = 0
    tab = ''
    do forever
        bx = m.s.pos
        if \ scanName(s) then
            leave
        hx = hy + 1
        h.hx.beg = bx
        if hx > 1 & bx <= h.hy.end then
            call scanErr s, 'header overlap' m.s.tok 'pos' bx
        h.hx = m.s.tok
        tab = tab', f' m.s.tok 'v'
        h.hx.end = m.s.pos
        hy = hx
        call compSpComment m, '*'
        end
    if tab \== '' then
       aClass = classNew('n* Ass u' substr(tab, 3))
    res = ''
    isFirst = 1
    do while scanReadNl(s)
        do forever
            call compSpNlComment m, '*'
            s1 = compStmt(m)
            if s1 == '' then
                leave
            res = res';' comp2code(m, ';'s1)
            last = 's'
            end
        if pos(scanLook(s, 1), '/]}') > 0 then
            leave

        do qx=1
            bx = m.s.pos
            s1 = compExpr(m, 'w', '=')
            if compIsEmpty(m, s1) then
                leave
            ex = m.s.pos
            if ex <= bx then
                return scanErr(s, 'colExpr backward')
            do hy=1 to hx while bx >= h.hy.end
                end
            hz = hy+1
            if hz <= hx & ex > h.hz.beg then
                call scanErr s, 'value on hdr' h.hy 'overlaps' h.hz
            else if hy > hx | bx >= h.hy.end | ex <= h.hy.beg then
                call scanErr s, 'value from' bx 'to' ex ,
                    'no overlap with header' h.hy
            if qx > 1 then
                nop
            else if isFirst then do
                res = res"; call envWithNext 'b', '"aClass"'"
                isFirst = 0
                end
            else
                res = res"; call envWithNext"
            res = res"; call envPut '"h.hy"'," comp2Code(m, "-"s1)", 1"
            call compSpComment m, '*'
            end
        end
    if isFirst then
        return res
    else
        return '#' res"; call envWithNext 'e'"
endProcedure compassTab

/**** lexicals ********************************************************/
/*--- skip a comment. return 0 if there is none ----------------------*/
compComment: procedure expose m.
parse arg m
    s = m.m.scan
    res = 0
    do forever
        if scanLit(s, '$**') then
            m.s.pos = 1 + length(m.s.src) /* before next nl */
        else if scanLit(s, '$*+') then
            call scanReadNl s, 1
        else if scanLit(s, '$*(') then do
            do forever
                if scanVerify(s, m.m.chDol, 'm') then iterate
                if scanReadNl(s) then iterate
                if compComment(m) then iterate
                if \ scanLit(s, '$') then
                    call scanErr s, 'source end in comment'
                if scanLit(s, '*)') then
                    return 1
                if scanLit(s, '$') then iterate
                if scanString(s) then iterate
                end
            end
        else
            return res
        res = 1
        end
endProcedure compComment

/*--- skip spaces and comments ---------------------------------------*/
compSpComment: procedure expose m.
parse arg m, xtra
    s = m.m.scan
    sp = 0
    co = 0
    do forever
        if scanVerify(s, m.m.chSpa) then
            sp = 1
        else if compComment(m) then
            co = 1
        else if xtra == '' then
            leave
        else if \ scanLit(s, xtra) then
            leave
        else do
            co = 1
            m.s.pos = 1+length(m.s.src)
            end
        end
    m.m.gotComment = co
    return co | sp
endProcedure compSpComment

/*--- skip spaces, NLs and comments ----------------------------------*/
compSpNlComment: procedure expose m.
parse arg m, xtra
    found = 0
    do forever
        if compSpComment(m, xtra) then
            found = 1
        else if scanReadNl(m.m.scan) then
            found = 1
        else
            return found
        end
endProcedure compSpComment
/**** small helper routines ******************************************/
compInter: procedure expose m.
    interpret arg(1)
    return
endProcedure compInter

/*--- if va == null then issue an error with msg --------------------*/
compCheckNN: procedure expose m.
parse arg m, va, msg
    if va == '' then
        call scanErr m.m.scan, msg 'expected'
    return va
endProcedure compCheckNN

/*--- return true iff expression is empty ---------------------------*/
compIsEmpty: procedure expose m.
parse arg m, ex
    if pos(' ', ex) < 1 & pos('COMP.AST.', ex) > 0 then do
         a = substr(ex, pos('COMP.AST.', ex))
         a = compAstAftOp(m, a)
         if m.a.type = 'block' then
             return 0 /* m.a.0 == 0 */
         else
             return m.a.text == ''
         end
    e1 = word(ex, 1)
    return ex = '' | verify(e1, 'ec', 'm') > 0
endProcedure compIsEmpty

/*--- if va == null or empty then issue an error with msg -----------*/
compCheckNE: procedure expose m.
parse arg m, ex, msg
    e1 = left(ex, 1)
    if compIsEmpty(m, ex) then
        call scanErr m.m.scan, msg 'expected'
    return ex
endProcedure compCheckNE

/**** AST = Astract Syntax Graph ***************************************
          goal is to migrate to migrate to old codeGenerator to AST
***********************************************************************/
/*--- create a new AST -----------------------------------------------*/
compAST: procedure expose m.
parse arg m, tp
    n = mNew('COMP.AST')
    m.n.type = tp
    if wordPos(tp, 'block') > 0 then do
        do cx=1 to arg()-2
            m.n.cx = arg(cx+2)
            end
        m.n.0 = cx-1
        end
    else do
        m.n.text = arg(3)
        m.n.0 = 0
        end
    m.a.isAnnotated = 1
    return n
endProcedure compAST

/*--- add operandands to an AST -------------------------------------*/
compASTAddOp: procedure expose m.
parse arg m, a, ops
    if ops == '' then
        return a
    if pos('COMP.AST.', a) < 1 then
        return ops || a
    if m.a.type = 'ops' then do
        m.a.text = ops || m.a.text
        return a
        end
    n = compAst(m, 'ops', ops)
    call mAdd n, a
    return n
endProcedure compAstAddOp

/*--- return the first AST after the operand chain
          put the operands into m.m.astOps ---------------------------*/
compASTaftOp: procedure expose m.
parse arg m, a
    m.m.astOps = ''
    if \ abbrev(a, 'COMP.AST.') then
        return ''
    do while m.a.type == 'ops'
        m.m.astOps = m.a.text || m.m.astOps
        a = m.a.1
        end
    return a
endProcedure compASTAftOpType

/*--- return the kind of an AST --------------------------------------*/
compASTKind: procedure expose m.
parse arg m, a
    m.m.astOps = ''
    if \ abbrev(a, 'COMP.AST.') then
        return left(a, 1)
    c = a
    do while m.c.type == 'ops'
        if m.c.text \== '' then
            return left(m.c.text, 1)
        c = m.c.1
        end
    if a == c then
        return '?'
    return compAstKind(m, c)
endProcedure compASTKind

/*--- return the code for an AST with operand chain trg --------------*/
compAst2Code: procedure expose m.
parse arg m, a, aTrg
    if pos(' ', a) > 0 | \ abbrev(a, 'COMP.AST.') then
        return comp2Code(m, aTrg || a)
    if \ abbrev(a, 'COMP.AST.') then
        call err 'bad ast' a
    do while m.a.type == 'ops'
        aTrg = aTrg || m.a.text
        a = m.a.1
        end
    trg = compAstOpsReduce(m, aTrg)
    if m.a.type == translate(right(trg, 1), ';', '@') then do
        if length(trg) == 1 then do
            if pos(trg, ';@') > 0  then
                return 'do;' m.a.text ';end'
            else
                return m.a.text
            end
        else
            return compAST2Code(m, a, left(trg, length(trg)-1))
        end
    if m.a.type == 'block' then do
        op = right(trg, 1)
        tLe = left(trg, length(trg)-1)
        call compASTAnnBlock m, a
        if pos(m.a.maxKind, '.-<') > 0 & pos(op, '.-|?') > 0 then do
            if m.a.0 = 1 then do
                o1 = if(op=='-', '-', '.')
                r = compAst2Code(m, m.a.1, o1)
                r = compC2C(m, o1, compAstOpsReduce(m, tLe||o1), r)
                if pos(op, '.-<') > 0 then
                    return '('r')'
                else
                    return r
                end
            if m.a.0 = 0 & op == '?' then
                return compC2C(m, '.', compAstOpsReduce(m, tLe'.'))
            if op == '-' then do
                cd = ''
                do cx = 1 to m.a.0
                    cd = cd '('compAst2Code(m, m.a.cx, '-')')'
                    end
                return compC2C(m, '-', trg, substr(cd, 2))
                end
            call scanErr m.m.scan, 'bad block cardinality' aTrg
            end
        cd = ''
        do cx = 1 to m.a.0
            cd = cd';' compAst2Code(m, m.a.cx, ';')
            end
        if right(trg, 1) == '@' then
            trg = overlay(';', trg, length(trg))
        return compC2C(m, ';', trg, 'do;' cd'; end')
        end
    else if m.a.type == ';' then do
        return compC2C(m, ';', trg, m.a.text)
        if right(trg, 1)  == '-' then
            return compAst2Code(m, "- o2String('"oRunner(m.a.text)"')",
                , trg)
        if right(trg, 1)  == '<' then
            return compAst2Code(m, "< o2File('"oRunner(m.a.text)"')",
                , trg)
        end
    else if m.a.type == ':' then do
        if m.a.0 = 0 then
            call mAdd a, 'call envPushWith', 'call envPopWith'
        return compC2C(m, ';', trg,
            , 'do;' m.a.1';' m.a.text';' m.a.2'; end')
        end
    call scanErr m.m.scan, 'implement type' m.a.type 'for' a 'trg' trg
endProcedure compAst2Code

/*--- do a chain of code transformations
          from code of kind fr by opList


    op  as from kind               operand
     =  constant                   -
     -  rexx string Expr           cast to string/ concat file/output
     .  rexx object Expr           cast to object
     <  rexx file   Expr           cast to file
     ;  rexx Statements            execute, write obj, Str
     @  -                          cast to ORun, run an obj, write file
     |  -                          extract exactlyOne
     ?  -                          extract OneOrNull
----------------------------------------------------------------------*/

compC2C: procedure expose m.
parse arg m, fr, opList, code
oldCode = fr':' code '==>' opList '==>'
    do tx=length(opList) by -1 to 1
        to = substr(opList, tx, 1)
        if fr == to then
            iterate
        nn = '||||'
        if to == '-' then do
            if fr == '=' then
                 nn = quote(code)
            else if abbrev(fr code, '. envGetO(') then
                nn =  'envGet(' || substr(code, 9)
            else if fr == ';' then
                nn = "o2String('"oRunner(code)"')"
            else if pos(fr, '.<') > 0 then
                nn = "o2String("code")"
            end
        else if to == '.' then do
            if fr == '=' then
                 nn = quote(s2o(code))
            else if abbrev(fr expr, '- envGet(') then
                nn = 'envGetO('substr(expr, 8)
            else if fr == '-' then
                nn = 's2o('code')'
            else if fr == '<' then
                 nn = code
            else if fr == ';' then
                nn = quote(oRunner(code))
            end
        else if to == '@' then do
            if fr == '.' then
                nn = 'call oRun' code
            else if fr == '<' then
                nn = 'call pipeWriteAll' code
            else if fr == ';' then
                nn = code
            to = ';'
            end
        else if to == ';' then do
            if fr == '=' then
                nn = 'call out' quote(code)
            else if fr == '-' then
                nn = 'call out' code
            else if fr == '.' | fr == '<' then
                nn = 'call outO' code
            end
        else if to == ':' then do
            if fr == '=' then
                 nn = quote(code)
            else
                nn = code
            to = ';'
            end
        else if to == '<' then do
            if fr == '-' then
                 nn = 'file('code')'
            else if fr == '=' then
                 nn = "file("quote(code)")"
            else if fr == '.' then
                nn = 'o2File('code')'
            else if fr == ';' then
                nn = 'o2File('oRunner(code)')'
            end
        else if to == '|' | to == '?' then do
            if fr == '<' | fr == '.' then
                nn = 'fileSingle('code if(to == '|','', ", ''")')'
            else if fr == '@' | fr == ';' then
                      /* ???wkTst optimize: do it directly */
                nn = compC2C(m, fr, to'<', code)
            to = '.'
            end
        if nn == '||||' then
            return scanErr(m.m.scan,
                ,'compC2C bad fr' fr 'to' to 'list' opList)
        fr = to
        code = nn
        end
    return code
endProcedure compC2C

/*--- reduce a chain of operands -------------------------------------*/
          eliminate duplicates and identity transformations ----------*/
compAstOpsReduce: procedure expose m.
parse arg m, ops
    ki = ops
    ki  = space(translate(ops, ' ', 'e('), 0)
    fr = ';<; <;< -.- <@<'
    to = ';   <   -   <  '
    fr = fr '== -- .. << ;; @@ @('
    to = to '=  -  .  <  ;  @  (@'
    wc = words(fr)
    do until ki = oldKi
        oldKi = ki
        do wx=1 to wc
            do forever
                wf = word(fr, wx)
                cx = pos(wf, ki)
                if cx < 1 then
                    leave
                ki = left(ki, cx-1) || word(to, wx) ,
                                    || substr(ki, cx+length(wf))
                end
            end
        end
    return ki
endProcedure compASTOpsReduce

/*--- annotate a block if necessary ----------------------------------*/
compASTAnnBlock: procedure expose m.
parse arg m, a
    if m.a.isAnnotated == 1 then
        return
    mk = ''
    do cx=1 to m.a.0
       c = m.a.cx
       if pos(' ', c) > 0 | \ abbrev(c, 'COMP.AST.') then
           ki = left(c, 1)
       else if \ abbrev(c, 'COMP.AST.') then
           return scanErr(m.m.scan, 'bad ast' c 'parent' a) /0
       else
           call scanErr m.m.scan, 'implement kind of' c 'type' m.c.type
       if pos(ki, '=-.<;@:|') < 1 then do
           if pos(ki, 'el0') < 1 then
               call err 'bad kind' ki
           end
       else if mk == '' | pos(ki, '=-.<;@:|') > pos(mk, '=-.<;@:|') then
           mk = ki
       end
    m.a.maxKind = mk
    m.a.isAnnotated = 1
    return
endProcedrue compASTAnnBlock
/**** old code generator ***********************************************
          plan is to replace it with AST ******************************/
/*--- transform abstract syntax tree to code ------------------------
  wkTst??? codeTree besser dokumentieren
           optimizer an/und/abschaltbar machen
                (test sollte laufen, allenfalls gehen rexx variabeln
                                       verloren)
        syntax tree is simple, only where
        * a transformation is needed from several places or
        * must be deferred for possible optimizations

sn = ops*                 syntax node            op or syntax function
    ( '=' constant                            none
    | '-' rexxExpr     yielding string            cast to string
    | '.' rexxExpr     yielding object            cast to object
    | '<' rexxExpr     yielding file            cast to file
    | ';' rexxStmts                            execute, write obj, Str
    | '*' stem         yielding multiple sn    none
    )

ops = '@'                                    cast to ORun
    | '|'                                    single
    | 'e'                                    empty = space only
    | 'c'                                    empty = including a comment
    | '0'                                    cat expression parts
    | 'l'                                    cat lines
    | '('                                    add ( ... ) or do ... end
---------------------------------------------------------------------*/

comp2Code: procedure expose m.
parse arg m, ki expr
    if expr == '' & pos(' ', ki) < 1 & pos('COMP.AST.', ki) > 0 then do
         cx = pos('COMP.AST.', ki)
         return compAst2Code(m, substr(ki, cx), left(ki, cx-1))
         end
    /* wkTst??? optimize: use stem with code and interpret */
    if expr = '' & pos(right(ki, 1), '@;=') < 1 then
        return scanErr(m.m.scan, 'comp2Code empty expr' ki expr)
    do forever
        ki = comp2CodeKind(m, ki)
        if length(ki) <= 1 then
            if pos(ki, m.m.chKind';<') > 0 then
                return expr
            else
                call err 'comp2Code bad return' ki expr
        fr = right(ki, 1)
        to = substr(ki, length(ki)-1, 1)
        opt = ''
        if pos(to, 'l0') > 0 | (to == '*' & fr == '*') then do
            opt = to
            to = substr(ki, length(ki)-2, 1)
            end
        toBef = to
        nn = '||||'
        if fr == '*' then do
            if opt == '' then
                call scanErr m.m.scan, 'no sOp for * kind' ki expr
            cat = comp2CodeCat(m, expr, opt, to)
            parse var cat to nn
            end
        else if to == '-' then do
            if fr == '=' then
                 nn = quote(expr)
            else if abbrev(fr expr, '. envGetO(') then
                nn =  'envGet(' || substr(expr, 9)
            else if fr == ';' then
                nn = "o2String('"oRunner(expr)"')"
            else if pos(fr, '.<') > 0 then
                nn = "o2String("expr")"
            end
        else if to == '.' then do
            if fr == '=' then
                 nn = quote(s2o(expr))
            else if abbrev(fr expr, '- envGet(') then
                nn = 'envGetO('substr(expr, 8)
            else if fr == '-' then
                nn = 's2o('expr')'
            else if fr == '<' then
                 nn = expr
            else if fr == ';' then
                nn = quote(oRunner(expr))
            end
        else if to == '@' then do
            if fr == '.' then
                nn = 'call oRun' expr
            else if fr == '<' then
                nn = 'call pipeWriteAll' expr
            else if fr == ';' then
                nn = expr
            to = ';'
            end
        else if to == ';' then do
            if fr == '=' then
                nn = 'call out' quote(expr)
            else if fr == '-' then
                nn = 'call out' expr
            else if fr == '.' | fr == '<' then
                nn = 'call outO' expr
            else if fr == '#' then
                nn = 'call envPushWith ;'expr'; call envPopWith'
            end
        else if to == ':' then do
            if fr == '=' then
                 nn = quote(expr)
            else
                nn = expr
            to = ';'
            end
        else if to == '<' then do
            if fr == '-' then
                 nn = 'file('expr')'
            else if fr == '=' then
                 nn = "file("quote(expr)")"
            else if fr == '.' then
                nn = 'o2File('expr')'
            else if fr == ';' then
                nn = 'o2File('oRunner(expr)')'
            end
        else if to == '(' then do
            nn = compAddBracks(m, fr, expr)
            to = fr
            end
        else if to == '|' | to == '?' then do
            if fr == '<' | fr == '.' then do
                nn = 'fileSingle('expr if(to == '|','', ", ''")')'
                to = '.'
                end
            else if fr == '@' | fr == ';' then do
                to = to'<'fr
                nn = expr
                end
            end
        if nn == '||||' then
            return scanErr(m.m.scan,
                ,'comp2code bad fr' fr 'to' toBef 'for' ki expr)
        ki = left(ki, length(ki)-2-length(opt))to
        expr = nn
        end
endProcedure comp2Code

/*--- optimize operands: eliminate duplicates and
                         identity transformations -------------------*/
comp2CodeKind: procedure expose m.
parse arg m, ki
    ki = '$'space(translate(ki, '  ', 'ce'), 0)
    fr.2 = '== -- .. << ;; (( -( .(  ;( (< @;  @@ ;@ @( $l $0 @#'
    to.2 = '=   -  .  <  ;  ( (- (.  (; <  ;   @  @  (@ $  $  ;#'
    fr.3 = ';0; ;l; -.- -;- .-. .;. ;<; ;<( <(; @(- @(l |(l ?(l'
    to.3 = ' 0;  l;   -   -   .   .   ; ;<  <;  ;(- ;(l (|l (?l'
    do until ki = oldKi
        oldKi = ki
        do le=3 by-1 to 2
            do cx=1 while cx <= length(ki)+1-le
                wx = wordPos(substr(ki, cx, le), fr.le)
                if wx > 0 then
                    ki = left(ki, cx-1) || ,
                        word(to.le, wx) || substr(ki, cx+le)
                end
            end
        end
    return substr(ki, 2)
endProcedure comp2CodeKind

/*--- generate one codeString for one stem -------------------------*/
comp2CodeCat: procedure expose m.
parse arg m, st, sOp, trgt
    toCode = trgt == '@' | trgt == ';'
    if m.st.0 < 1 & trgt \== '<' then
        return trgt
    tr1 = trgt
    if \ toCode then do
                        /* check wether we need to evaluate statements
                            and cast the outptut to an object */
        maxTy = 0
         do x=1 to m.st.0
            maxTy = max(maxTy, pos(left(m.st.x, 1), '=-.<;@'))
            end
        if trgt \== '<' then do
            if maxTy >= 5 then do
                tr1 = ';'
                toCode = 1
                end
            end
        else do                        /* handle files */
            if maxTy > 1 then do    /* not constant */
                res = ';'
                do sx=1 to m.st.0
                    res = res';' comp2Code(m, ';'m.st.sx)
                    end
                return '<'res
                end
                                    /* constant file write to jBuf */
            buf = jOpen(jBuf(), m.j.cWri)
            do sx=1 to m.st.0
                call jWrite buf, substr(m.st.sx, 3)
                end
            return '<' quote(jClose(buf))
            end
        end

    if m.st.0 = 1 then do
        if trgt == '|' | trgt == '?' then
            return left(m.st.1, 1)  comp2Code(m, m.st.1)
        else if trgt \== '<' then
            return trgt comp2Code(m, trgt || m.st.1)
        end
    tr2 = tr1
    if toCode then do
        mc = '; '
        if sOp == 0 then do
            mc = ''
            tr2 = ':'
            end
        end
    else if sOp == '0' then
        mc = if(tr1 == '.' | tr1 == '-', '', ' || ')
    else if sOp == 'l' then
        mc = ' '
    else
        call scanErr m.m.scan, 'bad sOp' sOp ,
            'in comp2CodeCat('m',' st',' sOp',' trgt')'
    if symbol('m.st.1') \== 'VAR' then
        return err("bad m."st'.1')
    sep = if(tr1 == '.' | tr1 == '-' | tr1 == '=', ' || ', ' ')
    sep = if(sOp = 0, ' || ', ' ')
    tr3 = left(tr2, sOp \== 0)
    res = comp2Code(m, tr3 || m.st.1)
    do sx = 2 to m.st.0
        if (tr2 == '.' | tr2 == '-') ,
            & (m.st.sx = '-' | m.st.sx = '.') then do
                /* empty expr is simply a rexx syntax space */
            if right(res, 1) \== ' ' then
                res = res' '
            end
        else do
            act = comp2Code(m, tr3 || m.st.sx)
            res = compCatRexx(res, act, mc, sep)
            end
        end
    return copies(trgt || sOp, tr1 \== trgt)tr1 res
endProcedure comp2CodeCat

/*--- add expression brackets if necessary --------------------------*/
compAddBracks: procedure expose m.
parse arg m, ki, ex
    if ki == ';' then
         return 'do;' ex || left(';', ex \= '') 'end'
    if \ (ki == '.' | ki == '-') then
        return ex
    ex = strip(ex)
    e1 = left(ex, 1)
    if e1 == '(' & pos('(', ex, 2) = 0 & pos(')', ex) = length(ex) then
        return ex
    if pos(e1, '"''') > 0  & pos(e1, ex, 2) = length(ex) then
        return ex
    return '('ex')'
endProcedure compAddBracks

/*--- cat two rexx parts, avoid strange effects--------------------*/
compCatRexx: procedure expose m.
parse arg le, ri, mi, sep
    if mi \== '' then
        return le || mi || ri
    lr = right(le, 1)
    rl = left(ri, 1)
    if (lr == "'" | lr == '"') then do
        if rl == lr then                /* "a","b" -> "ab" */
            return left(le, length(le)-1) || substr(ri, 2)
        else if  rl == '(' then            /* "a",( -> "a" || ( */
            return le||sep||ri            /* avoid function call    */
        end
    else if pos(lr, m.comp.idChars) > 0 then
        if pos(rl, m.comp.idChars'(') > 0 then
            return le || sep || ri        /* a,b -> a || b */
    return le || mi || ri
endProcedure compCatRexx

/*--- push an empty stem on the stack --------------------------------*/
compNewStem: procedure expose m.
parse arg m
    st = mAdd('COMP.STEM', '')
    do ix=1 to arg()-1
        m.st.ix = arg(ix+1)
        end
    m.st.0 = ix-1
    return st
endProcedure compNewStem

/* copy comp end ******************************************************/