php/googleClientLib/envFiber.php
<?php
/* output handling for cli, html or csv
design with fiber and classes
*/
class PutEcho {
public function put($txt) {
echo $txt;
}
public function close() { }
} # end class PutEcho
class PutFile extends PutEcho {
public $out;
public static function make($f=':E') {
return ($f === ':E') ? new PutEcho() : new self($f);
}
public function __construct($f) {
$this->out = ($this->doClose = ! is_resource($f)) ? ($f === ':B' ? fOpen('php://memory', 'w+') : fOpen($f, 'w')) : $f;
}
public function put($txt) {
fwrite($this->out, $txt);
}
public function clear() { # clear content and return content (before clearing of course)
$sz = ftell($this->out);
rewind($this->out);
$c = $sz < 1 ? '' : fread($this->out, $sz);
ftruncate($this->out, 0);
rewind($this->out);
return $c;
}
public function close() {
if ($this->doClose)
fclose($this->out);
}
} # end class PutFile
(OutFiber::$fib = new Fiber('OutFiber::start'))->start();
class OutFiber {
/* handles the stack of the fiber
methods f* (with * the tag, allowing dynamic calls) run within the fiber, add missing end tags, begin tags etc.
and call the methods g* to generate the concrete text from subclasses
*/
const cTb = [
'Html' => ['fmt' => 'OutHtml', 'tbH' => 'OutHtmlTbH' ]
, 'Cli' => ['fmt' => 'OutCli', 'o' => ':B', 'tbH' => 'OutBufTbH' ]
]
, cFmt = ['Html' => ['fmt' => 'OutHtml', 'tb' => self::cTb['Html']]
, 'tbRetH' => ['ret' => 1, 'o' => ':B', 'fmt' => 'OutHtml', 'tbH' => 'OutBufTbH', 'tb' => self::cTb['Html']]
, 'Cli' => ['fmt' => 'OutCli', 'tb' => self::cTb['Cli']]
, 'tbRetC' => ['ret' => 1, ...self::cTb['Cli'], 'tb' => self::cTb['Cli']]
];
public static $fib;
public $o;
public $last = 0; # 0 = nl , 1 nl . offset, 2 = after data
public static function cnfCheck($cA) {
$t = $c = is_array($cA) ? $cA : (self::cFmt[$cA] ?? err("cFmt key $cA missing"));
isset(($c)['tb']) or err("cnfCheck missing tb in" ,$t);
do {
is_subclass_of($t['fmt'], __class__) or err("make bad fmt class $t[fmt] in", $c);
isset($t['tbH']) ? (is_a($t['tbH'], 'OutHtmlTbH', true) or err("make bad tbH class $t[tbH] in", $c))
: ($t === $c or err("cnfCheck missing tbH in", $c));
} while ($t = $t['tb'] ?? false );
return $c;
}
public static function make($cA, $p = ':E') {
/* make a new instance of OutFiber for configuration $cA
output specified by $cA['o'], if missing in $p
*/
$c = self::cnfCheck($cA);
$n = new $c['fmt'];
$n->cnf = $c;
$n->o = ($q = $c['o'] ?? $p) instanceof PutEcho ? $q : PutFile::make($q);
if (isset($c['tbH'])) {
is_a($c['tbH'], 'OutHtmlTbH', true) or err("make bad tbH class $t[tbH] in", $c);
$n->tbH = new $c['tbH'];
$n->tbH->wrk = $n;
}
return $n;
}
public static function start() {
/* this is the start function for the fiber
wait for the first resume, and then make a working instance of outfiber
*/
$n = Fiber::suspend();
if ($n[0] === 'cfg') {
$c = $n[1];
$n = Fiber::suspend();
} else {
$c = (defined('OutHtml') ? OutHtml : (php_sapi_name() !== 'cli')) ? 'Html' : 'Cli';
}
self::make($c)->fMain($n);
}
public static function title($ti) {
global $dbg;
$b = envAssert($dbg); # todo is wrong place!
$t = array_map('a2str', $ti);
$t[] = basename(envScript()) . ' ' . implode(", ", envArgs());
outH(array_shift($t));
outUL();
foreach ($t as $l)
out($l);
if ($dbg) {
outLi("phpversion=" . phpversion());
outLi($b);
}
outULEnd();
}
public $cnf;
#--- f* methods: run in the (resumed) fiber stack - named after the tag they are handling (e.g. fTb)
function fMain($n) {
$this->gMain($n[0] === 'H' ? $n[1] : basename(envScript()));
$n = $this->fWork($n);
if ($n[0] !== 'End')
err("fMain resume returned bad node", $n);
array_shift($n);
($e = array_shift($n)) and $this->gH('End ' . $e);
while ($e = array_shift($n))
is_file($e) ? $this->gHighlightFile($e) : $this->gO($e);
$this->gMainEnd();
}
function fWork($n) {
static $ch = ['O' => 1, 'NL' => 1, 'W' => 1, 'A' => 1, 'H' => 1, 'LL' => 1, 'Tb' => 1];
while ($n[0] !== 'End') {
if (isset($ch[$n[0]])) {
$n = $this->{"f$n[0]"}($n);
} elseif ($n[0] === 'Flush') {
$n = Fiber::suspend();
} else {
err("OutFiber->fBegin bad resume request", $n);
}
}
return $n;
}
public function fA($a) { # text without any separators, neither before nor after
assert($a[0] === 'A');
if ($a[1] !== '') {
$this->gA($a[1]);
$this->last = 2;
}
return Fiber::suspend();
}
public function fW($a) { # text with space before if necessary
assert($a[0] === 'W');
if ($a[1] !== '') {
$this->gA(substr(' ', $this->last < 2) . $a[1]);
$this->last = 2;
}
return Fiber::suspend();
}
public function fO($a) { # text with space before if necessary, and an nl after
assert($a[0] === 'O');
$this->gO(substr(' ', $this->last < 2 or $a[1] === '') . $a[1]);
$this->last = 0;
return Fiber::suspend();
}
public function fNL($a) { # text with an nl before
assert($a[0] === 'NL');
$this->gNL($a[1]);
$this->last = ($a[1] !== '') << 1;
return Fiber::suspend();
}
public function fH($a) {
assert($a[0] === 'H');
$this->gH($a[1]);
$this->last = 0;
return Fiber::suspend();
}
public function fLL($a) {
static $ch = ['O' => 1, 'NL' => 1, 'W' => 1, 'A' => 1, 'Tb' => 1, 'TCF' => 1, 'LL' => 1, 'Li' => 1];
assert($a[0] === 'LL');
$oLx = $this->lx ?? 'none';
$oLTy = $this->lTy ?? 'none';
$this->lTy = $a[1];
$this->gLL($a[1]);
$n = Fiber::suspend();
for ($this->lx=1; isset($ch[$n[0]]); $this->lx++) {
$n = $this->fLi($n);
}
$this->gLLEnd($a[1]);
$this->lx = $oLx;
$this->lTy = $oLTy;
if ($n[0] !== 'LLEnd')
return $n;
assert($a[1] === $n[1]);
return Fiber::suspend();
}
public function fLi($a) { # List entry
static $ch = ['O' => 1, 'NL' => 1, 'W' => 1, 'A' => 1, 'LL' => 1, 'Tb' => 1, 'TCF' => 1];
if ($a[0] === 'Li') {
$this->gLi($a[1]);
$this->last = 1 + ($a[1] !== '');
$n = Fiber::suspend();
} else {
$this->gLi('');
$this->last = 1;
$n = $a;
}
while (isset($ch[$n[0]])) {
$n = $this->{"f$n[0]"}($n);
}
$this->gLiEnd($a[1]);
$this->last = 0;
return $n;
}
public function fTb($a) { # table begin, $a[1] might contain a cnf key or array, return tbH data if 'ret' => 1
assert($a[0] === 'Tb');
$nc = isset($a[1]) ? self::cnfCheck($a[1]) : $this->cnf['tb'];
$nc['tb'] ??= $nc;
$nn = self::make($nc, $this->o);
$e = $nn->fTbInNew();
if (1 !== ($nc['ret'] ?? 0)) {
$this->gTbEnd($nn->tbH);
} elseif ($e[0] === 'TbEnd') {
return Fiber::suspend($nn->tbH);
} else {
$this->gH('warning could not return buffered Table begin');
foreach ($nn->tbH->tb as $r)
$this->gO('[! ' . implode(' ! ', $r) . ' !]');
$this->gO('warning could not return buffered Table end');
}
return $e[0] !== 'TbEnd' ? $e : Fiber::suspend();
}
public function fTbInNew() { # table begin in new instance
static $ch = ['O' => 1, 'NL' => 1, 'W' => 1, 'A' => 1, 'LL' => 1, 'Tb' => 1, 'TR' => 1, 'TD' => 1, 'TCF' => 2];
$this->tbCF= [];
$this->rx = -1;
$this->cx = -1;
$this->tbH->hTb();
$n = Fiber::suspend();
while (isset($ch[$n[0]])) {
if ($n[0] === 'TCF') {
$n = $this->fTCF($n);
} else {
$this->rx++;
$this->cx = -1;
$n = $this->fTR($n);
}
}
return $n;
}
public function fTCF($a) { # table row formats
assert($a[0] === 'TCF' and isset($this->tbCF));
$this->tbCF= $a[1];
$this->tbF = $a[2] ?? null;
return Fiber::suspend();
}
public function fTR($a) { # table row
static $ch = ['O' => 1, 'NL' => 1, 'W' => 1, 'A' => 1, 'LL' => 1, 'Tb' => 1, 'TD' => 1, 'TCF' => 2];
assert(isset($this->tbH));
$this->tbH->hTR();
$n = $a[0] === 'TR' ? Fiber::suspend() : $a;
while (isset($ch[$n[0]])) {
if ($n[0] === 'TCF') {
$n = $this->fTCF($n);
} else {
$this->cx++;
$n = $this->fTD($n);
}
}
$this->tbH->hTREnd();
return $n;
}
public function fTD($a) { # table data
static $ch = ['O' => 1, 'NL' => 1, 'W' => 1, 'A' => 1, 'LL' => 1, 'Tb' => 1, 'TCF' => 2];
assert(isset($this->tbH));
if ($a[0] === 'TD') {
$fmt = $a[1];
$tx = $a[2];
$n = Fiber::suspend();
} else {
$fmt = $tx = '';
$n = $a;
}
$fmt .= $this->tbCF[$this->cx] ?? end($this->tbCF) ?? '';
$fmt = (strpos($fmt, '!') === false ? '' : '!') . (strpos($fmt, 'r') === false ? 'l' : 'r');
$this->last = 0;
$this->tbH->hTD($tx, $fmt);
$this->last = ($tx !== '')<<1;
while (isset($ch[$n[0]])) {
$n = $this->{"f$n[0]"}($n);
}
$this->tbH->hTDEnd($fmt);
$this->last = 0;
return $n;
}
} # end class OutFiber
class OutHtml extends OutFiber {
public function gMain($ti) {
$this->o->put("<html>\n<head>"
. '<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=3.0, user-scalable=yes"/>'
. ($ti === '' ? '' : "<title>$ti</title>")
. "\n</head>\n<body style='font-family: monospace;' >\n");
}
public function gMainEnd() { $this->o->put("\n</body>\n</html>\n"); }
public function gHighlightFile($f) {$this->o->put(self::highlightNum($f, true)); }
public function gA($txt) { $this->o->put($txt); }
public function gO($txt) { $this->o->put("$txt\n<br>"); }
public function gNL($txt) { $this->o->put("\n<br>$txt"); }
public function gH($txt) { $this->o->put("\n<h2>$txt</h2>"); }
public function gLL($fmt) { $this->o->put("\n<$fmt>"); }
public function gLLEnd($fmt) { $this->o->put("\n</$fmt>"); }
public function gLi($txt) { $this->o->put("\n<li>$txt"); }
public function gLiEnd() { $this->o->put("</li>"); }
public function gTbEnd($h) { assert($h instanceOf OutHtmlTbH); $this->o->put("\n</table>"); }
static public function highlightNum($fi, $hdr=false) { # 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), [' ' => ' ']) . '</span> ';
$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 <br>***' . substr($s, 0, $cy + 6);
}
if ($hdr)
$r = '<h3>' . basename($fi) . '</h3>' . realpath($fi) . '<br><br>' . $r;
while (FALSE !== ($cy = strpos($s, '<br />', $cx=$cy+6))) {
$r .= $hn(++$ln) . substr($s, $cx, $cy + 6 - $cx);
}
return $r . substr($s, $cx);
}
} # end class OutHtml
class OutHtmlTbH { # table handlere for Html
public function hTb() { ($this->o = $this->wrk->o)->put("\n<table border=2>"); }
public function hTR() { $this->o->put("\n<tr>"); }
public function hTREnd() { $this->o->put("\n</tr>"); }
public function hTD($txt, $f) { $this->o->put("\n<" . ($f[0] === '!' ? 'th' : 'td') . ($f[-1] === 'r' ? " style='text-align: right;'" : "") . ">$txt"); }
public function hTDEnd($f) { $this->o->put($f[0] === '!' ? '</th>' : '</td>'); }
} # end class OutHtmlTbH
class OutCli extends OutFiber {
protected $lvl = 0;
protected $intend = '';
public function level($l) {
$this->lvl = $l >= 0 ? $l : $this->lvl + 10 + $l;
$this->intend = str_pad('', 4 * $this->lvl);
}
public function gMain($ti) { }
public function gMainEnd() { }
public function gHighlightFile($f) { }
public function gA($txt) { $this->o->put($this->last === 0 ? "{$this->intend}$txt" : "$txt"); }
public function gO($txt) { $this->o->put($this->last === 0 ? "{$this->intend}$txt\n" : "$txt\n"); }
public function gNL($txt) { $this->o->put("\n$this->intend$txt"); }
public function gH($txt) {$this->o->put(substr("\n", $this->last < 2) . "\n--- $txt ---\n"); }
public function gLL($fmt) {
$this->level(-9); // +1
}
public function gLLEnd() {
$this->level(-11); // -1
}
public function gLi($txt) {
$rp = max(1, 4-$this->lvl);
$this->o->put(($this->last === 0 ? '' : "\n") . substr($this->intend, 0, -2)
. ($this->lTy === 'ol' ? ($this->lx) . str_repeat('.', $rp) : str_repeat('*', $rp)) . " $txt");
}
public function gLiEnd() {
if ($this->last !== 0)
$this->o->put("\n");
}
public function gTbEnd($h) {
if ('' !== $f = $h->wrk->o->clear())
err("gTbEnd o not empty but $f");
$h->wrk->o->close();
$tb = $h->tb;
$fmt = $h->fmt;
# echo i2str(['tbGen table', $tb, 'fmt', $fmt]);
$cc = '+:*!-='; # data sep (first, next) header sep (first, next) sep sep (data, hdr)
$cLen = [];
$rLin = [];
foreach ($tb as $rx => $rw) {
foreach ($rw as $cx => $ce) {
$s = preg_split("/\R/", $ce);
while ((end($s) ?? 1) === '')
array_pop($s);
if (count($s) > ($rLin[$rx] ?? -1))
$rLin[$rx] = count($s);
$tb[$rx][$cx] = $s;
# $l = array_reduce($s, fn($r, $e) => ($r > $l = iconv_strlen($e)) ? $r : $l, 0);
$l = $cLen[$cx] ?? -1;
foreach ($s as $sx => $se)
$l = max($l, iconv_strlen($se) + (($rx === 0 and (($fmt[$rx][$sx] ?? ' ')[0] === '!'))<<1));
$cLen[$cx] = $l;
}
}
# echo i2str(['tbGen table' , $tb, 'cLen', $cLen, 'rLin', $rLin]);
$tf = $h->tbF ?? '';
$sL = strpos($tf, 'a') !== false;
$sep = function($d, $h, $ff) use($cLen) {
$o = '';
foreach ($cLen as $cx => $cl)
$o .= str_repeat(($ff[$cx][0] ?? '') === '!' ? $h : $d, $cl+3);
$this->gO($o . $o[-1]);
};
if ($this->last !== 0)
$this->gO('');
$sep($cc[4], $cc[5], []);
for ($rx=0; $rx<count($rLin); $rx++) {
for ($lx=0; $lx< $rLin[$rx]; $lx++) {
$tx = '';
foreach ($cLen as $cx => $cl) {
$f1 = $fmt[$rx][$cx] ?? ' ';
$fx = ($f1[0] === '!' ? 2 : 0) + ($lx === 0 ? 0 : 1);
$t1 = $tb[$rx][$cx][$lx] ?? '';
$p = str_repeat($p1 = ($f1[0] === '!' && $t1 !== '') ? $cc[2] : ' ', $cl - iconv_strlen($t1));
$tx .= $cc[$fx] . (trim($t1) === '' ? "$p1$p$p1" : ($f1[-1] === 'r' ? "$p $t1 " : " $t1 $p"));
}
$this->gO($tx . $cc[$fx]);
}
if ($sL)
$sep($cc[4], $cc[5], $fmt[$rx]);
}
if (! $sL)
$sep($cc[4], $cc[5], []);
}
} # end class OutCli
class OutBufTbH extends OutHtmlTbH {
public function hTb() { # destination and work OutFiber's
$this->tb = [];
$this->fmt = [];
}
public function hTR() {
$this->tb[] = [];
$this->fmt[] = [];
}
public function hTREnd() {
}
public function hTD($txt, $f) {
$this->fmt[$this->wrk->rx][$this->wrk->cx] = $f;
$this->wrk->o->put($txt);
}
public function hTDEnd($f) { # store td or th data
$this->tb[$this->wrk->rx][$this->wrk->cx] = $this->wrk->o->clear();
}
} # end class OutBufTbH
class OutCsvTbH extends OutBufTbH {
public function tbGen() {
$this->dst->gO( '///' . ((($this->fmt[0][0] ?? ' ')[0] === '!' and (end($this->fmt[0]) ?? ' ')[0] === '!') ? 'tbH ' : 'tb ')
. ($this->id ?? 'tableId'));
if (! $this->dst->o instanceof PutEcho) {
foreach ($this->tb as $r)
fputcsv($this->csvO->out, $r);
} else {
foreach ($this->tb as $r) {
fputcsv($this->wrk->o->out, $r);
$this->work->o->put($this->o->clear());
}
}
$this->dst->gO( '///tbEnd');
}
} # end class OutCsvTbH
function outBegin (...$a) { # first call, initialize everything, write title
OutFiber::title($a);
}
function outFlush() { # flush the fiber stack and continue ...
OutFiber::$fib->resume(['Flush']);
}
function outEnd (...$a) { # last call, print source ($t) and cleanup
OutFiber::$fib->resume(['End', ...(count($a) > 1 ? $a
: (('-' === ($t = $a[0] ?? envScript())) ? [] :[basename($t), 'args ' . implode(', ', envArgs()), $t]))]);
}
function outErr($msg) {
global $dbg;
if (OutFiber::$fib and OutFiber::$fib->isSuspended()) {
outNL("(*$msg*)");
outFlush();
outH($msg);
}
}
function highlightNum($fi) {
if (OutHtml)
OutFiber::$fib->resume(['O', OutHTML::highlightNum($fi)]);
}
function outA() { # text without any separators, neither before nor after
OutFiber::$fib->resume(['A', func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : '')]);
}
function outW() { # text with a separating space before if necessary
OutFiber::$fib->resume(['W', func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : '')]);
}
function out() { # text with a separating space before if necessary and nl after
OutFiber::$fib->resume(['O', func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : '')]);
}
function outNL() {# text with a nl before
OutFiber::$fib->resume(['NL', func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : '')]);
}
function outEC() { # data in format $ = $, ... and nl
OutFiber::$fib->resume(['O', func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2strEC(func_get_args()) : '')]);
}
function outWEC() { # data in format $ = $, ... and nl
OutFiber::$fib->resume(['W', func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2strEC(func_get_args()) : '')]);
}
function outACE() { # data in format $ = $, ... and nl
OutFiber::$fib->resume(['A', ', ' . (func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2strEC(func_get_args()) : ''))]);
}
function outH() { # header
OutFiber::$fib->resume(['H', func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : '')]);
}
function outOL() { # ordered=numbered list, optional with first item
OutFiber::$fib->resume(['LL', 'ol']);
if (func_num_args() > 0)
OutFiber::$fib->resume(['Li', func_num_args() === 1 ? a2str(func_get_arg(0)) : i2str(func_get_args())]);
}
function outOLEnd() { # end ordered list
if (func_num_args() > 0)
OutFiber::$fib->resume(['Li', func_num_args() === 1 ? a2str(func_get_arg(0)) : i2str(func_get_args())]);
OutFiber::$fib->resume(['LLEnd', 'ol']);
}
function outLi() { # listitem (in ol or ul)
OutFiber::$fib->resume(['Li', func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : '')]);
}
function outUL() { # undordered= bulleted list, optional with first item
OutFiber::$fib->resume(['LL', 'ul']);
if (func_num_args() > 0)
OutFiber::$fib->resume(['Li', func_num_args() === 1 ? a2str(func_get_arg(0)) : i2str(func_get_args())]);
}
function outULEnd() { # end undordered list
if (func_num_args() > 0)
OutFiber::$fib->resume(['Li', func_num_args() === 1 ? a2str(func_get_arg(0)) : i2str(func_get_args())]);
OutFiber::$fib->resume(['LLEnd', 'ul']);
}
function outTb() { # table begin, optional with first td
OutFiber::$fib->resume(['Tb']);
if (func_num_args() > 0)
OutFiber::$fib->resume(['TD', '', func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args())]);
}
function outTbCH($c, ...$hh) { # table begin, optional with first td
OutFiber::$fib->resume(['Tb', $c]);
foreach ($hh as $h)
OutFiber::$fib->resume(['TD', '!', a2str($h)]);
}
function outTbEnd() { # table end
if (func_num_args() > 0)
OutFiber::$fib->resume(['TD', '', func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args())]);
return OutFiber::$fib->resume(['TbEnd']);
}
function outTbEndCsv($f, $id=null) { # table end (must be buffered) and output to csv
$h = OutFiber::$fib->resume(['TbEnd']);
$o = is_string($f) ? PutFile::make($f) : $f;
if ($id !== null)
$o->put( '///' . ((($h->fmt[0][0] ?? ' ')[0] === '!' and (end($h->fmt[0]) ?? ' ')[0] === '!') ? 'tbH' : 'tb') . " $id\n");
if (! $o instanceof PutEcho) {
foreach ($this->tb as $r)
fputcsv($o->out, $r);
} else {
$b = PutFile::make(':B');
foreach ($h->tb as $r) {
fputcsv($b->out, $r);
$o->put($b->clear());
}
}
if ($id !== null)
$o->put( "///tbEnd $id\n");
if (is_string($f))
$o->close();
}
function outTCF(...$a) { # table column format, currently only l (left align default) and r (right align)
$f = is_array($a[0]) ? $a[0] : $a;
foreach ($f as $e)
if ($e !== 'l' and $e !== 'r' and $e !== '!' and $e !== '!l' and $e !== '!r' )
err("bad format $e in outTCF(", $f, ')');
OutFiber::$fib->resume(['TCF', $f, is_array($a[0]) ? $a[1] ?? null : null]) ;
}
function outTR() { # row begin, optional with first td
OutFiber::$fib->resume(['TR']);
if (func_num_args() > 0)
OutFiber::$fib->resume(['TD', '', func_num_args() == 1 ? a2str(func_get_arg(0)) : i2str(func_get_args())]);
}
function outTRD() { # row begin, with 0 to n datacells
OutFiber::$fib->resume(['TR']);
foreach (func_get_args() as $d)
OutFiber::$fib->resume(['TD', '', a2str($d)]);
}
function outTRH() { # row begin, with 0 to n headercells
OutFiber::$fib->resume(['TR']);
foreach (func_get_args() as $h)
OutFiber::$fib->resume(['TD', '!', a2str($h)]);
}
function outTD() { # td = dataCell begin, optional with first words
OutFiber::$fib->resume(['TD', '', func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : '')]);
}
function outTDD() { # tdd = 0 to n dataCells
foreach (func_get_args() as $d)
OutFiber::$fib->resume(['TD', '', a2str($d)]);
}
function outTH() { # th = headerCell begin, optional with first words
OutFiber::$fib->resume(['TD', '!', func_num_args() === 1 ? a2str(func_get_arg(0)) : (func_num_args() > 1 ? i2str(func_get_args()) : '')]);
}
function outTHH() { # 0 to n headerCell
foreach (func_get_args() as $h)
OutFiber::$fib->resume(['TD', '!', a2str($h)]);
}