php/envOut.php

<?php
/*  output handling for cli or html
    procedural design
*/

###----------- out: output formatting, for commandline or html -----------
if (! OutHtml) { # commandline formatting
    function outPush($last, $fun, $fmt = '') { # push for commandline formatting
        global $outStack;
        $tos = count($outStack)-1;
        $nn = substr("\n", (int) ($last == '+nl' | $last == '-h2' | $last == '+be' )); # nl needed because not at new line
        if ($fun == 'be') {
            return '';
        } elseif ($fun[0] == 'w') {
            if ($last[1] == 'w')  
                return substr(' ', $fun[1] === 'a');
            elseif ($last == '-ol' or $last == '-ul' or $last == '-tb') 
                return "\n" . ($outStack[$tos][0] == 'li' ? str_repeat(' ', $outStack[$tos][1] * 4 +2) : ''); 
        } elseif ($fun == 'nl') {
            return "\n" . ($outStack[$tos][0] == 'li' ? str_repeat(' ', $outStack[$tos][1] * 4 +2) : ''); 
        } elseif ($fun == 'h2') {
            return "$nn\n--- ";
        } elseif ($fun == 'ol' or $fun == 'ul') {
            $outStack[$tos][2] = 0;
            return $nn;
        } elseif ($fun == 'li') {
            return ($last == '-li' ? "\n" : '') . str_repeat(' ', $outStack[$tos-1][1] * 4) 
                . (($lt = $outStack[$tos-1][0]) == 'ol' ? ++$outStack[$tos-1][2] : '')
                . substr($lt == 'ul' ? '?*** ' : '?... ', min($outStack[$tos-1][1], 3));
        } elseif ($fun == 'tb') {
            return $nn;
        } elseif ($fun == 'tr') {
            $outStack[0]['tb']['tr'] = [];
        } elseif ($fun == 'td' or $fun == 'th') {
            outWork('#s'); # store cell data
            $outStack[0]['tb']['rcf'][$outStack[0]['tb']['rx']][$outStack[0]['tb']['cx']] = "$fun$fmt";
        } else {
            err("bad terminal outPush($fun)");
        }
        return '';
    }

    function outPop($last, $fun) { # pop for commandline
        global $outStack;

        if ($fun == 'be')
            return '';
        elseif ($fun == 'h2')
            return " ---\n";
        elseif ($fun == 'ol' or $fun == 'ul' or $fun == 'li') 
            return '';
        elseif ($fun == 'li')
            return "";
        elseif ($fun == 'tb') 
            return outGenTb($outStack[0]['tb']); # gen stored table
        elseif ($fun == 'tr')
            $outStack[0]['tb'][] = $outStack[0]['tb']['tr'];
        elseif ($fun == 'td' or $fun = 'th')
            $outStack[0]['tb']['tr'][] = outWork('#r');
        else
            err("bad terminal outPop($fun)");
        return '';
    }

    function outGenTb($t) {
        global $outStack;
        for($rL = count($t); $rL > 0 and !isset($t[$rL-1]); $rL--) {}
        $cM = 0;
        $w = [];
        $h = array_fill(0, $rL, 0);
        for ($i=0; $i < $rL; $i++) {
            $cM = max($cM, $c = count($t[$i]));
            for ($k=0; $k < $c; $k++) {
                $a = $t[$i][$k] = explode("\n", $t[$i][$k]);  
                $h[$i] = max($h[$i], count($a));
                $l = ($t['rcf'][$i][$k][1] == 'h' ? 2 : 0) + (isset($a[0]) ? strlen($a[0]) : 0);
                for ($x=1; $x<count($a); $x++)
                    $l = max($l, iconv_strlen($a[$x]));
                $w[$k] = isset($w[$k]) ? max($w[$k], $l+2) : $l + 2; 
            }
        }
        $hL = count($h);
        $wL = count($w);
        $r = '';
        for ($i=0; $i < $hL; $i++) {
            for ($j=0; $j < $h[$i]; $j++) {              
                $r .= "\n";
                $br = $j == 0 ? '+' : '|';
                for ($k=0; $k < $wL; $k++) {
                    if (isset($t['rcf'][$i][$k]))
                        $ff = $t['rcf'][$i][$k];
                    $v = ('' == $v = isset($t[$i][$k][$j]) ? $t[$i][$k][$j] : '') ? '' : " $v " ;                   
                    $p  = str_repeat(($ff[1] == 'h' and $j == 0) ? '*' : ' ', $w[$k] - iconv_strlen($v));
                    $r .= ($ff[1] == 'h' ? '*' : $br) . ($ff[2] == 'r' ? "$p$v" : "$v$p");
                }
                $r .= $br;
            }
        }
        $tt = str_repeat('-', array_sum($w) + count($w) + 1);
        return "$tt$r\n$tt";
    }

    function highlightNum($fi) { # highlight file with lineNumbers
    }
         # end terminal mode
} else { # generate html output
    function outPush($last, $fun, $fmt = '') {
        global $outStack;
        if ($fun == 'be')
            return "<html>\n<head>" 
                    . '<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=3.0, user-scalable=yes"/>'
                    . ($fmt == '' ? '' : "<title>$fmt</title>") 
                    . "\n</head>\n<body style='font-family: monospace;' >\n";
        elseif ($fun[0] == 'w') 
            return substr(' ', $last[1] !== 'w' or $fun[1] === 'a'); # space between words
        elseif ($fun == 'nl')
            return '<br>';
        elseif ($fun == 'ol' or $fun == 'ul') 
            return ($last == '+li'? "&nbsp;" : '') . "<$fun>";   # bug in firefox, shows <li><ol> shows on same line!
        elseif ($fun == 'td' or $fun == 'th')
            return "<$fun style='text-align: " . ($fmt == 'r' ? 'right' : 'left') . ";'>";
        elseif ($fun == 'li' or $fun == 'h2' or $fun == 'td' or $fun == 'th' or $fun == 'tr')
            return "<$fun>";
        elseif ($fun == 'tb')
            return "<table border=2>";
        else
            err("bad html outPush($last, $fun, $fmt)");
    }

    function outPop($last, $fun) {
        global $outStack;
        if ($fun == 'be') 
            return (($f = $outStack[0]['endFile']) ? "$f<br><br>" . highlightNum($f) : '') . "\n</body>\n</html>\n"; 
        elseif ($fun == 'ol' or $fun == 'ul' or $fun == 'li' or $fun == 'h2' or $fun == 'td' or $fun == 'th' or $fun == 'tr')
            return "</$fun>\n";
        elseif ($fun == 'tb')
            return "</table>";
        else
            err("bad outPop($fun)");
    }

    function highlightNum($fi) { # highlight file with lineNumbers
        $s = highlight_file($fi, true);
        $ln = 1;
        $hn = fn ($no) => '<span style="color: #ffffff; background-color: #a0e0a0;">' 
                . strtr(sprintf('%6u  ', $no), [' ' => '&nbsp;']) . '</span>&nbsp;&nbsp;';
        $cy = strpos($s, '<br />');
        if (false !== ($sx = strpos($s, '<span')) and false !== ($sy = strpos($s, '>', $sx)) and $sy < $cy) { 
                # lineNo 1 after first span and possibly \n which must remain after first span
            $cx = $sy + 1 + (substr($s, $sy, 2) === ">\n");
            $r = substr($s, 0, $cx) . $hn($ln) . substr($s, $cx, $cy + 6 -$cx);
            }
        else {
            $r = '*** code does not have a span berfore first &lt;br&gt;***' . substr($s, 0, $cy + 6);
            }
        
        while (FALSE !== ($cy = strpos($s, '<br />', $cx=$cy+6))) {
            $r .= $hn(++$ln) . substr($s, $cx, $cy + 6 - $cx);
            } 
        return $r . substr($s, $cx);
    }
} # end generate html output

