#include <emsg: see ... '~$ sh aaa.sh.c -h'   (other opt:no/-m/-w/)>	/*
C='^[/][/*]SH_'     ;O=${0##*[/]};R=`dirname $0`;R=${R%/}/;R0=$R$O;R=$R${O%%.*}
O=${0##*.};Rs=$R.$O;Rm=$R.tmp.$O;Rh=$R.h;R=$Rs$Rh$Rm;Rp='printf %s\n ';Rc=:;O="
";[ "${R##*$R0*}" = '' ]&&$Rp"$0:NGsuffix"&&exit 1;R='sed -ne ';Cm=$R'"/[E]ND/!d
:l;n;p;bl"<$R0>$Rm;$Rp"$Rm"';RB=$($R"s/${C}OP//p"<$R0|(F=mw;while read -r a b;do
B=${a%:};F=`$Rp"$F"|$R"s#$B:*##1;p"`${a%_};$Rp"C$B=\$(cat<<'E'$O$b${O}E$O)";done
$Rp"R1=$F"));Rw=$R'"/$C$R/!d;:l;n;/${C}ED/q;p;bl"<$R0';Cw="(R=LS;$Rw;$Rw>&3;R=HD
$Rw;R=SC;$Rw>&3)"'>$Rh 3>$Rs;$Rp"$Rh $Rs"';Re=eval\ ;$Re"$RB";while getopts $R1\
 R;do case $R in \?)exit 1;;*)$Re"O$R=\$OPTARG";Rc=$Rc$O`$Re'$Rp"$C'$R\"`;;esac
done;[ "$Rc" = : ]&&Rc=$Cm;shift $((OPTIND-1));$Re"$C_$O$Rc";exit   #END GPL3+*/

//SH_LS
/* Copyright (C) 2020 Momi-g

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program. If not, see <http://www.gnu.org/licenses/>.
--*/
//SH_ED

//SH_SC
//SH_TSS
local u=require("*SH_bn*")
//SH_TSE


local ctxx={}
/*-*
@_name	tdup
@auther momi-g
@brief	duplicate table, from penlight lib 'deepcopy()'
@_synopsys	tb tdup(t1, nil/t2)	
@_eg
	t1={1,2,3};	t2={}
	tdup(t1, t2)	-- or  t2=tdup(t1)
	
@param t1 copy srctb. 
@param t2 dsttb.  
@return tb. same data with t2.
@_note cite from https://github.com/Tieske/Penlight
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, 2020-05-11
-*/

fn ctxx.tdup(t1, t2){
	if(t2==nil){ t2={} }
	if(type(t1) !="table" || type(t2) != "table"){
		ctxx.errstop("(ag1,ag2) must be (tb,tb/nil): "..ctxx.vinfo(t1,t2), 2)
	}
	t2 = ctxx.tdupsub(t1,{})
	return t2
}
fn ctxx.tdupsub(tb, loopck){
// copied from penlight lib.
// https://github.com/Tieske/Penlight
	if(type(tb)!="table"){return tb}
	if(loopck[tb]){return loopck[tb] }
	//loopckはアドレスstrをkeyにしてloopを管理
	local mt = getmetatable(tb)
 	local res = {}	//for use pointer. dmytbl.
	loopck[tb]=res
 	for(k,v in pairs(tb) ){
		k=ctxx.tdupsub(k, loopck)
		v=ctxx.tdupsub(v, loopck)
		res[k]=v
	}
	setmetatable(res,mt)
	return res
}
//SH_TSS
fn u.addtest.t_dup(){
	local lp
	local tb={ 1,2,3,{4,5,lp} }
	tb[4].lp=tb
	local rtn, emsg = u.tdup(tb)
	
	u.test_eq(tb[1]==rtn[1])
	u.test_eq(tb[2]==rtn[2])
	u.test_eq(tb[3]==rtn[3])
	u.test_eq(tb[4][1]==rtn[4][1])
	u.test_eq(tb[4][2]==rtn[4][2])
}
//SH_TSE


/*-*
@_name	eqv
@auther momi-g
@brief	compare values using recursive check, from penlight lib 'deepcompare()'
@_synopsys	bool eqv( obj1, obj2)
@_eg
	aa={1,2,3};	bb="hw"
	if( eqv(aa, bb) ) then print("same_data") end
@param obj1	compdata. accept any types, num/func/tb etc. 
@param obj2 same as obj1 
@return bool	true/false
@_note https://github.com/Tieske/Penlight
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, 2020-05-11
-*/
fn ctxx.eqv(t1,t2){ return ctxx.tcompsub(t1,t2, {}) }
fn ctxx.tcompsub(t1,t2, loopck){
	if(loopck[t1] && loopck[t1][t2] ){return true}
	local ty1 = type(t1)
	local ty2 = type(t2)
	if(ty1 != ty2){return false}
//non-table types can be directly compared
	if(ty1 != 'table'){ return t1 == t2 }
//as well as tables which have the metamethod __eq
	local mt = getmetatable(t1)
	for(k1 in pairs(t1) ){ if(t2[k1]==nil){return false} }
	for(k2 in pairs(t2) ){ if(t1[k2]==nil){return false} }
	loopck[t1] = loopck[t1] || {}
	loopck[t1][t2] = true
	for(k1,v1 in pairs(t1)){
		local v2 = t2[k1]
		if(! ctxx.tcompsub(v1,v2,loopck) ){return false}
	}
	return true
}

//SH_TSS
fn u.addtest.t_eqv(){
	local aa=123
	local bb="hw"
	local rc = u.eqv(aa, bb)
	u.test_eq(rc, false)
	u.test_eq(rc, false, "eqv_test")
}
//SH_TSE

/*-*
@_name	sopairs
@auther momi-g
@brief	rtn key-sorted val when use forloop
@_synopsys	k,v sopairs(tb)
@_eg
	tb={[10]=11, ["bob"]=20, ["alice"]=30, [4]=33 }
	for k,v in sopairs(tb) do
		print(v)	--> true,4,10,alice,bob,false  >> 9,33,11,30,20,99
	end
@param tb	tb consists of keytype num/str. sort as num<str if type is mixed.
@return k,v	sorted key/val
@_note
cite: http://lua-users.org/wiki/SortedIteration
license: http://lua-users.org/wiki/GuestBook... permissive more than MIT? 
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, 2020-11-20
-*/

local fn cmp_multitype(op1, op2){
	local type1, type2 = type(op1), type(op2)
	if(type1 != type2){ return type1 < type2 }
	elseif(type1 == "number" || type1 == "string"){ return op1 < op2 }
	elseif(type1 == "boolean"){ return op1 == true }
	else{ return tostring(op1) < tostring(op2) }
}
local fn __genOrderedIndex( t ) {
	local orderedIndex = {}
	for( key in pairs(t) ) { table.insert( orderedIndex, key ) }
	table.sort( orderedIndex, cmp_multitype )
	return orderedIndex
}
local fn orderedNext(t, state){
	local key = nil
	if(state == nil){
		t.__orderedIndex = __genOrderedIndex( t )
		key = t.__orderedIndex[1]
	}else{
		for(i = 1,table.getn(t.__orderedIndex) ) {
			if(t.__orderedIndex[i] == state){ key = t.__orderedIndex[i+1] }
		}
	}
	if(key){ return key, t[key] }
	t.__orderedIndex = nil
	return
}
fn ctxx.sopairs(t) {return orderedNext, t, nil }
//SH_TSS
fn u.addtest.t_sopairs(){
	local tb={[10]=11, ["bob"]=20, ["alice"]=30, [4]=33 }
	for(k,v in u.sopairs(tb)){
		print(v) // true,4,10,alice,bob,false  >> 33,11,30,20
	}
}
//SH_TSE

/*-*
@_name	tsize
@auther momi-g
@brief	count tb atms
@_synopsys	num tsize(tb/nil)
@_eg
	aa={1,2,3, msg="hw", {10,20}, {100,200} }
	local sz = tsize(aa)	-->	6
@param tb	tgt. use blanktb{} if nil.
@return num	count result
@_note -
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, 2020-05-11
-*/
fn ctxx.tsize(t) {
	if(type(t)!="table" && t!=nil){ return nil, ctxx.fperr(nil, "bad argtype: "..ctxx.vinfo(t))}
	t=t||{}
	local i = 0
	for(k in pairs(t)) { i=i+1 }
	return i
}

/*-*
@_name	loadstring
@auther momi-g
@brief	eval wrapper of lua5.1/loadstring() and lua5.3/load()
@_synopsys	rtns loadstring(str, pseudo_name/nil)
@_eg
	local buf="print('hello'); print('world')"
	local myfunc, emsg = loadstring(buf, "abc")
	myfunc()	--> disp hw
@param str	luacode
@param pseudo_name	funcname used in debug.info() etc.
@return rtns	5.1 loadstirng() or 5.3 load() return val.
@_note -
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, 2020-05-11
-*/
local loadstring=loadstring
fn ctxx.loadstring(cmd, name){
	if(loadstring==nil){ return load(cmd, name, "t", _G) }
	return loadstring(cmd, name)
}

/*-*
@_name	flwrite, flwrite_add, flread
@auther momi-g
@brief	read/write filename/filehandle data
@_synopsys	str, emsg flwrite(fh/flname, str/nil)
	str, emsg flwrite_add(fh/flname, str/nil)
	str, emsg flread(fh/flname)
@_eg
	local res, emsg = flwrite("./my.log", "hw")
	res, emsg = flwrite_add("./my.log", "g\0w")	--> res=gw, #res=3
	res, emsg = flread("./my.log")	--> res=hwg(\0)w	#res=5
	flwrite(io.stdout, "abc")	--> same as print("abc")

@param fh/flname	write/read tgt. use as filename if string
@param str/nil	write str. conv to blankstr "" if nil. 
@return str	write/read str. rtn nil, emsg if err.
@details flwrite(file) is always overwrite the file.
	run open() - read/write - flush() - close() if tgt is file
	run (none) - read/write - flush() - (none)	if tgt is fh
@_note -
@_conforming lua5.1+ , luajit2.0+
@version 1.0.3, 2021-03-19
-*/
fn ctxx.flwrite(tgt, str){
	local rc, emsg, cflg=nil, nil, nil
	if(type(tgt)=="string"){ cflg=1; tgt, emsg = io.open(tgt, "wb") }
	if(io.type(tgt)!="file" ){
		return nil, emsg||ctxx.fperr(nil, "invalid 1st arg file/fh: "..ctxx.vinfo(tgt), 2)
	}
	str=str||""
	if(type(str)!="string" ){
		return nil, ctxx.fperr(nil, "2nd arg allows only string/nil: "..ctxx.vinfo(str), 2)
	}
	rc, emsg = tgt.write(tgt, str)
	tgt.flush(tgt)
	if(emsg){ return nil, emsg }
	if(cflg){ tgt.close(tgt) }
	return str
}
fn ctxx.flwrite_add(tgt, str){
	local rc, emsg, cflg=nil, nil, nil
	if(type(tgt)=="string"){ tgt, emsg = io.open(tgt, "ab"); cflg=1 }
	if(io.type(tgt)!="file" ){
		return nil, emsg||ctxx.fperr(nil, "invalid 1st arg file/fh: "..ctxx.vinfo(tgt), 2)
	}
	str=str||""
	if(type(str)!="string" ){
		return nil, ctxx.fperr(nil, "2nd arg allows only string/nil: "..ctxx.vinfo(str), 2)
	}
	rc, emsg = tgt.write(tgt, str)
	tgt.flush(tgt)
	if(emsg){ return nil, emsg }
	if(cflg){ tgt.close(tgt) }
	return str
}

fn ctxx.flread(tgt){
	local str, emsg, cflg=nil, nil, nil
	if(type(tgt)=="string"){ cflg=1; tgt, emsg = io.open(tgt, "rb") }
	if(io.type(tgt)!="file"){
		return nil, emsg||ctxx.fperr(nil, "invalid 1st arg file/fh: "..ctxx.vinfo(tgt), 2)
	}
	str, emsg = tgt.read(tgt, "*a")
	if(emsg){ return nil, emsg }
	if(cflg){ tgt.close(tgt) }
	return str
}