function outWork() { /* handle stack and call output specific functions outPush and outPop
                        variable number of inputs
                            -ty*   space separated list of types to pop from stack
                            #ty    assert ty is on top of stack and pop it
                            ty     push ty and if it is wo, wa or h2 add data in next argument
                    missing pops or push's are generated
                    */ 
    global $outStack;
    static $last = '+00',
            $oo = '',
            $ooStack = [],
            $doEcho = true;
    $fna = func_num_args();
    for ($ax=0; $ax < $fna; $ax++) {
        $fun = func_get_arg($ax);
        $tos = count($outStack)-1;
        $toTy = $outStack[$tos][0]; # top of stack type
        if ($fun[0] == '-') {
            while (true) {
                if ($tos < 0)
                    err("stack popped empty tos=$tos");
                $ty = $outStack[$tos][0];
                if (strpos($fun, $ty) === false)
                    break;
                outWork("#$ty");
                if ($tos != count($outStack))
                    err("fun=$fun bad pop $tos ==>", count($outStack));
                $tos--;
            }
        } else if ($fun[0] == '#') {
            if (strlen($fun) == 3) {
                if ($toTy != substr($fun, 1))
                    err("fun=$fun but topOfStack " . a2str($outStack[$tos]));
                $oo .= outPop($last, $toTy);
                $last = "-$toTy";
                array_pop($outStack);
                if ($toTy == 'tb') 
                    $outStack[0]['tb'] = array_pop($outStack[0]['tbStack']); # null if empty
            } elseif ($fun == '#s') {
                if ($doEcho)
                    echo $oo;
                else
                    $ooStack[] = $oo;
                $oo = '';
                $doEcho = false;
            } elseif ($fun == '#r') {
                if ($doEcho)
                    err('#r but $doEcho');
                $r = $oo;
                if ( null === $oo = array_pop($ooStack)) {
                    $doEcho = true;
                    $oo = '';
                }
                return $r;    
            } else {
                err("bad fun $fun in outWork");
            }
        } else {
            if ($toTy == 'ol' or $toTy == 'ul') { # generate missing li
                $outStack[] = [$toTy = 'li', $outStack[$tos][1]];
                $oo .= outPush($last, 'li');
                $last = "+li";
            } 
            if ($toTy == 'tb') { # generate missing tr
                $outStack[] = [$toTy = 'tr'];
                $outStack[0]['tb']['rx'] ++;
                $outStack[0]['tb']['cx'] = -1;
                $oo .= outPush($last, 'tr');
                $last = "+tr";
            } 
            if ($toTy == 'tr' and $fun != 'tr') {  # generate missing td or th
                $cx = ++$outStack[0]['tb']['cx']; 
                $fx = count($outStack[0]['tb']['cf']);
                $f = $outStack[0]['tb']['cf'][$fx > $cx ? $cx : $fx-1];
                if ($fH = (strpos($f, '!') !== false)) {
                    $f = str_replace('!', '', $f);
                    if ($fun == 'td')
                        $fun = 'th';
                }
                $outStack[] = [$toTy =  ($fH or $fun == 'th') ? 'th' : 'td', 0];
                $oo .= outPush($last, $toTy, $f);
                $last = "+$toTy";
            } 
            if ($fun == 'li' or $fun == 'td' or $fun == 'th' or $fun == 'tr') {
                if ($fun != $toTy or "+$fun" != $last)
                    err("mismatch fun=$fun <> old=$toTy"); # should be created above !
            } else {
                if ($fun == $toTy or $fun == 'nl' or $fun[0] == 'w') { # no push on $outstack
                } elseif ($fun == 'ol' or $fun == 'ul') {
                    $outStack[] = [$fun, $outStack[count($outStack)-1][1]+1]; # increase list level
                } else {
                    $outStack[] = [$fun];
                    if ($fun == 'tb') {
                        if (isset($outStack[0]['tb']))
                            $outStack[0]['tbStack'][] = $outStack[0]['tb']; 
                        else
                            $outStack[0]['tbStack'] = [];
                        $outStack[0]['tb'] = ['rx' => -1, 'cf' => ['l']];
                    }
                }
                $oo .= outPush($last, $fun, ($fun === 'be' and ++$ax < $fna) ? func_get_arg($ax) : '');
                $last = "+$fun";
            }
            if ( $fun[0] == 'w' or $fun == 'h2' )  
                $oo .= func_get_arg(++$ax);
        }        
    }
    if ($doEcho) {
        echo "$oo";
        $oo = '';
    }
}