//SH_TSS
fn u.addtest.t_flwrite(){
	local res, emsg = u.flwrite("./my.log", "hw")
	u.test_eq(res, "hw")
	res, emsg = u.flwrite_add("./my.log", "g\nw")
	u.test_eq(#res, 3)
	res, emsg = u.flread("./my.log")
	u.test_eq(#res, 5)
//	u.flwrite(io.stdout, "abc")
}
//SH_TSE

/*-*
@_name	sleep
@auther momi-g
@brief	second sleep function, busyloop wait
@_synopsys	(nortn) sleep(num)
@_eg	sleep(2)
@param num wait second time(int). cpu usage will be 100%. 
@return nortn
@_note 
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, 2020-04-09
-*/
fn ctxx.sleep(num){
	if( type(num) != "number" ){ ctxx.errstop("argtype isnt num: "..ctxx.vinfo(num) ) }
	local s=os.time()
	while(1){
		if( os.difftime( os.time(), s ) > num ){
			break
		}
	}
}

//SH_TSS
fn u.addtest.t_sleep(){
	//u.sleep(1)
	u.test_eq(1, 1)
}
//SH_TSE

/*-*
@_name	ismain
@auther momi-g
@brief	test nowfunc is run as stackroot or not, mimic of py3 'if __main__'
@_synopsys	bool ismain(num/nil)
@_eg
	if( ismain(1) ){ print("run as root call") }
	ismain()	--> same as ismain(1)
	local function myf() print(ismain(2)) end	--> callsrc func is main or not
@param num funclv, same as error(). nil is use as num 1. 
@return 1rtn. bool
@_conforming lua5.1+ , luajit2.0+
@version 1.0.4, 2021-01-31
-*/

// mainチャンクとstackのカウント数でチェック
fn ctxx.ismain(num){
	if( type(num) != "number" && num != nil ){
		ctxx.errstop("argtype isnt num/nil: "..ctxx.vinfo(num) )
	}
	num=num||1
	local pos=1
	while( debug.getinfo(pos)){pos=pos+1}
	local mpos=pos-1
	while( debug.getinfo(mpos) && debug.getinfo(mpos).what != "main"){mpos=mpos-1}
	local cnt=0
	while( debug.getinfo(num+1+cnt) ){cnt=cnt+1}
	return (pos-mpos==cnt)
}

//SH_TSS
fn u.addtest.t_ismain(){
	//test関数内部だし。
	u.test_eq( u.ismain(), false ) 
	u.test_eq( u.ismain(3), true ) 
	u.test_eq( u.ismain(4), false )
}
//SH_TSE

/*-*
@_name	ismwd
@auther momi-g
@brief	tests srccode is called as Mainfile + exist in pWD  
@_synopsys	bool, emsg ismwd(num/nil)	
@_eg
	-- aa.lua
		-- copy&paste func --
	if( ! ismwd()){ print("selfcode doesnt exists in PWD. chdir to src path") }
	local u = require("myutil")
	local mod = loadfile("./mymod.lua")
	
	local function aa_main()
		if( ! ismwd(2) ){ print("chdir to src path") }
		print("hw")
	end

	-- ~$ lua aa.lua	...suc. disp hw
	-- ~$ lua ./aa.lua	...suc
	-- ~$ lua ../aa.lua	...fail
	-- ~$ lua /work/aa.lua (!= ./aa.lua)	...fail
	-- ~$ lua /home/dir/aa.lua (==./aa.lua)	...fail
	
	-- c-lang call:	luaL_loadfile(L, "./aa.lua")	..suc
	-- c-lang call:	luaL_loadfile(L, "../aa.lua")	..fail

	-- call from lua: ...fail
	load("aa.lua")		--> inner call
	load("..direct srccode..")	--> not file

@param num funclv, same as error(). nil is use as num 1. 
@return 1/2rtn. true if suc. false + emsg if fail.
@details
 satisfy the follow if pass this func
  - this funcsrc is a file(*.lua, *.so etc) and pwd ./ is the file dir
  - this src is called as stack lv 1, same as main()
 this allows you to:
  - call in the same dir files with loadfile("./foo.lua"), require("foo")

 ..lua lacks chdir api. lua modload sys is complex, changed frequently and poor.
 lfs has dirapi(c-lang) but dependency grows.
 this func protects your code portability by restricting how to call your src.

@_conforming lua5.3, luajit2.0+
@version 1.0.2, 2020-11-08
-*/
//getinfo(0)はgetinfo, 1はismwd(), 2はa.lua, 3がvm, 4でゼロになるはず。
local fn ismwd(num){
	num=num||1
	assert( type(num) == "number")
	local info=debug.getinfo(2).source..":"..debug.getinfo(2,"l").currentline..":"..debug.getinfo(2).name.."(): "
	local sname = debug.getinfo(1+num).source
	if( !(debug.getinfo(3+num)==nil && debug.getinfo(2+num)) ) {
		return false, info.."err. "..sname.." must be loaded as root func"
	}
	if(string.sub(sname, 1, 1)!="@"){return false,"err: this code must be loaded as file: "..sname }
	sname = string.sub(sname, 2)
	local sep = string.match(package.path, ".%?")
	if(sep=="") { return false, "err: failed to get dirsep from path ENV (/ etc): "..sname }
	sep=string.sub(sep, 1,1)	// win\ or unix/
	local buf = string.gsub(sname, ".*%"..sep, "")	//del head ./, allow only a.lua ./a,lua
	if(buf!=sname && "."..sep..buf != sname){
		return false, "err: this file must be loaded as './foo' or 'foo'. run from srcdir: "..sname
	}
	return true
}

//SH_TSS
fn u.addtest.t_ismwd(){
	print(1)
//	u.test_neq( u.ismwd() ) 
//	u.test_neq( u.ismwd(1) )
//	u.test_eq( u.ismwd(3) )
}
//SH_TSE

/*-*
@_name	v2bool
@auther momi-g
@brief	exchange var to bool
@_synopsys	v2bool(obj)
@_eg	flg = v2bool("123")	--> true
@return 1 bool
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, 2020-04-07
-*/
fn ctxx.v2bool(obj){
	if(obj){return true}
	return false
}

/*-*
@_name	istypes
@auther momi-g
@brief	check object types.
@_synopsys	char/nil istypes(obj, fmt/nil)
@_eg
	res = istypes(23, "sN") --> res=nil. 23 is (n)um. "sN"==(s)tr || (N)il
	res = istypes("abc") --> conv to istypes("abc", "A"). res="s"
	res = istypes(10.1) --> res="n"
	res = istypes(10.1, "i") --> res=nil
@param obj target obj you want to ck
@param fmt allow type chars. (N)il,(b)ool,(n)um,(s)tr,(t)bl,(f)unc,(u)data,
(T)hread and (i)nt,(A)ll. 'A' is conv to 'NbnstfuT'. 'i' is not primary type.
@return char/nil	always returns 1char except for 2arg compmode+fail...(nil).
@details	rtn "_" if detect non-basic type in 1arg mode, istypes(ffi.new) etc.
@_note
 loop 1000	u.istypes(123, "A")	--> real	1.580 ms	...slow 50 time 
 loop 1000	types(123)	--> real	0.027 ms
 loop 1000	nilfunc()	--> real	0.050 ms
@_conforming lua5.1+, luajit2.0+
@version 1.0.4, 2020-07-13
-*/
fn ctxx.istypes(obj, str){
	local f1=0
	if(str==nil){str="NbnstfuT";f1=1}
	if(type(str)!="string"){ ctxx.errstop("2nd arg is not string: "..type(str), 2) }
	if( string.find(str, "A") ){str = "NbnstfuT"..str }
	if( string.match(str, "[^NbnstfuTAi]") ){
		ctxx.errstop("bad typechar [NbnstfuTAi]: "..str, 2)
	}
	local tbl={}
	local res=type(obj)
	local ires="_"
	if( res=="nil" ){ res="N" }
	elif( res=="boolean" ){ res="b" }
	elif( res=="number" ){
		res="n"
		local i,r = math.modf(obj)
		if(r==0){ires="i"}
	}
	elif( res=="string" ){ res="s" }
	elif( res=="table" ){	res="t" }
	elif( res=="function" ){ res="f" }
	elif( res=="userdata" ){ res="u" }
	elif( res=="thread" ){	res="T" }
	local buf = string.find(str, res)
	//救済。integer
	if(buf==nil){
		res=nil
		buf = string.find(str, ires)
		if(buf!=nil){res=ires}
	}
	if(f1==1&&buf==nil){res="_"}
	return res
}

//SH_TSS
fn u.addtest.t_istypes() {
	u.test_eq( u.istypes(123, "sN") , nil )
	u.test_eq( u.istypes(123, "Nn") , "n" )
	u.test_neq( u.istypes(print), "N" )
	
	print( u.istypes(123.4, "i") )
	
	u.laptime(0)
	for(i=1, 1000){ u.istypes(123, "A") }
	u.laptime()

	u.laptime(0)
	for(i=1, 1000){ type(123) }
	u.laptime()
}
//SH_TSE

/*-*
@_name	tb2va
@auther momi-g
@brief	conv tbseq to va_args, wrapper of unpack/table.unpack
@_synopsys	va tb2va(tb/nil)
@_eg
tbuf={10, "aa", nil, "cc", name="alice"}
print("atm is:", tb2va(tbuf) )	--> atm is: 10 aa, ignore not seq atms.

--~$ lua 1 "alice" abc
print("args: ", tb2va(_G) )		--> 1, alice, abc

@param tb	tgt. use blanktb{} if nil.
@return va	splitted seqtb atms. ignore not seqdata.
@details	-
@_note -
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, 2020-07-29
-*/

local unpack_wrap=unpack
fn ctxx.tb2va(tb) {
	if(tb==nil){ tb={} }
	if(type(tb)!="table"){ ctxx.errstop("1st arg allows only tb/nil: "..ctxx.vinfo(tb), 2) }
	if(unpack_wrap==nil){unpack_wrap=table.unpack}
	if(unpack_wrap==nil){ ctxx.errstop("5.1/5.3 unpack/table.unpack is nil. new lua version?") }
	return unpack_wrap(tb)
}
//SH_TSS
fn u.addtest.t_tb2va() {
	print( u.tb2va({"aaa"}) )

	a,b,c = u.tb2va( {"hw",11} )
	u.test_eq(a, "hw")
	u.test_eq(b, 11)
	//print(a,b,c)
}
//SH_TSE

/*-*
@_name	getlovars
@auther momi-g
@brief	get callsrcfunc localvars(nameselect), getlocal() wrapper.
@_synopsys	obj/tbl getlovars(num/nil, str/nil)
@_eg
	local a, b = 11, 22
	local function myf()	--stklv 1
		local b=99
		res = getlovars(1, "a")	--> nil
		res = getlovars(2, "a")	--> 11
		res = getlovars(1)	--> {b=99}
	end
	myf()	--run
	local c=44	--not counted

@param num stack level. 1:self 2:callsrc 3-:more. rtn nil if lv is erange.
@param str/nil target name. rtn alldata tbl if nil
@return 1rtn.  rtn obj/tbl if 2nd param is exist/nil	rtn nil,emsg if err.
@_note external local vars(called 'upvalue' in lua) isnt local vars.  
@_conforming lua5.1+ , luajit2.0+
@version 1.0.2, y2020m04d07
-*/

fn ctxx.getlovars(lv, key){
	local rtn={}
	local emsg=nil
	local i=1

	if(type(lv)=="nil"){lv=1}
	if(type(lv)!="number"|| ctxx.istypes(key, "sN")==nil){
		emsg = ctxx.sprintf("%s(): bad args. 1,num/nil 2,num/nil/str: %s %s"
		,ctxx.S_FUNC(1), type(lv), type(key) )
		error(emsg, 2)
	}
	local buf = debug.getinfo(1+lv)
	if(lv<0||buf==nil){
		emsg=ctxx.fperr(nil, "1st arg, stack lv is out of stack depth", 2)
		return nil, emsg
	}
	while(true){
		local k, v = debug.getlocal(1+lv, i)
		if(k==nil){ break }
		rtn[k] = v
		i=i+1
	}
	if(key!=nil){rtn=rtn[key] }
	return rtn
}

/*-*
@_name	setlovars
@auther momi-g
@brief	set callsrcfunc localvars(nameselect), setlocal() wrapper.
@_synopsys
str setlovars(num, str, obj)
@_eg
	local a, b = 123, 999
	local res = setlovars(1, "a", 10)	--> res = "a"
	print(a) --> 10
@param num stack level. 1:self 2:callsrc 3-:more. rtn nil if lv is erange.
@param str target name.  
@param obj input data.  
@return 1 rtn.  suc:not nil/false	fail:nil, emag
@_conforming lua5.1+ , luajit2.0+
@version 1.0.2, y2020m04d07
-*/
fn ctxx.setlovars(lv, key, data){
	local rtn = debug.getinfo(1+lv)
	local i=1
	if(lv<0||rtn==nil){
		emsg=ctxx.fperr(nil, "1st arg, stack lv is out of stack depth", 2)
		return nil, emsg
	}
	while(1){
		local k, v = debug.getlocal(1+lv, i)
		if(k==key||k==nil){ break }
		i=i+1
	}
	if(k==nil){
		emsg=ctxx.fperr(nil, "2nd arg, no such local name", 2)
		return nil, emsg
	}
	rtn = debug.setlocal(1+lv, i, data)
	return rtn
}

//SH_TSS
fn u.addtest.t_getlovars() {
	print(  u.getlovars(1, "a") )
	u.test_eq( u.getlovars(1, "a"), nil )
	local a="hhh"
	u.test_eq( u.getlovars(1, "a"), "hhh" )
	u.dbg( nil, u.getlovars(1) )
	print( u.setlovars(-1, "a", 123) )
	print(a, 11)
}
//SH_TSE

/*-*
@_name	getscvars
@auther momi-g
@brief	get scopevars(local + external local + global) using stklv+varname.
@_synopsys	tbl/obj getscvars(num/nil, str/nil)
@_eg
	local a=1
	local function myf()	--stklv 1
		local b=99
		rtn = getscvars(1, "b")	--> 99
		rtn = getscvars(2, "b")	--> 13
		rtn = getscvars(1, "c")	--> nil
		rtn = getscvars(1)	--> {a=1,b=99,myf=func, +globals} ...scope vars tbl.
	end
	local b=13
	myf()	--run. stklv 2
	local c=34	--out of count
@param num stack lv. 1:self 2:callsrc 3-:more. rtn nil,emsg if lv is erange.
@param str target keyname. rtns datatbl if nil.  
@return 1rtn. target value or all value tbl. rtn nil,emsg if err.
@_conforming lua5.1+ , luajit2.0+
@version 1.0.1, y2020m04d07
-*/
fn ctxx.getscvars(lv, key){
	if(type(lv)!="number" ){ ctxx.errstop("1st arg must be num: "..type(slv), 2) }
	if(key!=nil && type(key)!="string" && type(key)!="number" ){
		ctxx.errstop("2nd arg must be mun/str/nil: "..type(key), 2)
	}
	
	local rtn={}
	local emsg=nil
	
	lv=lv+1
	local buf = debug.getinfo(lv)
	if(lv<=0||buf==nil){
		emsg=ctxx.fperr(nil, "1st arg, stklv is out of stkdepth", 2)
		return nil, emsg
	}
	local i=1
	//local
	while(true){
		local k, v = debug.getlocal(lv, i)
		if(k==nil){ break }
		rtn[k] = v
		i=i+1
	}
	//scope
	local buf = debug.getinfo(lv).func
	i=1
	while(true){
		local k, v = debug.getupvalue(buf, i)
		if(k==nil){ break }
		if(rtn[k] == nil){ rtn[k] = v }
		i=i+1
	}
	//global
	i=1
	for(k,v in pairs(_G) ){ if(rtn[k] == nil){ rtn[k] = v } }
	if(key!=nil){rtn=rtn[key] }
	if(rtn==nil){
		emsg=ctxx.fperr(nil, "err. key isnt used: "..key , 2)
		return nil, emsg
	}
	return rtn
}

/*-*
@_name	setscvars
@auther momi-g
@brief	set scopevars(local + external local + global) using stklv+varname.
@_synopsys	suc setscvars(num/nil, str, obj)
@_eg
	local a=1
	local function myf()	--stklv 1
		local b=10
		setscvars(1, "b", 100)
		setscvars(1, "z", 200)
		print(b, z)	--> 100, 200
		setscvars(1, "a", "hw" )	--> 'a' is external local var, a=1 >> a="hw".
	end
	local b=13
	myf()	--run. stklv 2
	print(a, b, z)	-->	hw, 13, nil
@param num stack lv. 1:self 2:callsrc 3-:more. rtn nil,emsg if lv is erange.
@param str target keyname. rtns datatbl if nil.  
@param obj the value you want to set.  
@return 1rtn. not nil/false value. rtn nil,emsg if err.
@_conforming lua5.1+ , luajit2.0+
@version 1.0.1, y2020m04d07
-*/

fn ctxx.setscvars(lv, key, data){
	if(type(lv)!="number" ){ ctxx.errstop("1st arg must be num: "..type(slv), 2) }
	if(key!=nil && type(key)!="string" && type(key)!="number" ){
		ctxx.errstop("2nd arg must be mun/str/nil: "..type(key), 2)
	}
	lv=lv+1
	local rtn = debug.getinfo(lv)
	local i=1
	if(lv<=0||rtn==nil){
		emsg=ctxx.fperr(nil, "1st arg, stklv is out of stkdepth", 2)
		return nil, emsg
	}
	//local
	while(1) {
		local k, v = debug.getlocal(lv, i)
		if(k==nil){ break }
		if(k==key){
			local rtn = debug.setlocal(lv, i, data)
			return rtn
		}
		i=i+1
	}
	//scope
	i=1
	local buf = debug.getinfo(lv).func
	while(1) {
		local k, v = debug.getupvalue(buf, i)
		if(k==nil){ break }
		if(k==key){
			local rtn = debug.setupvalue(buf, i, data)
			return rtn
		}
		i=i+1
	}
	//global
	for(k,v in pairs(_G) ){
		if(k == key){
			_G[k] = data
			return key
		}
	}
	//nohit
	emsg=ctxx.fperr(nil, "err. key isnt used: "..key , 2)
	return nil, emsg
}
//SH_TSS
fn u.addtest.t_getscvars() {
	local a=7
	local b=16
	local buf, emsg = u.getscvars(1, "b")
	u.test_eq(buf, 16)

	buf, emsg = u.getscvars(1, "a")
	u.test_eq(buf, 7)
	
	buf = u.getscvars(2, "k")
	u.test_eq(buf, "t_getscvars", "lutest forloop")
	buf = u.setscvars(1, "a", "unko", "settest")
	u.test_eq(a, "unko")
	buf, emsg = u.getscvars(-1, "lv")
	u.test_eq(buf, nil)
	print("errmsgck... "..emsg)
//os.exit(1)
}
//SH_TSE

/*-*
@_name	S_FILE, S_LINE, S_FUNC
@auther momi-g
@brief	mimic C macro __FILE__, __LINE__, __func__, all rtns (S)tr (not num).
@_synopsys
	str S_FILE()	--> conv to S_FILE(1)
	str S_FILE(num)
@_eg
 1:	function f_aa() f_bb();	end
 2:	function f_bb()
 3:		print( S_FILE(1),S_LINE(), S_FUNC() )
 4:		print( S_FILE(2), S_LINE(2), S_FUNC(2) )
 5:	end
 6:	f_bb()	--> mylua.lua, 3, f_bb //  mylua.lua, 6/nil, [C?]/nil 	
 7:	f_aa()	--> mylua.lua, 3, f_bb //  mylua.lua, 1(f_bb call pos), f_aa 
@param num fcall depth. same as debug.getinfo(num). 1:self 2:call src 3:...
@return 1 str. res will get strange string if name resolve failed (closure etc).
@details this func uses debug.getinfo(). recommend you to use small lv, 1 or 2.
@_conforming lua5.1+ , luajit2.0+
@version 1.0.3, y2020m07d16
-*/

fn ctxx.S_FILE(i){
	local rmsg=""
	if(type(i)=="nil"){i=1}
	if(type(i)=="number"){
		rmsg = debug.getinfo(1+i,"S")
		if(rmsg==nil){rmsg="nil"}
		elif(1){rmsg = rmsg.short_src}
	}
	elif(true){ error(ctxx.S_FUNC(1).."(): 1st arg is not num/nil: "..type(i), 1) }
	if(rmsg==nil){ rmsg = "nil" }
	return rmsg
}
fn ctxx.S_LINE(i){
	local rmsg=""
	if(type(i)=="nil"){i=1}
	if(type(i)=="number"){
		rmsg = debug.getinfo(1+i,"l")
		if(rmsg==nil){rmsg="nil"}
		elif(1){rmsg = rmsg.currentline}
	}
	elif(true){ error(ctxx.S_FUNC(1).."(): 1st arg is not num/nil: "..type(i), 1) }
	if(rmsg==nil){ rmsg = "nil" }
	return rmsg
}
fn ctxx.S_FUNC(i){
	local rmsg=""
//if(1){return "now_debugging..." }
	if(type(i)=="nil"){i=1}
	if(type(i)=="number"){
		rmsg = debug.getinfo(1+i,"n")
		if(rmsg==nil){ rmsg = "nil" }
		elif(1){rmsg = rmsg.name}
	}
	elif(true){ error(ctxx.S_FUNC(1).."(): 1st arg is not num/nil: "..type(i), 1) }
	return rmsg
}
//SH_TSS
fn u.addtest.t_S() {
//	for(i=1, 5){
//		print( u.S_FILE(i), u.S_LINE(i), u.S_FUNC(i) )
//	}
	u.test_neq( u.S_FILE(1) , "nil" )
	u.test_neq( u.S_LINE(1) , "nil" )
	u.test_neq( u.S_FUNC(1) , "nil" )

	u.test_eq( u.S_FILE(10) , "nil" )
	u.test_eq( u.S_LINE(10) , "nil" )
	u.test_eq( u.S_FUNC(10) , "nil" )
}
//SH_TSE

/*-*
@_name	tblconv, tbconv
@auther momi-g
@brief	tbl filter, grep key+value and sort. 
@_synopsys	tbl tblconv(tbl/nil, str)
@_eg
	tbl = {[-3]=9, "aa", 10, [7]=20, a=1, b="hw", c=true}
	res= tblconv(tbl, "ind:n")	--> {[-3]=9, nil, 10, [7]=20}, "aa" is not n(um)
	res= tblconv(tbl, "key:sn")	--> {a=1, b="hw"}, s(tr) or n(um), "c" is bool.
	res= tblconv(tbl, "seq:n")	--> {nil, 10}, pickup 0<ind keys until nil.
	res= tblconv(tbl, "indn:n")	--> {[-1]9, 10, 20}, ind + n(ormalize)
	res= tblconv(tbl, "seqn:n")	--> {10}, seq + n(ormalize)
	res= tblconv(tbl, "swap")	--> {aa=1,[10]2,[20]7,[1]a,hw="b",[9]-3}, ig "c"
	
	args= {[-2]="lua",[-1]="-la",[0]="b.lua","t1","t2"}	--~$ lua -la b.lua t1 t2
	res= tblconv(args, "seqn:s") --> {"t1", "t2"}
@param tbl(must)	target tbl. nil is conv to {}, blanktbl.
@param str(must)	filter cmd. out_tbltype:src_datatypes. dtype allows blank. 
	tbltype: ind,indn,seq,seqn,key,swap. ind is numkey, key is strkey.
	dtypes: (N)il,(b)ool,(n)um,(s)tr,(t)bl,(f)unc,(u)data,(T)hread,(A)ll or blank(All)
@return 1rtn. converted tbl. rtn nil,emsg if err. 
@details "swap" ignores dtypes except "ns". "ind:Nsn" greps arr[num]=nil||str||num.
	"key:NbnstfuT" is equals to "key:A" / "key:" / "key".
@_conforming lua5.1+, luajit2.0+
@version 1.1.3, y2020m04d05
-*/
fn ctxx.tblconv(tbl, cmd){
	local rtn={}
	local buf=nil
	local cnt=1
	local emsg=""
	
	if(tbl==nil){tbl={} }
	if( type(tbl)!="table" || type(cmd)!="string" ){
		ctxx.errstop("invaild args. f(tbl/nil, str): "..type(tbl)..", "..type(cmd), 2)
	}
	local b1, b2 = string.match (cmd, "([a-z]+):?([a-zA-Z]*)")
	if(b1==nil)	{b1=""}
	if(b2==nil||b2==""){ b2="A" }
	buf={ ["ind"]=1,["indn"]=1,["seq"]=1,["seqn"]=1,["key"]=1,["swap"]=1 }
	if(buf[b1] == nil || string.match(b2, "[^NbnstfuTA]") ){
		ctxx.errstop( "err. 2nd argfmt is invalid: "..cmd, 2 )
	}
	if(b1 == "seq" ){
		for(k,v in ipairs(tbl) ){ if( ctxx.istypes(v, b2) ){rtn[k]=v } }
		goto lbl_RTN
	}
	if(b1 == "seqn" ){
		for(k,v in ipairs(tbl) ){if( ctxx.istypes(v, b2) ){ rtn[1+#rtn]=v } }
		goto lbl_RTN
	}
	if(b1 == "ind"){
		for(k,v in pairs(tbl) ){
			if(type(k)=="number" && ctxx.istypes(v, b2) ){ rtn[k]=v }
		}
		goto lbl_RTN
	}
	if(b1 == "indn"){
		//ソートと詰めなおしを兼ねる。1,3,4,5みたいな飛び飛びインデックスを1,2,3,4へ
		//詰め込んで行ってvalueでソートをかければいけるか
		local kbuf={}
		for(k,v in pairs(tbl) ){
			if(type(k)=="number" && ctxx.istypes(v, b2) ){	kbuf[1+#kbuf] = k }
		}
		table.sort(kbuf)	//キーは-1とか1の数値なので標準ソートでいける。
		local pbuf={}
		local nbuf={}
		//kbufのvalueはtblのキーなので、並び替えではこうなる。
		for(k,v in ipairs(kbuf) ){
			if(0<v) { pbuf[1+#pbuf]=tbl[v] }
			elif(v<0){ nbuf[1+#nbuf]=tbl[v] }
		}
		//合体
		local buf= -#nbuf
		for(k,v in ipairs(nbuf) ){
			pbuf[buf]=v
			buf=buf+1
		}
		//[0]は無視してたので追加
		pbuf[0]=tbl[0]
		rtn=pbuf
		goto lbl_RTN
	}
	if(b1 == "key"){
		for(k,v in pairs(tbl) ){
			if(type(k)=="string" && ctxx.istypes(v, b2) ){ rtn[k]=v }
		}
		goto lbl_RTN
	}
	if(b1 == "swap"){
		for(k,v in pairs(tbl) ){
			if( ctxx.istypes(v, "sn") && ctxx.istypes(v, b2) ){	rtn[v]=k }
		}
		goto lbl_RTN
	}
	::lbl_RTN::
	return rtn
}
// add rename
ctxx.tbconv = ctxx.tblconv

//SH_TSS
fn u.addtest.t_tblconv(){
	local t={11,22,aa=123, "33"}
	local rtn = u.tblconv(t, "seqn:s")
	u.test_eq( u.eqv(rtn, {"33"} ) )
//	u.dbg(rtn)
//	u.dbg(  u.tblconv(nil, "ind") )
	
	rtn = u.tbconv(nil, "ind")
	u.test_eq( #rtn, 0 )
	u.test_eq( type(rtn), "table" )
//	
	t={[3]=11, [7]=22, aa=123, [-4]="33"}
	rtn = u.tbconv(t, "indn:A")
	u.test_eq( u.eqv(rtn, {[-1]="33", 11,22}) )
//	u.dbg(rtn)
}
//SH_TSE

/*-*
@_name	vinfo
@auther momi-g
@brief	make value infomsg 
@_synopsys	str vinfo(obj...)
@_eg
	a=12; b="hw"
	res = vinfo(a,b)	--> res = "12:n, hw:s"
@param obj...	target obj.
@return 1str. shortmsg about input arg value/type. this func always succeeds.
 types is : (N)il,(b)ool,(n)um,(s)tr,(t)bl,(f)unc,(u)data,(T)hread
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, y2020m04d03
-*/
fn ctxx.vinfo(...) {
	local rtn = ""
	local i=1
	local max=select("#", ...)
	while(i<=max){
		local v=select(i, ...)
		rtn=rtn.. ", "..tostring(v)..":"..ctxx.istypes(v)
		i=i+1
	}
	return string.sub(rtn, 3)
}
//SH_TSS
fn u.addtest.t_vinfo() {
	a=1
	b="unko"
	c=print
	buf = u.vinfo(a,b,nil, c)
	u.test_neq(buf, nil)
	print("disp_ck...: "..buf)
}
//SH_TSE

/*-*
@_name	tbdump
@auther momi-g
@brief	dump tbdata with reuse/eval format 
@_synopsys	str tbdump(tb)
@_eg
	tb={1, {b=12} }
	str = tbdump(tb)
	print(str)	--> {1, {["b"]=12} }
@param tb	target. key/val type allows only num,str,bool / num,str,bool,tb
@return str. rtn nil,emsg if err.
@note raise stackoverflow if tb nest is too deep. ok:-50  err:100-
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, 2020-11-25
-*/
fn ctxx.tbdump(tb){
	if(type(tb)!="table"){ return nil, ctxx.fperr(nil, "1st arg is not tb: "..ctxx.vinfo(tb), 2) }
	local memo={}
	local indent=""
	return ctxx.tbdumpsub(tb, indent, memo)
}
fn ctxx.tbdumpsub(tb, indent, memo) {
	memo[tb]=1
	local str="{\n"
	for(k,v in pairs(tb) ){
		if(type(k)=="number"){ k=tostring(k) }
		elif(type(k)=="string"){ k=string.format('%q',k) }
		elif(type(k)=="bool"){ k=tostring(k) }
		elif(1){ return nil, ctxx.fperr(nil, "bad key/val type, nsb/nsbt: "..ctxx.vinfo(k,v), 2) }
		k="["..k.."]"
		if(type(v)=="number"){ v=tostring(v) }
		elif(type(v)=="string"){ v=string.format('%q',v) }
		elif(type(v)=="bool"){ v=tostring(v) }
		elif(type(v)=="table"){
			if(memo[v]){ return nil, ctxx.fperr(nil, "detect tb ptrloop: "..ctxx.vinfo(k,v), 2) }
			local emsg
			v, emsg=ctxx.tbdumpsub(v, indent.."\t", memo)
			if(emsg){return nil, emsg}
		}
		elif(1){ return nil, ctxx.fperr(nil, "bad key/val type, nsb/nsbt: "..ctxx.vinfo(k,v), 2) }
		str=str..indent..k.."="..v..",\n"
	}
	memo[tb]=nil
	str=str..indent.."}"
	return str
}

//SH_TSS
fn u.addtest.t_tbdump() {
	local tt={aa=11,bb=12,cc=44}
	local t={a=11,b=22, c=nil, tt}
//	t.tloop=t
	local buf = u.tbdump(t)
	print(buf)
}
//SH_TSE


/*-*
@_name	tinfo,	ftinfo
@auther momi-g
@brief	make tblinfo string 
@_synopsys	str tinfo(tbl/nil)	/	str ftinfo(io, tbl/nil)
@_eg
	res = tinfo( {11,22, {a=1,33} })	--> stderr + res holds infostr.
	res = ftinfo( io.stderr, {1,2,3} )	-->	output + res
@param tbl/nil	target tbl.
@param io	io.stderr, io.stdout etc. noout if nil.
@return 1str rtn "some_msg" str if param is nil
@_conforming lua5.1+ , luajit2.0+
@version 1.1.6, 2020-12-12
-*/
local
fn tinfosub(tb, tbname, ofs, mtbl) {
	local tadd = string.match( tostring(tb), "0[0-9a-fA-FxX]+")
	mtbl[tadd]=1
	if(type(tbname)=="string"){ tbname='"'..tbname..'"' }
	tbname=tostring(tbname)
	
	local str=ofs.."{ "..tbname.." :"..tadd.."\n"
	for(k,v in pairs(tb) ){
		if(type(v) == "table"){
			tadd = string.match( tostring(v), "0[0-9a-fA-FxX]+")
			//loop系発見
			if(mtbl[tadd]){
				str = str..ofs.."\t{ "..tostring(k).." "..tostring(tadd).." (sametb) }\n"
			}
			else{
				str=str..tinfosub(v, k, ofs.."\t", mtbl)
			}
		}
		elif(1){
			if(type(k)!="number"){k='"'..tostring(k)..'"'}
			local buf=ctxx.istypes(v)
			str=str..ofs..tostring(k).." = "..tostring(v)..":"..buf..",\n"
		}
	}
	str=string.gsub(str,",\n$","\n")
	str=str..ofs.."}\n"
	return str
}
fn ctxx.tinfo(tb) { return ctxx.ftinfo(io.stderr, tb) }
fn ctxx.ftinfo(fh, tb) {
	if(type(tb)!="table"){return nil, ctxx.fperr(nil, "2nd arg must be table: "..ctxx.vinfo(tb), 2) }
	local str = tinfosub(tb, "", "", {} )
	ctxx.fprintf(fh, "%s\n", str )
	return str
}
ctxx.tbinfo=ctxx.tinfo
ctxx.ftbinfo=ctxx.ftinfo

//SH_TSS
fn u.addtest.t_tinfo() {
	tt={aa=11,bb=12,cc=44}
	t={a=11,b=22, c=nil, tt}
	t.tloop=t

	local rtn = u.tinfo(t)
	rtn = u.ftinfo(nil, t)
	u.test_neq(rtn, nil)
//	u.dbg(nil, t)
}
//SH_TSE

/*-*
@_name	dbg, fdbg
@auther momi-g
@brief	printf debug helper. 
@_synopsys	str dbg(...)
@_eg
	a=123
	dbg( a, {22,33}, func ) --> output data to stderr. tbl is expanded.
	msg = fdbg(io.stdout, a, {22,33}, func ) --> change fh. noout if nil
@param va_args	all type obj. 
@return 1 str. copy of disp msg
@_conforming lua5.1+ , luajit2.0+
@version 1.0.1, y2020m04d03
-*/

fn ctxx.fdbg(fh, ...) {
	if (! io.type(fh) &&fh!=nil){ ctxx.errstop("1st arg is not filetype: "..ctxx.vinfo(fh), 2) }
	local msg="dbg( "
	local submsg=""
	local buf
	//#は渡された...の引数の個数マーク
	for(i=1,select("#", ...) ) {
		buf=select(i, ...)
		if(type(buf)=="table"){
			msg=msg.."tbl:"..i..", "
			submsg=submsg.."tbl"..i..":"..ctxx.ftinfo(nil, buf).."\n"
			goto lbl_NEXT
		}
		msg=msg..tostring(buf)..":"..ctxx.istypes(buf)..", "
		::lbl_NEXT::
	}
	msg=string.gsub(msg, ", $", " )")
	if(#submsg!=0){	msg=msg.."\n"..submsg }
	if( select("#", ...)==0){	msg="dbg()\n" }
//print("dbgfunc_1", msg)
//	msg = "(): "..msg.."\n\n"
//print( ctxx.S_FILE(2) )
//print( ctxx.S_LINE(2) )
//print( ctxx.S_FUNC(2) )
//	msg = ctxx.S_FILE(2).." "..ctxx.S_LINE(2)..": func_SEGV_mode(): "..msg.."\n\n"
	msg = (ctxx.S_FILE(2)||"").." "..(ctxx.S_LINE(2)||"")..": "..(ctxx.S_FUNC(2)||"").."(): "..msg.."\n\n"

//	msg = ctxx.S_FILE(2).." "..ctxx.S_LINE(2)..": "..ctxx.S_FUNC(2).."(): "..msg.."\n\n"
//	msg = string.format("%s %s: %s(): %s\n\n", ctxx.S_FILE(2)
//	, ctxx.S_LINE(2), ctxx.S_FUNC(2), msg)
//	msg = ctxx.sprintf("%s %s: %s(): %s\n", ctxx.S_FILE(2)
//	, ctxx.S_LINE(2), ctxx.S_FUNC(2), msg)
	if(fh){
//print("dbgfunc_2")
		fh.write(fh, msg)
//	ctxx.fprintf(fh,"%s\n",msg)
//print("dbgfunc_3")
//		fh.flush(fh)
	}
	return msg
}
fn ctxx.dbg(...) { return ctxx.fdbg(io.stderr, ...) }
//SH_TSS
fn u.addtest.t_dbg() {
	tt={aa=11,bb=22,cc=44}
	t={a=11,b=22, tt}
	local rtn = u.fdbg(nil, 11,22,t,"unko")
	u.test_neq(rtn ,nil)
	print("disp_ck:	"..rtn)
}
//SH_TSE

/*-*
@_name	clit, sprintf, fprintf, printf
@auther momi-g
@brief	C style print format.
@_synopsys
	str clit(str)
	str sprintf(str, ...) / printf(str, ...)
	str fprintf(nil/fh, str, ...)
@_eg
	str = clit( [[\041]].."\041" )	--> "!)", C\041==33, lua\041==41
	str = clit( [[he\llo\012world%d\n]] )	--> he\llo(\n)world%d(\n)
	str = sprintf([[hello\012world%d\n]], 33 )	--> "hello(\n)world33(\n)"
@param clit(str) '\' works as posix escfmt. \ooo, \xFF, \ + abntvrf?\'",\uU
@param printf(str) str is passed to clit() then passed to string.format().  
@param nil/fh	io.stdout, io.stderr etc. no out if nil is set. 
@param str	if arg 'str' is nil, conv to "", blank string.
@return 1 str. rtn nil,emsg if err.
@_conforming lua5.1+ , luajit2.0+
@version 1.1.3, 2021-06-27
-*/

local
fn sprintfsub(fmt, ...) {
	local emsg
	fmt, emsg=ctxx.clit(fmt)
	if(fmt==nil){return nil, emsg}
	return string.format(fmt, ...)
}
fn ctxx.sprintf(fmt, ...) {
	return sprintfsub(fmt, ...)
}
fn ctxx.fprintf(fh, fmt, ...) {
	local emsg
	fmt, emsg=sprintfsub(fmt, ...)
	if(fh==nil){ return fmt }
	if(fmt==nil){return nil, emsg}
	fh.write(fh, fmt)
	return fmt
}
fn ctxx.printf(fmt, ...) {
	local buf, emsg = ctxx.fprintf(io.stdout, fmt, ...)
	if(buf==nil){return nil, emsg}
	return buf
}

// \ooo\a系以外のescは放置 >> 修正 [[ \123\x00 ]] 系で完全対応させる
fn ctxx.clit(fmt){
	if(fmt==nil){fmt=""}
	if(type(fmt)!="string"){
		local emsg = ctxx.fperr(nil, "1st arg is not nil/str: "..type(fmt), 2)
		return nil,emsg
	}
	//\000 octtype
	local pos=1
	local sres = ""

	while(pos<=#fmt) {
		local raw  = string.match(fmt, "^[^\\]+", pos)
		local elit = string.match(fmt, "^\\[abfnrtv\\?\"\']", pos)
		local eoct = string.match(fmt, "^\\[0-3]?[0-7]?[0-7]", pos)
		local hh = "[0-9a-fA-F]"
		local ehex = string.match(fmt, "^\\x"..hh..hh, pos)
		local euni = string.match(fmt, "^\\u"..hh..hh..hh..hh, pos)
		local eUNI = string.match(fmt, "^\\U"..hh..hh..hh..hh..hh..hh..hh..hh , pos)
		local s = raw||elit||eoct||ehex||euni||eUNI
		if(raw){ sres=sres..s; pos=pos+string.len(s) }
		elif(elit){
			pos=pos+string.len(s)
			local cbuf = string.sub(s, 2, 2)
			if(cbuf=="\\"){ sres=sres.."\\"}
			elif(cbuf=="a"){sres=sres.."\a"}
			elif(cbuf=="b"){sres=sres.."\b"}
			elif(cbuf=="f"){sres=sres.."\f"}
			elif(cbuf=="n"){sres=sres.."\n"}
			elif(cbuf=="r"){sres=sres.."\r"}
			elif(cbuf=="t"){sres=sres.."\t"}
			elif(cbuf=="v"){sres=sres.."\v"}
		}
		elif(eoct){
			pos=pos+string.len(s)
			local buf = string.sub(s, 2)
			local num = tonumber(buf, 8)
			local cbuf = string.char(num)
			sres=sres..cbuf
		}
		elif(ehex){
			pos=pos+string.len(s)
			local buf = string.sub(s, 3)
			local num=tonumber(buf, 16)
			local cbuf = string.char(num)
			sres=sres..cbuf
		}
		elif(euni||eUNI){
			pos=pos+string.len(s)
			local buf = ctxx.strconv(s, "uc:b")
			sres=sres..buf
		}
		elif(s==nil){
			//if s==nil, err
			local emsg = ctxx.fperr(nil,"bad escchar: "..string.sub(fmt,pos,pos+20),2)
			return nil, emsg
		}
	}
	return sres
}

//SH_TSS
fn u.addtest.t_printf(){
	local rtn, emsg = u.sprintf("1234")
	u.test_eq(rtn, "1234")
	
	rtn = u.fprintf(nil, [[%d\01278\n]], 1234)
	u.test_eq(rtn ,"1234\n78\n")
	
	rtn, emsg = u.fprintf(nil, [[@hello\nworld\042\n \u{0022}\n]])
	u.test_eq(rtn, nil)

	rtn, emsg = u.fprintf(nil, [[@hello\nworld\042\n \u0022}\n]])
	u.test_eq(rtn, "@hello\nworld\"\n \34}\n")

	rtn, emsg = u.fprintf(nil, [[@hello\nworld\042\n \u022}\n]])
	u.test_eq(rtn, nil)

	rtn, emsg = u.fprintf(nil, [[@hello\nworld\042\n \U022}\n]])
	u.test_eq(rtn, nil)

	rtn, emsg = u.fprintf(nil, [[@hello\nworld\042\n \U00000022}\n]])
	u.test_eq(rtn, "@hello\nworld\"\n \"}\n", "UNI")
}
//SH_TSE


/*-*
@_name	matchlit, matchmlit, matchcmt, matchmcmt
@auther momi-g
@brief	grep 1st lit ("",'',[?[..]?]), cmt(--(\n), --[?[..]?]) str. skip endesc \" \'. 
@_synopsys
	str,num,num matchlit(str/nil, num/nil)	...mlit,cmt,mcmt style is same as lit
@_eg
	str, s, e = matchlit( [[12"4\041\"\2" zzz "yy" ]] )	--> 4\041\"\2, 3, 13
	str, s, e = matchlit( [[12'4\041\'\2' zzz "yy" ]], 4 )	--> \2, 10, 13
	str, s, e = matchlit( "12345" )	--> nil, nil, nil
	str, s, e = matchlit( "123'5" )	--> nil, 4, nil
	str, s, e = matchmlit( "[[345]]" )	--> 345, 1, 7
	str, s, e = matchcmt("--[[567\n]]")	--> [[567, 1, 8
	str, s, e = matchmcmt("12'4--[==[ab'c]==]")	--> ab'c, 5, 18
@param str base string. conv to "" if nil.
@param num/nil search start position. set 1 if nil.
@return 3rtn. 1:match str. 2/3:charpos with open/close. 3rd is nil if close not found. 
@_details lit() / mlit() / cmt() / mcmt() dont care about each other.
@_conforming lua5.1+, luajit2.0+
@version 1.1.3, y2020m04d08
-*/

fn ctxx.matchlit(str, pos) {
	if(pos==nil){pos=1}
	if(type(str)!="string"){ ctxx.errstop("1st arg must be 'string': "..type(str),2 ) }
	if(type(pos)!="number"){ ctxx.errstop("2nd arg must be 'nil/num': "..type(pos),2 ) }

	local rtn=nil
	local cc=nil
	local s,ss,e,ee, _ = nil,nil,nil,nil
	while(pos <= #str){
		if(cc==nil){
			s = string.find(str, "['\"]", pos)
			if(s==nil){break}
			cc = string.sub(str, s,s)
			pos=s+1
			goto lbl_NEXT
		}
		if(cc!=nil){
			_, e = string.find(str, cc, pos, true)
			if(e==nil){break}
			_, ee = string.find(str, '\\\\', pos, true)
			if(ee!=nil && ee<e){pos=ee+1; goto lbl_NEXT}
			_, ee = string.find(str, '\\'..cc, pos, true)
			if(ee==e){ pos=e+1;goto lbl_NEXT }
			rtn = string.sub(str, s+1, e-1)
			break
		}
		::lbl_NEXT::
	}
	return rtn, s, e
}
//SH_TSS
fn u.addtest.t_matchlit(){
	local str, s,e = u.matchlit(/*="12\"3" "aaaa" "bb" "jkl" ""=*/,1)
	u.test_eq(str, "12\\\"3")
}
//SH_TSE

fn ctxx.matchcmt(str, pos) {
	if(pos==nil){pos=1}
	if(type(str)!="string"){ ctxx.errstop("1st arg must be 'string': "..type(str),2 ) }
	if(type(pos)!="number"){ ctxx.errstop("2nd arg must be 'nil/num': "..type(pos),2 ) }
	local rtn=nil
	local s, e = string.find(str, "[-][-]", pos)
	local ss, ee = string.find(str, "[-][-][%[]=*[%[]", pos)

	if(s!=nil && ss==nil || s!=nil && s<ss){
		ss, ee = string.find(str, "\n", s)
		if(ee!=nil){rtn = string.sub(str, s, ee)}
	}
	return rtn, s, ee
}

//multi系は[[--]]が同じなのでpre--で共通化
local
fn match_sub(str, pos, pre){
	if(pos==nil){pos=1}
	if(type(str)!="string"){ ctxx.errstop("1st arg must be 'string': "..type(str), 2 ) }
	if(type(pos)!="number"){ ctxx.errstop("2nd arg must be 'nil/num': "..type(pos), 2 ) }
	local rtn, spos, epos=nil
	local emsg=nil
	local buf=nil
	local s,e,ss,ee=nil,nil,nil,nil
	
	s, e = string.find(str, pre.."[%[]=*[%[]", pos)
	if(s==nil){goto lbl_RTN}
	spos=s

	buf = string.match(str, "[%[]=*[%[]", pos)
	buf = string.gsub(buf, "[%[]", "]")	// [==[ --> ]==]
	ss, ee = string.find(str, buf, e+1,true)
	if(ss==nil){ goto lbl_RTN }
	epos=ee
	rtn = string.sub(str, e+1, ss-1)
	::lbl_RTN::
	return rtn, spos, epos	//後ろ二つはrawのまま。その方が対応範囲が広い
}
fn ctxx.matchmlit(str, pos){ return match_sub(str, pos, "") }
fn ctxx.matchmcmt(str, pos){ return match_sub(str, pos, "[-][-]") }

//SH_TSS
fn u.addtest.t_matchmcmt(){
	local str = nil
	local rtn, s, e = nil, nil,nil

	str = "abc"
	rtn, s, e = u.matchmcmt(str)
	u.test_eq(rtn, nil)
	print(s)
	
	str = "abc--[]"
	rtn, s, e = u.matchmcmt(str)
	u.test_eq(rtn, nil)

	str = "abc--[[123]]"
	rtn, s, e = u.matchmcmt(str)
	u.test_eq(rtn, "123")
	
	str = "abc--[===[]==]123]===]"
	rtn, s, e = u.matchmcmt(str)
	u.test_eq(rtn, "]==]123")

	str = "abc--[["
	rtn, s, e = u.matchmcmt(str)
	u.test_eq(rtn, nil)
	print(rtn, s, e)

	str = "abc[[]]--[[]]"
	rtn, s, e = u.matchmcmt(str)
	u.test_eq(rtn, "")
	print(rtn, s,e)
	
	rtn, s, e = u.matchlit(str)
	print(s,e)
	rtn, s, e = u.matchmlit(str)
	print(s,e)
	rtn, s, e = u.matchcmt(str)
	print(s,e, rtn)
	rtn, s, e = u.matchmcmt(str)
	print(s,e)


//	rtn, s, e = u.matchmcmt(print)
//	rtn, s, e = u.matchmcmt("", print)
}
//SH_TSE

/*-*
@_name	strconv
@auther momi-g
@brief	str converter, utf8byte/utf8/unicode, supports sequential input.
@_synopsys	str/nil,str/nil,str/nil strconv(str1, str2, num/nil)
@_eg
	str,lft,emsg = strconv("hw","b:u8")	--> str=\150 \167, lft="", emsg=nil
	str,lft,emsg = strconv("hw", "b:uc") --> str=\u{0068}\u{0077} (4/8lowerchar)
	str = strconv([[\u{068}\u{77}]],"uc:b") --> str="hw" (uni-in: n-degits)
	str = strconv([[\150\167]], "u8:b") --> str="hw" (u8-in: only 3-degits)
	str=strconv("longmsg","b:u8",60) --> ..(\n).. output maxlen is 60 in oneline.
	
	--"あい" >> \201\204 + \343\201(valid)>> \201\204\3 + 43\201(illegal split)
	str,lft,emsg=strconv([[\201\204\3]],"u8:b") --> "あ", [[\3]], "invalid seq"
	buf = lft .. [[43\201]]
	str,lft,emsg = strconv(buf, "u8:b")	--> "い", "", nil
	
	str,lft,emsg = strconv([[\777]], "u8:b") --> nil, nil, "out of utf8rule"
	
@param str1 srcstr. b:lua_str, u8:\ooo (3oct) uc:\u{xx} (lower/UPPER n-hex char)
@param str2 b(yte),u8,uc combination. "src:out".
@param num max strlen of output. works only at 'uc/u8' out. no split if nil(dfl).
@return 3rtn. 1:converted str 2:unprocessed str 3:emsg. see detail.
@details
 rtn case is as follows
 - completely suc: r1,r2,r3 = str,str,NIL
 - partially suc : r1,r2,r3 = str,str,str
 - fail. invalid input: r1,r2,r3 = NIL,NIL,str
 others 
 - u8src needs 3-degits. 1-2 degits causes err. ucsrc allows n-hex degits.
 - u8/uc src ignores not numchar. ("150%*167","u8:b")("6A 0x77","uc:u8") is safe.
 - u8out splits sequence with " " for each char. maxlen doesnt split sequence.
 - uc-in allows lower/upper char. uc-out outputs lower char with 4/8 hex degits.
@_conforming lua5.1+ , luajit2.0+
@version 1.0.4, 2020-09-05
-*/

fn ctxx.strconv(str, ini, mlen){
	local emsg=nil
	local rstr=""
	local nstr=""	//残り連結推奨文字列
	local src=""
	local dst=""
	local buf=""
	if(mlen!=nil && (type(mlen)!="number" || mlen<16)){
		ctxx.errstop("3rd arg must be nil / 16=<num: "..ctxx.vinfo(mlen) )
	} 
	if(type(str) !="string" || type(ini) !="string"){
		ctxx.errstop("invalid args type. 1st,2nd needs str: "..ctxx.vinfo(str,ini) )
	}
	//matchは全てstring返し。nilはnomatchだけ。
	src, dst = string.match(ini, "([a-z0-9]+):([a-z0-9]+)")
	if(src==nil){ ctxx.errstop("invalid convert command: "..ctxx.vinfo(ini) ) }

	buf = {uc=1, u8=1, b=1}
	if(buf[src]==nil || buf[dst]==nil){
		ctxx.errstop("invalid convert setting (b:uc etc): "..ctxx.vinfo(ini) )
	}
	if(src==dst){ ctxx.errstop(" srctype == outyupe is not support: "..ctxx.vinfo(ini) ) }
	
	//while系の共通変数。メモってnrtnで再利用前提なあたり
	local uc,len,pos,ppos = 0,0,1,1
	local s,e,ns,n = 0,0,0,0
	if(src=="uc"){
		local fmt='%c'	//byte out
		if(dst == 'u8'){ fmt='\\%.3o' }
		while(1){
			s,e = string.find(str, "[0-9a-fA-F]+", pos)
			if(s!=nil){ ns=string.sub(str, s,e) }
			if(s==nil){ pos=#str; break }
			pos=e+1
			ppos=e+1
			n=tonumber(ns, 16)
			if(n>0x10FFFF){
				emsg="erange unicode: "..string.format("%.4x", n)
				rstr=nil;nstr=nil; goto lbl_RTN
			}
			len, buf=0,0;
			if( n <= 0x7F){len=0; buf=0}
			elif( n <= 0x7FF){len=1; buf=6*(2^5)}
			elif( n <= 0xFFFF){len=2; buf=14*(2^4)}
			elif( n <= 0x10FFFF){len=3; buf=30*(2^3)}

			buf=buf + math.modf(n/(2^(6*len)) )
			rstr=rstr .. string.format(fmt, buf)
			n=math.fmod(n, 2^(6*len) ) 

			while(len!=0){
				len=len-1
				buf=math.modf(n/(2^(6*len)) ) + 2^7	// 10xxxxxx
				rstr=rstr .. string.format(fmt, buf)
				n=math.fmod(n, 2^(6*len) ) 
			}
			if(dst == 'u8'){ rstr=rstr.." " }
		}
		goto lbl_RTN
	}
	if(src=="u8"){
		while(1){
			s,e = string.find( str, "[0-9][0-9]?[0-9]?", pos )
			if(s!=nil){ ns=string.sub(str, s,e) }
			if(s==nil){ pos=#str; break }
			pos=e+1
			//尻尾はちぎれている可能性があるので、それ以外の not 3-degits
			if(#ns != 3 && e!= #str){
				emsg="u8 input allows only octal 3-digits: "..string.sub( str, s,s+10)
				rstr=nil; nstr=nil; break
			}
			elif(#ns != 3){
				emsg="u8 input allows only octal 3-digits: "..string.sub( str, s,s+10)
				break
			}
			
			local n=tonumber(ns, 8)
			if(len==0){
				if(math.modf(n/(2^7) ) == 0){ len=1 ; uc=math.fmod(n, 2^7) }
				elif(math.modf(n/(2^5) ) == 6){ len=2 ; uc=math.fmod(n, 2^5) }
				elif(math.modf(n/(2^4) ) == 14){ len=3; uc=math.fmod(n, 2^4) }
				elif(math.modf(n/(2^3) ) == 30){ len=4; uc=math.fmod(n, 2^4) }
				elif(1==1){
					emsg="invalid utf8 1st byte"..string.sub( str, s,s+10)
					rstr=nil; nstr=nil; break
				}
			}
			elif(len!=0 && math.modf(n/(2^6) )==2 ){ uc=uc*(2^6) + math.fmod(n, 2^6) }			
			elif(1){
				emsg="invalid utf8 seq"..string.sub( str, s,s+10)
				rstr=nil; nstr=nil; break
			}
			len=len-1
			
			//出力による切り替え
			if(dst=="b"){ rstr = rstr..string.format('%c', n) }
			elif(dst=="uc" && len==0){
			//	local buf=string.format('\\u{%.4X}', uc)
				local buf=string.format('\\u{%.4x}', uc)
			//	if(8<#buf){ buf=string.format('\\U{%.8X}', uc) }
				if(8<#buf){ buf=string.format('\\u{%.8x}', uc) }
				rstr=rstr..buf
				uc=0
			}
			if(len==0){ppos=pos}
		}
		if(len!=0){ emsg="detect invalid utf8 EOS." }
		goto lbl_RTN
	}
	if(src=="b"){
		if(dst=="u8"){
			for(i=1, #str){
				n = string.byte(str, i)
				if(math.modf(n/(2^6) ) != 2 ){ rstr=rstr.." " }	
				rstr=rstr..string.format('\\%.3o', n)
			}
			rstr = string.sub (rstr, 2 )
			ppos=#str+1
			goto lbl_RTN
		}
		if(dst=="uc"){
			len=0
			for(i=1, #str){
				n = string.byte(str, i)
				if(len==0) {
					if(math.modf(n/(2^7) ) == 0){ len=1; buf=0 ; uc=math.fmod(n, 2^7) }
					elif(math.modf(n/(2^5) ) == 6){ len=2 ; buf=0x80;uc=math.fmod(n, 2^5) }
					elif(math.modf(n/(2^4) ) == 14){ len=3; buf=0x800;uc=math.fmod(n, 2^4) }
					elif(math.modf(n/(2^3) ) == 30){ len=4; buf=0x10000;uc=math.fmod(n, 2^4) }
					elif(1==1) {ctxx.errstop("unreachable code: uc decode failed"..i, 1) }
				}
				elif(1){ uc=uc*(2^6) + math.fmod(n, 2^6) }
				len=len-1
				if(len==0){
				//	ns=string.format('\\u{%.4X}', uc)
					ns=string.format('\\u{%.4x}', uc)
				//	if(8<#ns){ ns=string.format('\\U{%.8X}', uc) }
					if(8<#ns){ ns=string.format('\\u{%.8x}', uc) }
					rstr=rstr..ns
					ppos=i+1
					if(uc<buf){
						//add min unicode ck
						emsg="detect risky utf8byte seq: pos "..i
						rstr=nil; nstr=nil; goto lbl_RTN
					}
				}
			}
			goto lbl_RTN
		}
	}
	::lbl_RTN::
	if(rstr!=nil && mlen!=nil && dst!="b"){
		local ptn=""
		local buf=""
		local offset=1
		//頭の位置をとらえる
		if(dst=="u8"){ ptn=" [^ ]*$"; offset = offset+1 }	//頭に" " が乗っかる
		if(dst=="uc"){ ptn="\\[^\\]*$"}
		while(mlen<#rstr){
			local lbuf = string.sub(rstr, 1, mlen+1)
			local s, e = string.find(lbuf, ptn)
			if(s<mlen+1){ lbuf = string.sub(rstr, 1, s-1) }
			elif(1){ lbuf = string.sub(rstr, 1, mlen) }
			buf=buf.."\n"..lbuf
			rstr=string.sub(rstr, #lbuf+offset)
		}
		if(#rstr!=0){ rstr=buf.."\n"..rstr }
		rstr=string.gsub(rstr, "^[\n]", "", 1)
	}
	if(rstr!=nil){ nstr=string.sub(str, ppos) }
	return rstr, nstr, emsg
}
ctxx.u8conv = ctxx.strconv

//SH_TSS
fn u.addtest.t_strconv(){
	print("--byte test")
	print(u.strconv("hw1b", "b:u8") )
	print(u.strconv("hw1b", "b:uc") )
	print(u.strconv("Ω2b", "b:u8") )
	print(u.strconv("Ω2b", "b:uc") )
	print(u.strconv("あいう3b", "b:u8") )
	print(u.strconv("あいう3b", "b:uc") )
	print(u.strconv("😊4b", "b:u8") )
	print(u.strconv("😊4b", "b:uc") )
	print("--utf8 test")
	print(u.strconv(/*=\150\167\061\142=*/, "u8:b") )
	print(u.strconv(/*=\150\167\061\142=*/, "u8:uc") )
	print(u.strconv(/*=\316\251\062\142=*/, "u8:b") )
	print(u.strconv(/*=\316\251\062\142=*/, "u8:uc") )
	print(u.strconv(/*=\343\201\202\343\201\204\343\201\206\063\142=*/, "u8:b", 16) )
	print(u.strconv(/*=\343\201\202\343\201\204\343\201\206\063\142=*/, "u8:uc", 30) )
	print(u.strconv(/*=\360\237\230\212\064\142=*/, "u8:b") )
	print(u.strconv(/*=\360\237\230\212\064\142=*/, "u8:uc") )
	print("errtest x 3")
	print(u.strconv(/*=\361=*/, "u8:uc") )
	print(u.strconv(/*=\361=*/, "u8:b") )
	print(u.strconv(/*=\15\167\061\142=*/, "u8:b") )
	print("--uc test")
	print(u.strconv(/*=\u{0068}\u{0077}\u{0031}\u{0062}=*/, "uc:b") )
	print(u.strconv(/*=\u{0068}\u{0077}\u{0031}\u{0062}=*/, "uc:u8") )
	print(u.strconv(/*=\u{03A9}\u{0032}\u{0062}=*/, "uc:b") )
	print(u.strconv(/*=\u{03A9}\u{0032}\u{0062}=*/, "uc:u8") )
	print(u.strconv(/*=\u{3042}\u{3044}\u{3046}\u{0033}\u{0062}=*/, "uc:b") )
	print(u.strconv(/*=\u{3042}\u{3044}\u{3046}\u{0033}\u{0062}=*/, "uc:u8", 30) )
	print(u.strconv(/*=\u{1F60A}\u{0034}\u{0062}=*/, "uc:b") )
	print(u.strconv(/*=\u{1F60A}\u{0034}\u{0062}=*/, "uc:u8") )
	print(u.strconv(/*=\u{11F60A}\u{0034}=*/, "uc:u8") )
	print("--seqencial read test")
	for(k,v in pairs({"u8:b", "u8:uc"}) ){
		buf,str,emsg = u.strconv(/*=\343\201\202\343=*/, v)
		print(buf,str,emsg)
		str=str .. [[\201\204\343\201\206\063\142]]
		buf,str,emsg = u.strconv(str, v)
		print(buf,str,emsg)
	}
}
//SH_TSE

/*-*
@_name	stsort
@auther momi-g
@brief	table.sort() wrapper, Stable Table sort. 
@_synopsys	nortn stsort(tbl, func)
@_eg
	tbl={ 2, 1.9, 1.1, 3}
	function comp(a, b)
		return ( math.modf(a) < math.modf(b) )
	end		--(1.9, 1,1):1<1 false,	(1.1, 1,9):1<1 false
	
	stsort(tbl, comp)		--> tbl={1.9, 1.1, 2, 3}	...save order
	
@param tbl	seqtbl
@param func	compare function 
@return nothing
@details	usage is completely the same as table.sort()
@_conforming lua5.1+, luajit2.0+
@version 1.0.0, y2020m04d07
-*/
function ctxx.stsort(tbl, comp){
	local alttbl={}
	for(k,v in ipairs(tbl) ){ alttbl[k]={k, v} }
	local function altcomp(bf, af) {
		//bool変換。nilとか123とかありえる
		local ck1 = 1 and comp(bf[2], af[2])
		local ck2 = 1 and comp(af[2], bf[2])
		if(ck1!=ck2){ return ck1 }
		return ( bf[1]<af[1] )
	}
	table.sort(alttbl, altcomp)
	for(k,v in ipairs(alttbl) ){ tbl[k]=v[2] }
}

//SH_TSS
fn u.addtest.t_stsort() {
	local tbl={1, 3, 2.4, 2.1}
	local function comp(a,b){ return (math.modf(a) < math.modf(b) ) }
	table.sort(tbl, comp)
	u.dbg(tbl)
	u.stsort(tbl, comp)
	u.dbg(tbl)
}
//SH_TSE


/*-*
@_name	ttrm
@auther momi-g
@brief	compare tbl keys and remove overlap member.
@_synopsys	tbl, num ttrm(tbl1, tbl2)
@_eg
	res, cnt = ttrm( {a=1,b=2,10,20}, {a=99,55} )	--> res={b=2,20}, cnt=2
@param tbl1 main tbl.
@param tbl2 sub tbl.
@return  2 rtn. 1:maintbl data which has removed subtbl keys.	2:rtntbl size
@details roughly equals to: cat maintbl | grep -v subtbl_keys
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, y2020m03d08
-*/

fn ctxx.ttrm(a,b) {
	if(a==nil){a={}}
	if(b==nil){b={}}
	local rtn= {}
	local cnt=0

	if(type(a)!="table" || type(a)!="table"){
		local msg=ctxx.ferrdisp(io.stderr, "CODE_ERR: args is not tbl: "..a.."/"..b,1)
		rtn=nil
		cnt=msg
		goto lbl_RTN
	}
	for(k,v in pairs(a)) {
		if(b[k]==nil) { rtn[k]=v; cnt=cnt+1 }
	}
	::lbl_RTN::
	return rtn, cnt
}

/*-*
@_name	tsrm
@auther momi-g
@brief	remove keys from tbl using sequence data.
@_synopsys	tbl, num tsrm(tbl1, tbl2)
@_eg
	res, cnt = tsrm( {a=11,b=22,10,30}, {"b", 2} )	--> res={a=11,10}, cnt=2
@param tbl1 maintbl.
@param tbl2 subtbl. demands sequence with num/str data.
@return 2 rtn. 1:maintbl data which has removed keys by subtbl.	2:rtntbl size
@details subtbl{} is keyname *sequence* you want del.
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, y2020m03d08
-*/

fn ctxx.tsrm(tbl, keys) {
	local rtn= {}
	local cnt= ctxx.tsize(tbl)
	if(tbl==nil){tbl={} }
	if(keys==nil){leys={} }
	if(type(tbl)!="table" || type(keys)!="table"){
		local msg=ctxx.ferrdisp(io.stderr, "CODE_ERR: args is not tbl: "..a.."/"..b,1)
		rtn=nil
		cnt=msg
		goto lbl_RTN
	}
	for(k,v in ipairs(keys)) {
		if(type(v)!="string" && type(v)!="number" ){
			local msg=ctxx.sprintf(
			"CODE_ERR: 2nd arg, keylist is not str/num: keys[%s]/%s%s",
			k,type(v),":"..v)
			local msg = ctxx.ferrdisp(io.stderr, msg, 1)
			rtn=nil
			cnt=msg
			goto lbl_RTN
		}
		if(tbl[v] != nil){ tbl[v]=nil; cnt=cnt-1 }
	}
	rtn=tbl
	::lbl_RTN::
	return rtn, cnt
}
//SH_TSS
fn u.addtest.t_ttrm(){
	t={a=11,b=22,c=44}
	tt={aa=11,bb=22,cc=44}
	a,b=u.ttrm(t,tt)
	u.test_eq(b==3)

	t={a=11,b=22,c=44}
	tt={aa=11,b=22,cc=44}
	a,b=u.ttrm(t,tt)
	u.test_eq(b==2)
	
	t={a=11,b=22,c=44}
	tt={"b"}
	a,b=u.tsrm(t,tt)
	u.test_eq(b==2)
}
//SH_TSE

/*-*
@_name	ttgrep
@auther momi-g
@brief	compare tbls and grep match keys data
@_synopsys	tbl, num ttgrep(tbl1, tbl2)
@_eg
	res, cnt = tsrm( {a=11,b=22,10,30}, {a=100, 2} )	--> res={a=11,30}, cnt=2
@param tbl1 maintbl.
@param tbl2 subtbl.
@return 2 rtn. 1:parts of maintbl which key is detect in subtbl too. 2:rtntbl size
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, y2020m03d08
-*/
fn ctxx.ttgrep(a,b) {
	if(a==nil){a={}}
	if(b==nil){b={}}
	local rtn= {}
	local cnt=0

	if(type(a)!="table" || type(a)!="table"){
		local msg=ctxx.ferrdisp(io.stderr, "CODE_ERR: args is not tbl: "..a.."/"..b,1)
		rtn=nil
		cnt=msg
		goto lbl_RTN
	}
	for(k,v in pairs(a)) {
		if(b[k]!=nil) { rtn[k]=v; cnt=cnt+1 }
	}
	::lbl_RTN::
	return rtn, cnt
}

/*-*
@_name	tsgrep
@auther momi-g
@brief	select tbl data using keyname sequence
@_synopsys	tbl, num tsgrep(tbl1, tbl2)
@_eg
	res, cnt = tsrm( {a=11,b=22,30,10}, {"b",1,3} )	--> res={b=22,30}, cnt=2
@param tbl1 maintbl.
@param tbl2 subtbl.
@return 2 rtn. 1:parts of maintbl which is select by subtbl(sequence). 2:rtntbl size
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, y2020m03d08
-*/
fn ctxx.tsgrep(tbl, keys) {
	local rtn= {}
	local cnt= 0
	if(tbl==nil){tbl={}}
	if(keys==nil){leys={}}
	if(type(tbl)!="table" || type(keys)!="table"){
		local msg=ctxx.ferrdisp(io.stderr, "CODE_ERR: args is not tbl: "..a.."/"..b,1)
		rtn=nil
		cnt=msg
		goto lbl_RTN
	}
	for(k,v in ipairs(keys)) {
		if(type(v)!="string" && type(v)!="number" ){
			local msg=ctxx.sprintf(
			"CODE_ERR: 2nd arg, keylist is not str/num: keys[%s]/%s%s",
			k,type(v),":"..v)
			local msg = ctxx.ferrdisp(io.stderr, msg, 1)
			rtn=nil
			cnt=msg
			goto lbl_RTN
		}
		if(tbl[v] != nil){ rtn[v]=tbl[v]; cnt=cnt+1 }
	}
	::lbl_RTN::
	return rtn, cnt
}

//SH_TSS
fn u.addtest.t_tsgrep(){
	local t={a=123,b=223,c=323}
	local k={"a","b", "2"}
	local tt, n = u.tsgrep(t,k)
	u.test_eq(n==2)
	print( u.tinfo(tt))
	local tt, n = u.tsgrep(nil,k)
	print(n)
	print(u.tinfo(tt))
}
//SH_TSE

/*-*
@_name	tmerge
@auther momi-g
@brief	concat tbl, overlapped keydata is ignored.
@_synopsys	tbl, num tmerge(tbl1, ...)
@_eg
	res, cnt = tmerge( {a=1,30}, {a=9,c=7,1,3} ) --> res={a=1,c=7,30,3}, cnt=4
@param tbl1 basetbl.
@param va_args addtbls.
@return 2 rtn. 1:merged tbl	.	2:tbl size
@_conforming lua5.1+ , luajit2.0+
@version 1.0.0, y2020m03d08
-*/
fn ctxx.tmerge(...) {
	//#は渡された...の引数の個数マーク
	local ac=select("#", ...)
	local av = {...}
	local rtn={}
	local emsg=nil
	for(i=1, ac){
		if(type(av[i]) == "table"){
			for(k,v in pairs(av[i]) ){
				rtn[k] = rtn[k] || v
			}
		}
		elif(av[i]!=nil){
			emsg=ctxx.sprintf("arg is not tbl/nil: av[%d]=%s", i, tostring(av[i]) )
			return nil, ctxx.fperr(nil, emsg, 2)
		}
	}
	//ig nil
	::lbl_RTN::
	return rtn, ctxx.tsize(rtn)
}

/*-*
@_name	tmergeo
@auther momi-g
@brief	concat tbl, overlap keydata is overwrite.
@_synopsys	tbl, num tmergeo(tbl1, ...)
@_eg
	res, cnt = tmergeo( {a=1,b=2}, {a=9,c=7,10} ) --> res={a=9,b=2,c=7,10}, cnt=4
@param tbl1 basetbl.
@param va_args addtbls.
@return 2 rtn. 1:merged tbl	.	2:tbl size
@_conforming lua5.1+ , luajit2.0+
@version 1.0.1, y2020m05d10
-*/
fn ctxx.tmergeo(...) {
	local ac=select("#", ...)
	local av = {...}
	local rtn={}
	local emsg=nil
	for(i=1, ac){
		local pos=ac+1-i
		if(type(av[pos]) == "table"){
			for(k,v in pairs(av[pos]) ){
				rtn[k] = rtn[k] || v
			}
		}
		elif(av[pos]!=nil){
			emsg=ctxx.sprintf("arg is not tbl/nil: av[%d]=%s", pos, tostring(av[pos]) )
			return nil, ctxx.fperr(nil, emsg, 2)
		}
		//ig nil
	}
	::lbl_RTN::
	return rtn, ctxx.tsize(rtn)
}

//SH_TSS
fn u.addtest.t_tmerge(){
	local t1 = {1,2}
	local t2 = {10,20,30}
	local t = u.tmerge(t1, t2 )
	u.dbg(t)
	t = u.tmergeo({1,2}, print)
	u.dbg(t)
}
//SH_TSE

/*-*
@_name	perr, fperr, errstop
@auther momi-g
@brief	make or print short errmsg.
@_synopsys
str perr(str/nil, num/nil)
str fperr(fh/nil, str/nil, num/nil)
nortn errstop(str/nil, num/nil)
@_eg
	emsg = fperr(io.stdout, "abc is invalid" ) --> emsg="errinfo+msg" + output
	emsg = perr( "aa is invalid", 1) --> equals to ferrinfo + fh=io.stderr
	errstop("aa is invalid", 2) --> error() wrapper. add func() name.
@param str errmsg. conv to "" if nil.
@param num stack lv as error(). 1:nowpos, 2:callsrc, 3-:more. set 1 if nil.
@param fh output target. noout if nil.
@return 0/1 rtn. 1:errmsg. perr(), errstop() outputs errmsg + "\n".
@_conforming lua5.1+ , luajit2.0+
@version 1.1.1, y2020m05d01
-*/

fn ctxx.errstop(msg, n){
	if(n==nil){n=1}
	local fname=ctxx.S_FUNC(2)
	error(fname.."(): "..msg, n+1)
}
fn ctxx.perr(msg, n){
	return ctxx.fperr(io.stderr, msg, n)
}
fn ctxx.fperr(fh, msg, n){
	local rtn=nil
	local emsg=nil
	if(fh!=nil && io.type(fh)==nil){
		ctxx.errstop("1st arg is not file handle", 2)
	}
	if(msg==nil){msg=""}
	if(type(msg)!="string"){
		ctxx.errstop("2nd arg is not str/nil: "..type(msg), 2)
	}
	if(n==nil){n=1}
	n=n+1
	local dobj=debug.getinfo(n, "nlSf");//(1)test()info. (2)test_parent() info
//	rtn = ctxx.fprintf(nil, "%s %s: %s(): %s", tostring(dobj.short_src)
//			, tostring(dobj.currentline), tostring(dobj.name), msg )
//	//fix, add "%s" 
	rtn = ctxx.fprintf(nil, "ERR %s(): %s", tostring(dobj.name), msg )
	ctxx.fprintf(fh, "%s", rtn.."\n")
	return rtn
}

//SH_TSS
fn u.addtest.t_perr(){
	u.fperr(nil, "123")
	u.perr("1234")
//	u.errstop("123", 1)
}
//SH_TSE

/*-*
@_name	laptime, flaptime
@auther momi-g
@brief	disp pg working time, os.clock() wrapper.
@_synopsys
str laptime(num/str/nil)
str flaptime(fh, num/str/nil)
@_eg
	res = laptime(0)	--> if num 0, timer reset + start. res=0
	msg = laptime() 	--> get ival time. disp to stderr. msg="sys 100 ms" etc.
	msg = laptime("hw") --> msg="sys 100 ms: hw"
	msg = flaptime(io.stdout, "hw") --> disp to stdout.
@param num	if num==0, reset timer and start. if not 0, disp/get used time.
@param fh	file handle to disp info. no msg disp if nil set.
@return 1 rtn. 1: passed time msg. units of time are "s/ms" or others.
@_conforming lua5.1+, luajit2.0+
@version 1.0.5, 2021-03-03
-*/

local laptimebuff=0
fn ctxx.laptime(key){ return ctxx.flaptime(io.stderr, key) }
fn ctxx.flaptime(fh, key){
	local res=""
	if(key==nil){key="-"}
	if(key==0){ laptimebuff=os.clock() }
	elif(1){
		local i=1
		res=os.clock()-laptimebuff
		local sym={[1]="s", [2]="ms", [3]="us", [4]="ns"}
		while(i<2){
			if(res>=1){break}
			i=i+1
			res=res*1000
		}
		res= ctxx.sprintf("sys	%.3f %s", res, sym[i])
		res=res..": "..tostring(key).."\n"
		ctxx.fprintf(fh, "%s", res)
		return res
	}
	return 0
}
//SH_TSS
fn u.addtest.t_laptime(){
	// luaは単体でマイクロ秒を扱えない。systemかccallしかない。
	local t1 = os.time ()
	
	u.ffsrcinfo(io.stdout, "*SH_bn*.lua", {"^[%*]"}, "@.?name[^\n]*\n", {"fflaptime"} )
	local res = u.laptime(0)
	u.test_eq(res, 0)
	res = u.laptime()
	res = u.laptime()
	res = u.laptime(0)
	res = u.laptime()
	
	local t2 = os.time ()
	print(t1, t2, os.clock())
	print(os.difftime(t2,t1) )
}
//SH_TSE


/*-*
@_name	cktp
@auther momi-g
@brief	check args type if global var '_debug' != nil.
@_synopsys	suc cktp(str...)
@_eg
	function myf(lv, word, ...)
		rtn, emsg = cktp("N,s,n", "n,sN,A...", "")
	end
	_debug=1
	myf()	--> suc: rtn is not nil. hit the last setting "".
	myf(8)	--> fail: rtn is nil. no setting hits to "n". 
	myf(8,"a")	--> suc: args is "n,s" and hits "n,sN,A..." (vaarg allow noarg)
	_debug=nil
	myf(8)	--> suc: rtn is not nil.
	
@param str	allow types fmt. "n,sN,A..." means 1st:num 2nd:str or Nil 3-:Alltypes
@return	1 rtn. suc:not nil/false	fail:nil,emsg (no setfmt hits callstyle)
@details
 types are (N)il,(b)ool,(n)um,(s)tr,(t)bl,(f)unc,(u)data,(T)hread + (A)ll,(i)nt
 "," is splitter. "..." means vaargs. note that vaargs contains noarg.
 if "tn...", noarg or all vaargs must be tbl or num.
 cktp() makes your func slow 500-1000 times. test 1000 call result is below.
 real	29.579 ms	(insert cktp(), _debug=1 )
 real	0.033 ms	(no use cktp(), _debug=nil )
 ...recommend you to use this func for dev, betatest or rarely called function.
@_conforming lua5.1+, luajit2.0+
@version 1.0.4, y2020m04d09
-*/

fn ctxx.cktp(...){
	if( _debug==nil ){return 0}
	if(select("#", ...) == 0){return 0}
	//origargs
	local argc = debug.getinfo(2).nparams
	local argv={}
	local emsg=""
	for(i=1, argc){
		local k,v = debug.getlocal(2, i)
		local cc=ctxx.istypes(v)
		if(type(v)=="number"){
			local _,buf=math.modf(v)
			if(buf==0){ cc=cc.."(i)"}
		}
		argv[#argv+1] = cc
		emsg=emsg..","..cc
	}
	local i=0
	while(1){
		i=i+1
		local k,v = debug.getlocal(2, -i)
		if(k==nil){ break }
		local cc=ctxx.istypes(v)
		if(type(v)=="number"){
			local _,buf=math.modf(v)
			if(buf==0){ cc=cc.."(i)"}
		}
		argv[#argv+1] = cc
		emsg=emsg..","..cc
	}
	emsg="("..string.sub(emsg, 2).."): demands"
	//allowtype list
	local tlist={}
	for(k,v in ipairs({...}) ){
		//emsg用メモ
		if( type(v)!="string" ){
			ctxx.errstop("invalid args. eg) cktp('s,ft,N') cktp('Nns,Sn','N') etc", 2)
		}
		local buf=string.gsub(v, "[^NbnstfuTAi., ]", "")
		if(buf != v){
			ctxx.errstop("detect argchar other than 'NbnstfuTAi,. ' : "..v, 2)
		}
		if(#buf==0){ emsg=emsg.."/(noarg) " }
		elif(1){ emsg=emsg.."/ "..v }
		
		//tlistはbuf={"num", "str"} などのhashリスト. 1-でvaargのみ[-1]
		local tbuf={}
		local pos=1
		local apos=1
		while(1){
			//要求typeの加工
			//3argなら,が二つでスプリット。
			local s,e = string.find(v, "[^,]+", pos)
			if(s==nil){break}
			if(tbuf[-1]!=nil){
				//...の後は,が無いのでここにこないはず
				ctxx.errstop("invalid args. '...' allows only last arg"..v, 2)
			}
			local fmt = string.sub(v, s, e)
			pos=e+1
			s = string.find(fmt, "...", 1, true)
			if(s!=nil){apos=-1}
			fmt=string.gsub(fmt, "[^NbnstfuTAi]", "")
			local buf={}
			if(string.find(fmt, "A")){fmt="NbnstfuTi"}
			if(string.find(fmt, "i")){buf.i=1}
			if(string.find(fmt, "N")){buf.N=1}
			if(string.find(fmt, "b")){buf.b=1}
			if(string.find(fmt, "n")){buf.n=1}
			if(string.find(fmt, "s")){buf.s=1}
			if(string.find(fmt, "t")){buf.t=1}
			if(string.find(fmt, "f")){buf.f=1}
			if(string.find(fmt, "u")){buf.u=1}
			if(string.find(fmt, "T")){buf.t=1}
			tbuf[apos]=buf
			apos=apos+1
		}
		tlist[#tlist+1]=tbuf
	}
//ctxx.dbg(tlist, argv, emsg)

	//argv, tlistの比較。[-1]のvaarg以外はindを同じで比較可能。
	//実引数の数は全て仮引数に自動修正されるので分からない
	//()は(nil)と同様の処理をすることで全体的にうまくまとまる。
	local flg=nil
	local hit=nil
	for(k,v in ipairs(tlist) ){
		flg=1
		for(n,t in ipairs(argv) ){
			//一つじゃないのはn(i)のみ
			local tt="dmy"	
			if(#t!=1){t="n"; tt="i"}
			if(v[n]==nil && v[-1]!=nil){v[n]=v[-1]}
			if(v[n]==nil){ v[n]={["N"]=1 } } //args数は自動一致調整. nil補完
			//実引数のtypeがtlistにあるか判定。intはnumと被るので二段判定
			if( v[n][t] == nil && v[n][tt] == nil ){ flg=nil; break }	
		}
		hit=k
		if(flg==1){break}	//全argが成功した
	}
	if(flg==1){return hit}	//nil,false以外だけど、hitfmtの位置を返しとこう
	emsg="invalid args type: "..emsg
	return nil, emsg
}

//SH_TSS
fn u.addtest.t_cktp(){
	local myf
	local myff
	_debug=1
--[===[	
	myf = function(a){ return (u.cktp("")) }
	u.test_eq( myf(1), nil)
	myf = function(a){ return (u.cktp("bnstfuT")) }
	u.test_eq( myf(nil), nil)
	myf = function(a){ return (u.cktp("NbnstfuT")) }
	u.test_eq( myf(nil) != nil)
	myf = function(a, b){ return (u.cktp("N...")) }
	u.test_eq( myf() != nil)
	u.test_eq( myf("abc") , nil)
	
	myf = function(a, b){ return (u.cktp("n,s")) }
	u.test_eq( myf() == nil)
	u.test_eq( myf(1,"a") != nil)
	u.test_eq( myf(1,1) == nil)
	u.test_eq( myf(1) == nil)

	myf = function(a){ return (u.cktp("n,s", "")) }
	u.test_neq( myf() , nil)

	myf = function(a){ return (u.cktp("n,s", "A...")) }
	u.test_eq( myf(print) != nil)
]===]
	myf = function(a){//		local a,b = u.cktp("i")
		//return a,b
		return ( u.cktp("i") ) 
	}
	u.test_eq(myf(1) != nil)
	u.test_eq(myf(1.1) == nil)
	print( myf(1.1) )
//os.exit()

	//mode test
	_debug=1
	myf = function(a){ return (u.cktp("")) }
	u.test_eq( myf(print) == nil)
	_debug=nil
	myf = function(a){ return (u.cktp("")) }
	u.test_eq( myf(print) != nil)
	
	print("speed test, ckop(), nouse, _debug=nil")
	function myf(aaa, ...){
		local rtn, emsg = u.cktp("ns,tT,N", "s,s...", "")
		local a=1
	}
	function myff(aaa, ...){
		local a=1
	}
	_debug=1
	u.laptime(0)
	for(i=1, 1000){ myf(123,1,2,3) }
	u.laptime()	//1000call		29.579 ms	...typeが1つなら15ms程度。
	
	u.laptime(0)
	for(i=1, 1000){ myff(123,1,2,3) }
	u.laptime()	//1000call		29.579 ms	...typeが1つなら15ms程度。

	_debug=nil
	u.laptime(0)
	for(i=1, 1000){ myf(123,1,2,3) }
	u.laptime()	//1000call		0.024 ms
//	u.usage(u, "cktp_usage")
}
//SH_TSE


/*-*
@_name	ckopt
@auther momi-g
@brief	cmdline option parser.
@_synopsys	tb1,tb2 ckopt(str, tb_ini, tb_av)
@_eg
	av={ "-h", "-c9", "-d", "10.4", "abc", "-v", "99" }

	ini={	# 1:key 2:opt 3:dfl 4:type 5-:eval testcode. concat code string.
	{"verbose",	"-v",	"0",	"bool" }
	,{"usage",	"-h",	"0",	"bool", "usage();os.exit(0)" }
	,{"count",	"-c",	"1",	'int', "return (1<n and n<10),'invalid range'"}
	,{"delay",	"-d",	0.3,	"dbl" }
	,{"log", "-f",io.stderr,"str","local fh=io.open(s);raw.v=fh","return fh"}
	}

	opt, agleft = ckopt("", ini, av)
	--> opt.count==9, opt.log==io.stderr ...etc
	--> agleft=={"abc", "-v", "99"}

@param str	parse mode. set "xq" if eXpantion + Quiet mode. dfl:"".
	x: expantion. dfl mode works under the posix Utility Syntax Guidelines.
	 'x' allows symbol charopt, '-@', '-_' '-+' etc. '--' works as optend.
	 'x' parses until detect '--' or args end. eg) a.out abc -h >> hit -h

	q: quiet. dfl mode outputs emsg and exit(1) if err. q mode rtns nil,emsg. 

@param tb_ini	optsetting table. must:clm1-4	no_must:clm5,6...
	- clm1 :keyname used for access the parsed result. str.  
	- clm2 :cmdline short option char. needs prefix hyphen '-'. str.
	- clm3 :dfl val. dfl val skips testfunc. anytype.
	- clm4 :cmdin type. 'bool/int/dbl/str'. bool is changed to 0/1. str.
	- clm5-:optional. rawcode str you want to test cmdin val. expands as below.
 
 local function tf(raw)
 	local s,n = raw.s, raw.n	--> 's' is cmdin optarg str. n=tonumber(s)
 	-- clm5 --
 	-- clm6 --
 	...
 	return 1	--> test suc/fail == no_fail / 0,nil,false
 	-- return 0,'bad filename'	--> set emsg to 2nd rtnarg if you want
 end
	
	...preset local s,n is cmdin val str/num. special val 'raw.v' is used to
	set val directly to result.
	eg) (testcode) "raw.v='xyz';return 1"		--> opt.count == "xyz"

@param tb_av	parse target str array. index key + str val. use {} if nil.
@return tb1	main result tb. each opt has 3 data. rtn nil,emsg if err+q mode.
	rtn.(keyname): main val.	eg) opt.help==1, rtn.filename==io.stderr etc	
	rtn.(?_dfl): dfl val.	eg) opt.help_dfl=="0"
	rtn.(?_cmdstr):cmdin str. set nil if opt is dfl. eg)res.usage_cmdstr=="1"
@return tb2	left args. remove optdata and shift args. str array.
	eg) av={"-c9", "zz", "-h", "--", "abc"} --(x mode)--> tb2={"zz", "abc"}

@details -
@_conforming lua5.1+, luajit2.0+
@_note
 ckopt("", ini, av)	--> 0.180ms
 for(i=1,100000) do a=a+1 end --> 0.200ms ...parse cost is equals to 100k steps?
 
 http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02
 https://docs.oracle.com/cd/E19455-01/816-3518/6m9ptvr12/index.html
 https://www.mm2d.net/main/prog/c/getopt-03.html
 https://wp.mikeforce.net/gnome/category/gtk
 https://documents.mikeforce.net/gtk+-2.14.x-refs/gtk/html/gtk-General.html#gtk-init-with-args
 http://catb.org/%7Eesr/writings/taoup/html/ch10s05.html
@version 1.1.1, 2021-03-18
-*/

fn ctxx.ckopt(mode, tb, args){
	args=args||{}
	if(type(mode)!="string"){ error(ctxx.fperr(nil, "ag1 isnt string"), 2) }
	if(type(tb)	!="table"  ){ error(ctxx.fperr(nil, "ag2 isnt table"), 2) }
	if(type(args)!="table" ){ error(ctxx.fperr(nil, "ag3 isnt table"), 2) }
	//iniのstringからフォーマットを読み取ってarrにsplit
	local fn test_i(str){
		local ck=string.find(str, ".", 1, true)
		if(ck!=nil){ return 0 }
		ck=tonumber(str)
		if(ck==nil){return 0 }
		return 1
	}
	local fn test_d(str){
		local ck=tonumber(str)
		if(ck==nil){return 0 }
		return 1
	}
	local fn errstop(emsg){
		local buf=io.output()
		io.output(io.stderr)
		io.write(emsg)
		io.output(buf)
		os.exit(1)
	}
	// convert setting to ini
	local ini={}	//ini.a.name, ini.a.dfl=100, .tp="str", testf=nil こんなの
	for(i,darr in ipairs(tb) ){
		if(type(darr[1])!="string"||type(darr[2])!="string"||type(darr[4])!="string" ){
			errstop("ckopt ini err: [1][2][4] must be str: "..tostring(darr[1]).."\n")
		}
		if(string.sub(darr[2],1,1)!="-"||string.len(darr[2])!=2 ){
			errstop("ckopt ini err: optchar must be 2char start with hyphen, '-h' etc: "..tostring(darr[1]).."\n")
		}
		if( ! string.match ("int/bool/dbl/str", darr[4]) ){
			errstop("ckopt ini err: bad opttype (int/bool/dbl/str): "..tostring(darr[1]).."\n")
		}
		//name
		local char = string.sub(darr[2],2,2)
		local rg="[0-9a-zA-Z]"
		if( string.match(mode, "x") ){ rg=ctxx.fprintf(nil, [=[[\040-\054\056-\176]]=]) }
//		if( string.match(mode, "x") ){ rg=ctxx.printf([[]])"[\32-\44\46-\126]" }	// - ...45
		if( !string.match(char, rg) ){
			errstop("ckopt ini err: bad optchar: -"..char.."\n")
		}
		ini[char] = {}
		ini[char].name = darr[1]
		ini[char].dfl = darr[3]
		ini[char].tp = darr[4]
		
		//testcode
		local cmd="local function testfunc(raw) local s,n = raw.s, raw.n;do"
		for(i=5, #darr){
			local tp = type(darr[i] )
			if( tp !="string" ){
				errstop("ckopt ini err: testcode must be str: "..tostring(darr[1]).."\n")
			}
			cmd=cmd.."\n"..darr[i]
		}
		cmd=cmd.."\nend;return 1;end;return testfunc;"
		local fbuf;
		if(loadstring==nil){ fbuf= load(cmd, nil, "t", _G)() }
		if(loadstring!=nil){ fbuf= loadstring(cmd)() }
		ini[char].testf = fbuf
	}
	//parse_args
	local agleft={}
	local ex = string.match(mode, "x")
	local apos = 1
	local aposmax = #args
	local cpos = 2
	local optarg=""
	local emsg=nil

	while(1){
::lb_NEXT::
		optarg=""
		//[0]ck,  p@tget. optarg get	--, arg, argskip, -ab -c123
		if( args[apos] ==nil ){ break}
		if( string.len(args[apos]) <cpos ){ args[apos]=nil;apos=apos+1;cpos=2;goto lb_NEXT }
		if( args[apos]=="--"  ){ args[apos]=nil;break}
		if( string.sub(args[apos],1,2)=="--" || string.sub(args[apos], 1,1)!="-"){
			if( ex==nil){ break }	//longopt
			apos=apos+1;cpos=2;goto lb_NEXT
		}

		local optc = string.sub(args[apos], cpos, cpos)
		if( ini[optc] ==nil){ emsg="no such opt: -"..optc.."\n";break }
		if( ini[optc].tp == "bool"){ ini[optc].val=1;optarg="1";cpos=cpos+1;goto lb_TESTF }

		cpos=cpos+1
		optarg = string.sub(args[apos], cpos)
		args[apos]=nil
		apos=apos+1
		cpos=2
		if( optarg=="" ){
			optarg = args[apos]
			if(args[apos]==nil){ emsg="optarg not found: -"..optc.."\n";break }
			args[apos]=nil
			apos=apos+1
		}
		if(ini[optc].tp=="str"){ ini[optc].val=optarg }
		if(ini[optc].tp=="int"){
			local ck=test_i(optarg)
			if(ck==0){ emsg="optarg is not int: -"..optc..","..optarg.."\n";break }
			ini[optc].val=tonumber(optarg)
		}
		if(ini[optc].tp=="dbl"){
			local ck=test_d(optarg)
			if(ck==0){emsg="optarg is not dbl: -"..optc..","..optarg.."\n";break }
			ini[optc].val=tonumber(optarg)
		}
::lb_TESTF::
		ini[optc].cmdin=optarg
		local raw={s=optarg, n=tonumber(optarg), v=ini[optc].val}
		local rc, str = ini[optc].testf(raw)
		str=str||""
		if(!rc){ emsg="bad optarg: -"..optc..", "..optarg.." :"..str.."\n"; break }
		ini[optc].val=raw.v	//更新
		goto lb_NEXT
	}
	
	//rtn or errstop
	if(emsg){
		local rc = string.match(mode, "q")
		if(rc!=nil){ return nil, emsg }
		errstop(emsg)
	}
	//result
	local res={}
	for(optc,info in pairs(ini) ){
		res[info.name]=ini[optc].val||ini[optc].dfl
		res[info.name.."_cmdstr"]=ini[optc].cmdin
		res[info.name.."_dfl"]=ini[optc].dfl
	}
	local nargs={}
	for(i=1, aposmax ){
		if(args[i]!=nil ){ nargs[#nargs+1]=args[i] }
	}
	return res, nargs
}

//SH_TSS
fn u.addtest.t_ckopt(){
//	u.usage(u, "ckopt_usage")
//	os.exit(1)
	u.laptime(0)
	av={ "-h", "--abc", "-c9", "-d", "10.4", "--", "-v", "99" }
	ini={
	{"verbose",	"-v",	"0",	"bool" }
	,{"usage",	"-h",	"0",	"bool", "print('unko')" }
	,{"count",	"-c",	"1",	'int', "return (1<n and n<10),'invalid range'"}
	,{"delay",	"-d",	0.3,	"dbl" }
	,{"log", "-f",io.stderr,"str","local fh=io.open(s);raw.v=fh","return fh"}
	}

	local opt, agleft = u.ckopt("qx", ini, av)
	u.laptime()
	u.dbg(opt, agleft)
	
	u.laptime(0)
	local a,b=0,1
	for(i=1, 100000){ a=a+1 }
	u.laptime()
}
//SH_TSE

/*-*
@_name	ag2opt
@auther momi-g
@brief	getopt/getopts like args parser
@_synopsys	tbl,tbl ag2opt(str, tbl)
@_eg
	ag={ "-abzz", "-4", "23", ["dmy"]="ig"  }	--> key "dmy" is ignored.
	opts, args = ag2opt("av4:", ag)	--> opts={a=1,["4"]="23"}, args={"bzz"}
	opts, args = ag2opt("ab:", ag)	--> errstop. -4 is not set as opt.
	opts, args = ag2opt(":ab:", ag)	--> nil, emsg... silent mode.
@param str	optfmt. same as posix getopts. top ":" works as silent flg.
@param tbl	parse target. seq table with strvalue. ignores not seqkey.
@return 2 rtn. 1:opttbl. key is optchar.	2:left args. order is saved.
@details if opt is flgtype, rtndata is 1(num) or nil. this func checks all args
	except argend or "--". this is the only one deference from posix.
@_note	https://github.com/cheusov/lua-alt-getopt/issues (Aleksey Cheusov, MIT)
@_conforming lua5.1+, luajit2.0+
@version 1.0.4, 2020-05-08
-*/
fn ctxx.ag2opt(fmt, ag){
	local ferr=ctxx.errstop
	if(string.sub(fmt, 1,1) == ":" ){
		ferr= function(a, b) return a end
		fmt=string.sub(fmt, 2)
	}
	
	local buf, bbuf, pos = nil, nil, 1
	local subflg={}	//subargを持つか否か
	local agtbl={}
	local optbl={}

	//fmtck
	buf = string.find(fmt, "::")
	if(buf){  print(fmt, buf); ctxx.errstop("optfmt is 'a:bd' etc", 2) }
	buf = string.find(fmt, "[a-zA-Z0-9:]+")
	if(!buf){ ctxx.errstop("optfmt is 'a:bd' etc", 2) }
	if(type(ag)!="table"){ ctxx.errstop("2nd arg must be tbl: "..type(ag), 2) }
	
	while(pos<=#fmt){
		buf = string.match(fmt, "[a-zA-Z0-9:]", pos)
		if(buf==":" && bbuf!=nil){ subflg[bbuf]=1 }
		if(buf!=":"){ subflg[buf]=0 }
		bbuf=buf
		pos=pos+1
	}

	//切り取り型に変更した。
	pos=1
	while(pos<=#ag){
		if(type(ag[pos])!="string"){
			ctxx.errstop("argtbl must be string: "..pos.." "..type(ag[pos]), 2)
		}
		buf = string.match(ag[pos], "^-[a-zA-Z0-9].*" )
		if(buf!=nil) {
			local head=string.sub(buf,2,2)
			if(subflg[head] == nil) {
				local emsg=ferr("no such option: -"..head, 2)
				return nil, emsg
			}
			buf=string.sub(buf,2)
			while(0<#buf){
				head, buf = string.match(buf, "(.)(.*)")
				buf=buf||""
				if(subflg[head] ==0){ optbl[head]=1 }
				elif(subflg[head] ==1 && buf!=""){ optbl[head]=buf; buf="" }
				elif(subflg[head] ==1 && buf==""){
					pos=pos+1
					if( ag[pos]==nil){
						local emsg=ferr("arg lacks optarg: -"..buf, 2)
						return nil, emsg
					}
					optbl[head]=ag[pos]
				}
			}
			goto lbl_NEXT
		}
		if(ag[pos]=="--"){
			pos=pos+1
			for(i=pos, #ag){ agtbl[#agtbl+1]=ag[pos] }
			break
		}
		agtbl[#agtbl+1]=ag[pos]
		::lbl_NEXT::
		pos=pos+1
	}
	return optbl, agtbl
}

//SH_TSS
fn u.addtest.t_ag2opt(){
	local ag={}
	local optb, agtb=nil, nil
	
	ag={"-abc", "d", "123", "--", "-v"}
	optb, agtb = u.ag2opt( ":ab:f:v", ag)
	u.dbg(optb, agtb)

	optb, agtb = u.ag2opt( "a:b:f:v", ag)
	u.dbg(optb, agtb)

	optb, agtb = u.ag2opt( "a:b:f:", ag)
	u.dbg(optb, agtb)
	
	ag={"-abc", "d", "123", "000", "-v"}
	optb, agtb = u.ag2opt( ":a:b:f:", ag)
	u.dbg(optb, agtb)
	
	//optb, agtb = u.ag2opt( "a:b:f:", ag)
	u.dbg(optb, agtb)
}
//SH_TSE

/*-*
@_name	grepmcmtf,	grepmcmts
@auther momi-g
@brief	gather multiline cmt from luasrc.
@_synopsys	tbl grepmcmtf(str, num/nil)		/	tbl grepmcmts(str)
@_eg
	rtn = grepmcmtf("file.lua")		--> rtn = { "cmt1, "cmt2", "cmt3" }
	rtn = grepmcmtf("file.lua", 2048)	--set innerbuff size. dfl 1024.
	
	src = '--[[ cmt_11 ]]	print("hw");  --[==[cmt_22]==] --[[12 cmt]]'
	rtn = grepmcmts(src)		-->  { " cmt_11 ", "cmt_22", "12 cmt" }

@param str1	target obj. filename(mcmtf) or luacode string(mcmts).
@param num	grepmcmt() buffsize for read file at once.
@return 1rtn. seqtbl of cmtstr. rtn nil,emsg if err.
@_conforming lua5.1+, luajit2.0+
@version 1.0.0, y2020m04d09
-*/

//common
local
fn grepcmtc(str) {
	local r1=nil	//result mcmt string
	local r2=nil	// lft str 食べ残し。再代入に必要になる。
	while(1){
		local rtn, s1, e1 = ctxx.matchmcmt(str)
		local _, s2, e2 = ctxx.matchlit(str)
		local __, s3, e3 = ctxx.matchmlit(str)
		local ___, s4, e4 = ctxx.matchcmt(str)	//追加修正
		local min=nil;
		//ここが間違ってた。{} =={} は常にfalse. テーブルはポインタ参照評価だから。
		//代わりに遅延評価とラスト返却に書き換える。さらにlineコメントも追加しないとダメ。
		if( (s1||s2||s3||s4) == nil ){ r1=nil; r2=str; break }	//該当なし
		//どれかしらヒットしてる. s1でなくても
		
		if(s1==nil){s1=#str }
		if(s2==nil){s2=#str }
		if(s3==nil){s3=#str }
		if(s4==nil){s4=#str }
		min=math.min(s1,s2,s3,s4)
		
		//本命 minの関係と--単独とのレースで先に出る必要がある
		if(s1==min){
			if(e1==nil){r1=nil; r2=string.sub(str, s1); break}
			r1=rtn
			r2 = string.sub(str, e1+1)
			break
		}
		if(s2==min){
			if(e2==nil){r1=nil; r2=string.sub(str, s2); break}
			str = string.sub(str, e2+1)
			goto lbl_NEXT
		}
		if(s3==min){
			if(e3==nil){r1=nil; r2=string.sub(str, s3); break}
			str = string.sub(str, e3+1)
			goto lbl_NEXT
		}
		if(s4==min){
			if(e4==nil){r1=nil; r2=string.sub(str, s4); break}
			str = string.sub(str, e4+1)
			goto lbl_NEXT
		}
		::lbl_NEXT::
	}
	return r1, r2
}

fn ctxx.grepmcmts(str){
	local tbl={}
	while(1) {
		rtn, str = grepcmtc(str)
		if(rtn!=nil){tbl[#tbl+1]=rtn}
		if(rtn==nil){break}
	}
	return tbl
}

fn ctxx.grepmcmtf(fname, buffsize){
	local rtn, emsg = io.open(fname, "r")
	if(rtn==nil){return rtn, emsg}
	local sv=io.input()
	io.input(rtn)	//fhをstdinに設定する
	local tbl={}
	local str=""
	rtn=""
	while(1){
		local buf = io.read(buffsize) || ""
		str=str..buf
		rtn, str = grepcmtc(str)
		if(rtn==nil && buf==""){break}
		if(rtn!=nil){tbl[#tbl+1]=rtn}
	}
	io.input(sv)	//reset
	return tbl
}
//SH_TSS
fn u.addtest.t_grepmcmt() {
	local rtn=nil
	rtn = u.grepmcmtf("*SH_bn*.lua", 1024)
	u.dbg(rtn)
	rtn = '--[[ cmt_11 ]]	print("hw");  --[==[cmt_22]==] --[[12 cmt]]'
	rtn = u.grepmcmts(rtn)
	u.dbg(rtn)
}
//SH_TSE

/*-*
@_name	lua_gazcmt
@auther momi-g
@brief	grep, filter, sort multiline comment from luasrc
@_synopsys	tb lua_gazcmt(func/str1/nil [, str2/false, ...])
@_eg
	--mydoc.lua
	local function myf() print("hw") end	
	--[[todo abc]]
	--[[doc abc]]
	--[[debug_msg ]]
	
	--run
	tb = lua_gazcmt("mylua.lua") --> {"todo abc", "doc abc", "debug_msg",  }
	lua_gazcmt(myf) --> search myf() srcfile and run. same as above.
	lua_gazcmt(nil) --> conv to current file, (debug.getinfo(1).source).
	lua_gazcmt(myf,"[d][o]") --> {"todo abc", "doc abc" } ..luaptn filter
	
	lua_gazcmt(myf,"abc$","[d]o" ) --> {"todo abc", "doc abc" }  ..multiple filter
	lua_gazcmt(myf,"abc","do") --> {"doc abc","todo abc"} ..sort. see below.
	lua_gazcmt(myf,"abc",false,"doc" ) --> { "todo abc" } ..abc && (!doc)

@param str1/func/nil	srcobj. filename / func_srcfile / current_file.
@param str2/false...	luaptn for filtering. 'false' remove next arg ptn.
 if last ptn is [a-Z0-9_]+ and out of 'false' effect, it is used as rtntbl
 sortkey. sort rule is,
 1- exact word match ("abc" / "()abc[]," / "a+abc.zz" etc)
 2- forward match ("abcde" / ",abcord"  etc. ignore symbols)
 3- patical match ("cabc" / "fabcompany" etc)
 ..then younger wordpos cmt comes first.

@return	1rtn. rtn cmttb. rtn {} if nohit. rtn nil,emsg if err.
@_conforming lua5.1+, luajit2.0+
@version 2.0.0, 2021-06-18
-*/

//ファクトリ. 単語を受け取って最速位置で発見したとき、exact,forward, partial
//の優先順位で、かぶったときだけposが強い方が勝利。辞書方式。
local
fn wordsort(word){
	local p1 = "[^a-zA-Z0-9_]"..word.."[^a-zA-Z0-9_]"
	local p2 = "[^a-zA-Z0-9_]"..word
	local p3 = word
	local tbl = {p1, p2, p3}
	
	local fn rtn(bf, af){
		//事前にwordは含有済
		for( k,v in ipairs(tbl) ){
			local s = string.find(bf, v)
			local ss = string.find(af, v)
			if(s!=nil && ss==nil){return true}	//bfが無いってことはそれ以上の格
			if(s!=nil && ss!=nil && s<ss){return true}
			if(s==nil && ss!=nil){return nil}	//bfが無いってことはそれ以上の格
			if(s!=nil && ss!=nil && ss<s){return nil}
			//if(s==ss) { return true }	//区別不能.次の単語でソート 
		}
		return true	//全滅。一致処理は上流で始末する.stsort()を使う
	}
	return rtn
}

fn ctxx.lua_gazcmt(src, ...){
	local rtn, emsg = ctxx.cktp("fsN, bs...")
	if(rtn==nil){ ctxx.errstop(emsg, 2) }
	
	//vaからpとnを分ける
	local ptbl = {}
	local ntbl = {}
	local tbl = {...}
	if(#tbl==0){tbl={""}}
	local lastptn=nil
	local flg=0
	local buf=nil
	local buffsize=1024
	for(k,v in ipairs(tbl) ){
		if( !ctxx.istypes(v, "sb") || v==true || flg == -1 && type(v)!="string" ){
			flg=-1;break
		}
		if( v==false ){ flg= -1 }
		elif(flg<0){ ntbl[1+#ntbl]=v; flg=0}
		elif( type(v)=="string" ){ ptbl[1+#ptbl]=v; flg=1}
	}
	if(flg == -1){ ctxx.errstop("3rd- arg is invalid type: "..ctxx.vinfo(v) ) }
	lastptn=ptbl[#ptbl]
	if(flg==0){ lastptn=ntbl[#ntbl] }
	
	//一覧取得
	if(type(src)=="function" || src==nil){
		src=src || 2	//nil追加。使われているソースファイル名を自動取得
		local buf=debug.getinfo(src).source
		local ck = string.sub(buf, 1, 1)
		if(ck == "@"){ src = string.sub(buf, 2) }
		elif(ck == "="){
			buf = string.sub(buf, 2)
			return nil, "failed to get luasrc. C-API?: "..buf
		}
		rtn, emsg = ctxx.grepmcmtf(src, buffsize)
		if( rtn==nil ){ return nil, emsg }
	}
	elif( type(src) == "string" ) {
		rtn, emsg = ctxx.grepmcmtf(src, buffsize)
		if(rtn==nil){return nil, emsg}
	}
	//ptblを集める
	tbl={}
	for(k,v in ipairs(rtn) ){ 
		local ck=nil
		for(kk,vv in ipairs(ptbl) ){
			ck = string.find(v, vv)
			if(!ck){break}
		}
		if(ck){ tbl[1+#tbl]=v }
	}
	rtn=tbl
	
	//ntblを差っ引く
	tbl={}
	for(k,v in ipairs(rtn) ){
		ck=nil
		for(kk,vv in ipairs(ntbl) ){
			ck = string.find(v, vv)
			if(ck){break}
		}
		if(ck==nil){ tbl[1+#tbl]=v }
	}
	rtn=tbl
	//ラストptnでtblかソート単体か
	buf=string.find(lastptn, "^[a-zA-Z0-9_]+$")	// ラストptnがpでexact系
	if(flg==1 && buf !=nil){
		local comp=wordsort(buf)
		ctxx.stsort(rtn, comp)
	}
	return rtn
}

//SH_TSS
fn u.addtest.t_lua_gazcmt() {
	_debug=1
	local rtn = u.lua_gazcmt("*SH_bn*.lua", "^[*]", "[o]pt", false, "ag2")
	u.dbg(rtn)
	rtn = u.lua_gazcmt("*SH_bn*.lua")
	u.dbg(rtn)
}
//SH_TSE


/*-*
@_name	srcinfo, ffsrcinfo
@auther momi-g
@brief	disp lua srccode doccmt list
@_synopsys
 str srcinfo(tbl1, str, tbl2)
 str ffsrcinfo(fh, func/filename, tbl1, str, tbl2)
 ..all args allows nil. tbl={}, str="", fh: noout, func/file: self file 
@_eg
--abc.lua
	function myf() return 123 end
	--[[dc(\n)	@name myf(\n)	@param	some msg1(\n) ]]
	--[==[dc(\n)	@funcname myf2(\n)	]==]
	--[[ todo myname_abc ]]
		
	rtn = srcinfo( {"^dc", "[m][y]"}, "name[^\n]*\n", _G.arg )
	
	-- ~$ lua abc.lua		#>> @name myf , @name myf2
	-- ~$ lua abc.lua myf2	#>> disp myf2 cmtstr if hit only 1 cmt
	-- ~$ lua abc.lua my	#>> if multiple cmt hit, disp list
	-- ~$ lua abc.lua func my	#>> search including both word cmt. (AND search)
	-- ~$ lua abc.lua my 1	#>> if last arg is num, select cmt from list.

	ffsrcinfo( io.stderr, "xyz.lua", ... )	--> targetting other file doccmt
	ffsrcinfo()	--> ffsrcinfo( io.stdout, "now.lua",  {}, "", {} )
	ffsrcinfo( nil, myf, ... )	--> search myf() srcfile and use. nodisp.

@param fh output file handle. strinfo() is set to io.stdout. noout if nil.
@param func/filename cmtsrc. search srcfile if func. use as filename if str.
@param tbl1 filter for def doc(luaptn). gather multiline cmt satisfying allptn.
 javadoc: {"^[*]","param","auth"} --> start with '*' and includes param + auth
 other: {"api$",false,"p[o]em",false,"egg"} --> contains neither poem nor egg
@param	str	title ptn of cmt. uses 1st hit str. used for disp cmtlist. 
@param	tbl2	userin ptntbl. if last word is num, select cmt. see above eg.
@return	str	result msg. same as disp msgstr. rtn nil,emsg if err/fail.
@details userin search word isnt only applyed to titlestr but all cmtstr.
@_conforming lua5.1+, luajit2.0+
@version 1.0.0, y2020m04d09
-*/

fn ctxx.srcinfo(ctbl,lptn,tbl){ return ctxx.ffsrcinfo(io.stdout, nil, ctbl, lptn, tbl) }
fn ctxx.ffsrcinfo(fh, src, ctbl, lptn, argtbl){
	local rtn, emsg = ctxx.cktp("A,fsN,tN,sN,tN")
	if(rtn==nil){ ctxx.errstop(emsg, 2) }
	if( fh!=nil && io.type(fh) != "file" ) {
		ctxx.errstop("invalid filehandle: "..ctxx.vinfo(fh) )
	}
	//nil support
	fh=fh||io.stdout
	src=src||debug.getinfo(2, "f").func
	ctbl=ctbl||{}
	lptn=lptn||""
	argtbl=argtbl||{}

	local lnum = tonumber( argtbl[#argtbl] )
	if(lnum!=nil){ argtbl[#argtbl] = nil }
	for(k,v in ipairs(argtbl) ) { if(v!=nil){ ctbl[1+#ctbl]=v }	}
	local rtn, emsg = ctxx.lua_gazcmt(src, ctxx.tb2va(ctbl) )

	if(rtn==nil){ return rtn, emsg }
	if(#rtn==0){ rtn = "no doccmt exists: "..src }
	elif(#rtn != 1 && lnum==nil){
		local buf=""
		for(k,v in ipairs(rtn) ){
			local lbuf = string.match(v, lptn)||""
			buf=buf..lbuf
		}
		rtn=buf
	}
	elif(1){
		lnum=lnum||1
		rtn[lnum]=rtn[lnum]||"nodoc or select num is erange"
		rtn=rtn[lnum].."\n"
	}
	ctxx.fprintf(fh, "%s\n", rtn)
	return rtn
}

//SH_TSS
fn u.addtest.t_srcinfo() {
	_debug=1
	u.ffsrcinfo(io.stderr,u.ffsrcinfo,{"^[*]"},"@.?name[^\n]*\n", {})
	u.ffsrcinfo(io.stderr, print,{"^[*]"},"@.?name[^\n]*\n", {})
}
//SH_TSE


/*-*
@_name	srcpath, srcdir, srcdirsep, srcname, srcnametop, srcnamesuf 
@auther momi-g
@brief	portable path getter like dirname/$0/arg[0] etc, uses debug.getinfo().
@_synopsys
str srcpath(nil/num),	str srcdir(nil/num),	str srcdirsep(nil/num)
str srcname(nil/num),	str srcnametop(nil/num),	str srcnamesuf(nil/num)	
@_eg
	-- abc.lua...
	m = require("modname")
	src =	m.srcpath()		--> ./dir/abc.lua,	 .\win\abc.lua	etc
	dname =	m.srcdir()		--> ./dir/,	 .\win\		...rtn str with dirsep end.
	sep = m.srcdirsep()		--> \ or /, win or notwin.
	name=	m.srcname()		--> abc.lua
	top =	m.srcnametop()	--> abc	...top .shortest match until dotchar ".".
	suf =	m.srcnamesuf()	--> .lua, .tar.gz	...suffixes. rest of topname.	
	src = dname .. name	--> ./dir/abc.lua
	
	buf = srcpath(1)	-->	exit if failed to get fname(used in loadstring() etc)
	rtn,emsg = srcpath()	-->	nil,emsg if failed to get fname

@param num nil/1. errmode switch. nil:rtn nil,emsg		1:os.exit(1)
@return	str	result path/dir/sep str. rtn nil,emsg if param = nil. 
@details	srcdir() rtns not blank str. (filename aaa.lua -> dir == ./ etc) 
@_conforming lua5.1+, luajit2.0+
@version 1.0.1, y2020m07d16
-*/

local
fn errfunc(mode, msg){
	if(mode!=nil){
		ctxx.fperr(io.stderr, msg, 2)
		os.exit(1)
	}
	local rtn = ctxx.fperr(nil, msg, 2)
	return nil, rtn
}
fn ctxx.srcpath(mode){
	local buf = debug.getinfo(2, "S").source
	local ck = string.match(buf, ".")
	if(ck != "@"){
		return errfunc(mode, "failed to get filename, srcchar: "..ck)
	}
	return string.sub(buf, 2)
}
fn ctxx.srcdirsep(mode){
	local sep1 = string.find (package.path, "/", 1, true)||0
	local sep2 = string.find (package.path, "\\", 1, true)||0
	if(sep1==0 && sep2==0 ){
		return errfunc(mode, "failed to get dirsep from package.path, LUA_PATH='(blank)' ? ")
	}
	local sep = "/"
	if(sep1<sep2){ sep="\\" }
	return sep
}
fn ctxx.srcdir(mode){
	local base, emsg = ctxx.srcpath(mode)
	if(base==nil){return nil, emsg}
	
	local sep = ctxx.srcdirsep(mode)
	if(sep==nil){return nil, emsg}
	
	local pos=0
	for(i=1, #base, 1){
		local buf = string.sub(base, i, i)
		if(buf==sep){pos=i}
	}
	return string.sub(base, 1, pos)
}

fn ctxx.srcname(mode){
	local base, emsg = ctxx.srcpath(mode)
	if(base==nil){return nil, emsg}
	
	local sep = ctxx.srcdirsep(mode)
	if(sep==nil){return nil, emsg}
	
	local pos=1
	for(i=1, #base, 1){
		local buf = string.sub(base, i, i)
		if(buf==sep){pos=i+1}
	}
	return string.sub(base, pos)
}
fn ctxx.srcnametop(mode){
	local base, emsg = ctxx.srcname(mode)
	if(base==nil){return nil, emsg}
	return string.match (base, "[^%.]*")
}
fn ctxx.srcnamesuf(mode){
	local base, emsg = ctxx.srcname(mode)
	if(base==nil){return nil, emsg}
	return string.match (base, "%..*")
}

//SH_TSS
fn u.addtest.t_srcpath() {
	print(u.srcpath() )
	print(u.srcdir() )
	
	print(u.srcdirsep() )
	u.test_eq(u.srcdirsep(), "/", "sep test" )
	print(u.srcname() )
	u.test_eq(u.srcname(), "util.lua", "sep name" )
	print(u.srcnametop() )
	u.test_eq(u.srcnametop(), "util", "sep topname")
	print(u.srcnamesuf() )
	u.test_eq(u.srcnamesuf(), ".lua", "sep sufname" )
	
	cmd="local u = require('util'); local rtn, emsg = u.srcpath(); return rtn, emsg "
	local rf = u.loadstring(cmd)
	local rtn, emsg = rf()
	print("failtest- ", rtn, emsg)
	u.test_eq(rtn, nil, "cmdstring_test" )
}
//SH_TSE

/*-*
@_name	toint
@auther momi-g
@brief	conv num to int+decimals, 1.01 >> 1, 0.01  -2.9 >> -2, -0.9	
@_synopsys	num,num toint(num)	
@_eg
	num = -10.12
	print( toint(num) )	-->		-10,	-0.12
@param num number. not allow string.
@return	num. os.exit(1) if err. 
@_conforming lua5.1+, luajit2.0+
@version 1.0.0, y2020m07d01
-*/
ctxx.toint = math.modf

/*-*
@_name	run_test, frun_test, eq/neq, add_test(not func)
@auther momi-g
@brief	lutest, simple unittest tools.
@_synopsys	
	eq(...) / neq(...)	...see eg about assert func.
	n1,n2,msg run_lutest(tbl/nil)
	n1,n2,msg frun_lutest(fh/nil, tbl/nil)
@_eg
	lut=require("util").lutest
	lut.add_test.abc_myf()	--> 'add_test' is lutest inner table.
		lut.eq( 1==1 )	--> suc,	true/false test if argc==1
		lut.eq( 1,1  )	--> suc.	compare ag1/ag2 if argc==2
		lut.eq( 1,2, "my errmsg" )	--> fail.	ag3 == failed msg
		lut.neq( 1,1 )	--> fail, not equal.
	end
	lut.add_test.zzz()
		lut.neq( 10,20 )
	end
	
	fail,all,msg = lut.run_test()	--> test all suite. disp report to stderr.
	fail,all,msg = lut.run_test({"zzz"}) --> test only select suite (str seqtbl)
	fail,all,msg = lut.frun_test(io.stdout) --> change fh. noout if nil.

@param n1	num. count failed assert.
@param n2	num. count all assert.
@param msg	str. test result report.
@details
	run_test() is testsuite runner. test select suite if arg is str seqtbl.
	rtn failcnt, allcnt, report. frun_test() changes outfh. noout if fh is nil.
@_conforming lua5.1+, luajit2.0+
@version 1.1.0, 2020-11-10
-*/

ctxx.lutest={}
ctxx.lutest.dispflg=1
ctxx.lutest.testsuc=0
ctxx.lutest.testfail=0
ctxx.lutest.add_test={}
ctxx.lutest.rmsg=""

local 
fn testeq_common(msg, n, a1, a2, ck) {
	local info=""
	if(n==1){
		//agが一つだけinfoを変える
		if(ck==true){info="eq_test"}
		if(ck==false){info="neq_test"; a1=!a1}
	//	a1= (a1== ck)
	}
	if(n==2){
		local buf=""
		if(ck==true){buf=" == "}
		if(ck==false){buf=" != "}
		info=ctxx.sprintf("%s:%s"..buf.."%s:%s",tostring(a1),(ctxx.istypes(a1))
		, tostring(a2), (ctxx.istypes(a2)) )
		a1 = ((a1 == a2) == ck)
	}
	if(msg!=nil){msg=" ("..tostring(msg)..")"}
	msg=msg||""
	
	local rmsg=""
	if(a1){
		ctxx.lutest.testsuc=ctxx.lutest.testsuc+1
		if(ctxx.lutest.dispflg==2){ rmsg = "test suc: "..info..msg }
	}
	else {
		ctxx.lutest.testfail=ctxx.lutest.testfail+1
		if(ctxx.lutest.dispflg!=0){ rmsg = "test FAIL: "..info..msg }
	}
		//callソースが狂うので		ctxx.lutest.nowfunc = k;	を使って表示する
	local buf = ctxx.getlovars(3, "k")
	buf = ctxx.fprintf(nil, "%s %s: %s(): %s\n", ctxx.S_FILE(2), ctxx.S_LINE(2), buf, rmsg )
	if(rmsg!=""){ ctxx.lutest.rmsg = ctxx.lutest.rmsg .. buf }
	if(!a1 && ctxx.lutest.dispflg!=0
		|| a1 && ctxx.lutest.dispflg==2	){
		local fh = ctxx.getlovars(3, "fh")
		ctxx.fprintf(fh, "%s", buf)
	}
//suc,failでres方式を変える
	if(a1){	return buf}
	return nil, buf
}

local
fn stoptest(str, n){
	local buf = ctxx.getlovars(4, "k").."(): test argc allows "..str..": "..n
	error(buf, 3)
}
fn ctxx.lutest.eq(...) {
	local ac = select("#", ...)
	local a1,a2, msg = select(1, ...)
	if(3<ac){ stoptest("1:expr / 2:a,b / 3:a,b,msg", ac) }
	if(1<ac){ ac=2 }
	return testeq_common(msg, ac, a1, a2, true)
}
fn ctxx.lutest.neq(...) {
	local ac = select("#", ...)
	local a1,a2, msg = select(1, ...)
	if(3<ac){ stoptest("1:expr / 2:a,b / 3:a,b,msg", ac) }
	if(1<ac){ ac=2 }
	return testeq_common(msg, ac, a1, a2, false)
}

fn ctxx.lutest.run_test(flist, flg) {
	return ctxx.lutest.frun_test(io.stderr, flist, flg)
}
fn ctxx.lutest.frun_test(fh, flist, flg) {
	if( fh!=nil && io.type(fh)==nil ){ ctxx.errstop("1st arg is invaild file handle") }
	if( ctxx.istypes(flist, "Nt") == nil ){ ctxx.errstop("3nd arg must be nil/tbl") }
	if( ctxx.istypes(flg, "Nn") == nil ){ ctxx.errstop("3rd arg must be nil/num") }
	//set verbosemode
	if(flg==nil||flg==1){ctxx.lutest.dispflg=1}
	elif(flg==0){ctxx.lutest.dispflg=0}
	elif(flg==2){ctxx.lutest.dispflg=2}
	
	local tlist={}
	ctxx.lutest.testsuc=0 
	ctxx.lutest.testfail=0
	ctxx.lutest.rmsg=""
	
	local fsq=ctxx.tblconv(flist, "seqn:s")	//シーケンスのみ集める
	local emsg=""
	if( flist != nil && type(flist) != "table"){
		ctxx.errstop("err. demands noarg or one seqtbl {'ts_f1', 'ts_f2' ...}")
	}
	if(flist==nil||#fsq==0){ tlist=ctxx.lutest.add_test }
	elif(1){
// grep
		tlist = ctxx.tsgrep(ctxx.addtest, fsq)
		if( ctxx.tsize(tlist) != ctxx.tsize(fsq) ) {
			local bad = ctxx.ttrm(fsq, tlist)
			ctxx.errstop("testfunc not found. typo?\n"..ctxx.tinfo(bad))
		}
	}
	for( k,v in pairs(tlist) ) { v() }
	local fnum = ctxx.lutest.testfail
	local anum = ctxx.lutest.testsuc + ctxx.lutest.testfail
	local res = ctxx.fprintf(nil, "fail/run: %d/%d\n", fnum, anum )
	res = "--test summary:\n"..ctxx.lutest.rmsg..res
	ctxx.fprintf(fh, "%s", res)
	return fnum, anum, res
}

ctxx.addtest = ctxx.lutest.add_test
ctxx.test_eq = ctxx.lutest.eq
ctxx.test_neq = ctxx.lutest.neq
ctxx.run_lutest = ctxx.lutest.run_test
ctxx.frun_lutest = ctxx.lutest.frun_test

//SH_TSS
u.run_lutest(_G.arg)
//SH_TSE

//called_as_main
if(ctxx.ismain() ){
	ctxx.srcinfo( {"^[*]"}, "@.?name[^\n]*\n", _G.arg )
	print("\ndocs. ~$ lua abc.lua 'hw' #>> find doc holding 'hw', show list if many")
	print("select list. ~$ lua abc.lua 'alice' 3	#>> disp 3rd doc of list")
	print("show api ~$ lua xyz.lua 'myf' 'mike$'	#>> search doc holding all luaptn")
//	ctxx.sleep(10)
	return
//	os.exit(0)
}
//call_as_req
return ctxx

/*
 change log
 --
2021-06-27  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (lutest): add dispflg, 0,1,2 + fix dispmsg
	
	* util.sh.lua (printf): add \uU esc, fix test

2021-06-18  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (luadoc_orange): reanme+fix lua_gazcmt
	
	* util.sh.lua (brp.sh): rewrite build script

2021-05-17  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (ismain): fix, add debug.getinfo(n) is nil or tb check

2021-03-18  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (fprintf, flwrite): fix io.output() bug/code

2021-03-18  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (ckopt): add argtype test

2021-03-03  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (laptime): fix disp msg, real >> sys.

2021-01-31  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (ismain): debug, use debug.getinfo().what

2020-12-12  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (ftinfo): fix keyname disp style, "123" 0x11 >> 123 :0x11

2020-12-10  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (clit): support all c-posix esc. \ooo, \xFF, \[abntvrf?\'"]

2020-11-25  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (tbdump): new

	* util.sh.lua (laptime): add msg input api

2020-11-20  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (ftinfo): rewrite. code simplify. api isnt change.

2020-11-10  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (lutest): omit some assert func.  v1.1.0.

2020-11-08  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (ckopt): change ini fmt. ckopt v1.1.0.

	* util.sh.lua (ismwd): independent from util family, direct paste style

2020-09-07  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (tinfosub): change disp fmt, aligned (lptb) indent

2020-09-05  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (strconv): add u8conv func, othername
	* (strconv): omit UPPERchar from unicode out

2020-08-22  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (testeq_common): fix true/false logic, a1=(a1==a2) -> omit

2020-07-29  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (tbl2va): func omit. use tb2va.
	* (tb2va): new func. unpack/table.unpack wrapper.

2020-07-17  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (tbconv): add newname api, tblconv -> tbconv
	
	* util.sh.lua (tinfo): change inner msg, lptbl -> lptb

2020-07-16  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (tinfo): fix no exist func tinfosub() bug
	
	* util.sh.lua (S_FUNC): fix overlv err
	
	* util.sh.lua (srcnametop): change funcname, srctop -> srcnametop
	* (srcnamesuf): same
	
	* util.sh.lua (ismain): fix overlv bug
	
	* util.sh.lua (astmwd): omit. use assert( ismwd() ) 
	* (ismwd): improve rtn false -> false, emsg 

2020-07-13  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (istypes): fix/add out of basic type info "_"

2020-07-07  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (ismwd, astmwd): make new func

2020-07-04  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (tinfo): change dflout nil --> io.stderr
	
	* util.sh.lua (tinfosub): fix str concat ..k.. to ..tostring(k).. 

2020-06-24  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (srcpath): add newfuncs, srcpath() family
	
	* util.sh.lua (S_FILE): fix apidoc typo

2020-06-19  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (tinfo): add newfunc, ftinfo()

2020-05-06  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (ckopt): change 1st arg as setting inistr

2020-05-05  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (ag2opt): fix -abc separate bug

2020-05-01  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (fperr): fix errmsg '%s' pfmt bug

2020-04-26  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (srcinfo): fix nohit msg, erange list select
	
	* util.sh.lua (ckopt): debug dflini reader, add changed flg tbl opt[0]
	
	* util.sh.lua (ag2opt): debug errrtn, stop or msg

2020-04-07  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (*many*): fix/refacter/debug

2020-04-05  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (istypes): add 'i'nteger typeck
	
	* util.sh.lua (tbl2va): fix invalid tail return at loadstring

2020-04-04  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (luadoc_orange): enable nil arg to luaptn
	* (grepmcmt): update argtype check
	
	* util.sh.lua (tblconv): fix seqn,indn work

2020-04-04  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua : v1.0.0 releases.
*/
//SH_ED

//SH_OP _ a=`sed -ne "/${C}DF/!d;:l;n;/${C}DE/q;p;bl"<$R0`;eval "$a"	#*/
//SH_OP	h echo "-tsb:test/smpl/bld -LMP:leak,mem,prof"

//SH_OP t $e"$CW";$p"cp $tf main.lua; ./bootlj $*"|fv
//SH_OP L $p"valgrind --leak-check=full ./bootlj $@ 2>&1|sed -e '/SUMMA/!d;n;n;n;n'"|fv
//SH_OP M $p"fM ./bootlj $*"|fv	 #*/
//SH_OP P $p"valgrind --tool=callgrind --trace-children=yes --callgrind-out-file=log.out ./bootlj $@;kcachegrind log.out"|fv	 #*/

//SH_OP s $e"$CW";fgr0 "${C}SMP" "${C}SMPE"<$Rm|$e"$Cp">main.lua;$p"./bootlj $*"|fv
//SH_OP b $e"$CW";$p"rm $Rm $tf"|fv
//SH_OP p nped
//SH_OP k echo 'lsk $R0'|fv

//SH_OP 8 fgR "${C}TSS" "${C}TSE"<$Rm|$e"$Cp">$Rs;luajit -b $Rs $Rh #sc, hd
//SH_OP 9 fgr0 "${C}TSS" "${C}TSE"<$Rm|$e"$Cp">$tf	#ts
//SH_OP W $e"$Cw">/dev/null;fbn<$Rs>$Rm;$e"$C9$O$C8";echo "$Rh $Rm $Rs $tf"
//SH_OP o $e"$Cb"

/*SH_DF

#-- vars
bn=`basename ${Rs%.*}`; tf=${Rs%/*}/${bn}.ts.${Rs##*.}; e="eval "; p="$Rp"
#-- mod
fv()(while read -r a;do $e"cat<<E$O# $a${O}E"|sed -e 's@-L.*-L[^ ]*@-L(omit)@g'>/dev/stderr;$e"$a";done)

fbn()(sed -e "s@\*${C##*]}bn\*@$bn@g"|frf)
fsn()(tr -s ' \t' '\n')
fsl()(tr -s '\n' ' ')
fu()(fsn|sort -u)
fU()(fu|fsl;$p)

fgr()(sed -e "/$1/!d;:l;/$2/{p;d};n;bl")	#切出
fgr0()(sed -ne "/$1/!d;:l;n;/$2/d;p;bl")	#抜き切出
fgR()(sed -ne "/$1/bl;p;d;:l;n;/$2/d;bl")	#切すて
fg()(sed -ne "s/.*${C##*]}co\*\([^*]*\).*$/\1/p" "$@"|fsn|awk '!a[$0]{a[$0]=1;print}'|fsl)

#--cmd
frf()(
 awk -v r="${C##*]}rf" 'match($0,r){
 s=substr($0, RSTART+RLENGTH+1)
 gsub(/.[^*]*$/, "", s);split(s, a)
 m="[ -f %s ]&&echo \"/*--copyfrom %s*\"/&&cat %s&&echo \"/*--copyend %s*\"/"
 for(i=1;v=a[i];i++){ system( sprintf(m, v,v,v,v))}
 next
 }
 {print}'
)

fM()(valgrind -q --tool=massif --massif-out-file=./vmem.buf --stacks=yes \
--trace-children=yes $@>/dev/null
ms_print ./vmem.buf|sed -ne '/[KMG]B/bl;d;:l;/snap/q;p;n;bl';rm ./vmem.buf)
i0=$e'f0 "^#ifdef TEST" "^#endif"<$Rm|$cn>$tf'
i1=$e'f2 "^#ifdef TEST" "^#endif"<$Rs|$cn>$Rm;mv $Rm $Rs'
i4=$e'sed -ne "p;/_RUN/bl;d;:l;/);/{c\\$O $1);$O p;d};n;bl"<$tf>$Rm;mv $Rm $tf'

/*SH_DE*/