function outBegin () { # first call, initialize everything, write title
    global $outStack, $dbg;
    $outStack[] = ['be', 0, 'last' => '+00'];  
    $i = [];
    $t = [];
    if (func_num_args() > 0) {
        $t[] = a2str(func_get_arg(0));
        $i[] = "begin " . (func_num_args() == 1 ? $t[0] : i2str(func_get_args()));
    }
    $t[] = basename(envScript());
    $i[] = implode(", ", envArgs());
    outWork('be', $t[0]);
    outH($i[0]);
    $o = ini_set('zend.assertions', 1);
    $n = ini_get('zend.assertions');
    if ($dbg) {
        outUL();
        for ($k=1; $k < count($i); $k++)
            outLi($i[0]);
        outLi("phpversion=" . phpversion());
        outLi("zend.assertions=$n", $n == 1 ? " from $o" : "could not activate");
        outULEnd();
    }
}

function outEnd ($t = false) { # last call, print source ($t) and cleanup
    global $outStack;
    outH("end " . basename($t ? $t : envScript()) . ': ' . implode(', ', envArgs()));
    $outStack[0]['endFile'] = $t;
    outWork('#be');
}

function out() { # data and nl
    if (func_num_args() > 0)
        outWork('wo', func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()), 'nl');
    else
        outWork('nl');
}

function outNL() { #nl and data
     if (func_num_args() > 0)
        outWork('nl', 'wo', func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
    else
        outWork('nl');
}

function outEC() { # data in format $ = $, ... and nl
    if (func_num_args() > 0)
        outWork('wo', func_num_args() == 1 ? a2str(func_get_arg(0)) : i2strEC(func_get_args()), 'nl');
    else
        outWork('nl');
}

function outW() { # data (without any nl)
    outWork('wo', func_num_args() == 0 ? false 
        : ( func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args())));
}

function outA() { # data (without any nl)
    outWork('wa', func_num_args() == 0 ? false 
        : ( func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args(), '')));
}

function outWEC() { # data (without any nl) in format equal comma: $ = $, ...
    outWork('wo', func_num_args() == 0 ? false 
        : ( func_num_args() == 1 ? a2str(func_get_arg(0)) : i2strEC(func_get_args())));
}

function outACE() { # data (without any nl) in format comma equal: , $ = $, ...
    outWork('wa', ', ' . ( func_num_args() == 1 ? a2str(func_get_arg(0)) : i2strEC(func_get_args())));
}

function outH() { # header
    outWork('-li ol ul', 'h2', 
        func_num_args() <= 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()), '#h2');
}

function outOL() { # ordered=numbered list, optional with first item   
    outWork('ol');
    if (func_num_args() > 0)
        outLi(func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
}

function outLi() { # listitem (in ol or ul)
    outWork('-li','li');
    if (func_num_args() > 0)
        outWork('wo', func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
}

function outOLEnd() { # end ordered list      
    if (func_num_args() > 0)
        outLi(func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
    outWork('-li', '#ol');
}

function outUL() { # undordered= bulleted list, optional with first item
    outWork('ul');
    if (func_num_args() > 0)
        outLi(func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
}

function outULEnd() { # end undordered list       
    if (func_num_args() > 0)
        outLi(func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
    outWork('-li', '#ul');
}

function outTb() { # table begin, optional with first td       
    outWork('tb');
    if (func_num_args() > 0)
        outTD(func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
}

function outTCF() { # table column format, currently only l (left align default) and r (right align)
    global $outStack;
    $f = func_get_args();
    if ( 0 >= $c = count($f))
        $f =['l'];
    else
        for ($i=0; $i < $c; $i++)
            if (! (isset($f[$i]) and ($f[$i] == 'l' or $f[$i] == 'r' or $f[$i] == '!l' or $f[$i] == '!r' )))
                err('bad format in outTCF(', $f, ')'); 
    $outStack[0]['tb']['cf'] = $f;
}

function outTR() { # row begin, optional with first td       
    outWork('-li ol ul td th tr', 'tr');
    if (func_num_args() > 0)
        outTD( func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
}

function outTRD() { # row begin, with 0 to n datacells
    outTR();
    foreach (func_get_args() as $d)
        outTD($d);
}

function outTRH() { # row begin, with 0 to n headercells
    outTR();
    foreach (func_get_args() as $h)
        outTH($h);
}

function outTD() { # td = dataCell begin, optional with first words        
    outWork('-li ol ul td th', 'td');
    if (func_num_args() > 0) 
        outWork('wo',  func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
}

function outTDD() { # tdd = 0 to n dataCells
    foreach (func_get_args() as $d)
        outTD($d);
}

function outTH() { # th = headerCell begin, optional with first words
    outWork('-li ol ul td th', 'th');
    if (func_num_args() > 0) 
        outWork('wo',  func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
}

function outTHH() { # 0 to n headerCell
    foreach (func_get_args() as $h)
        outTH($h);
}

function outTbEnd() { # table end        
    if (func_num_args() > 0)
        outTd(func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args()));
    outWork('-li ol ul td th tr', '#tb');
}