--[[ 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/>.
--]]


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
]=]

function ctxx.tdup(t1, t2) 
	if t2==nil then  t2={}  end 
	if type(t1) ~="table"  or  type(t2) ~= "table" then 
		ctxx.errstop("(ag1,ag2) must be (tb,tb/nil): "..ctxx.vinfo(t1,t2), 2)
	 end 
	t2 = ctxx.tdupsub(t1,{})
	return t2
 end
function ctxx.tdupsub(tb, loopck) 
-- copied from penlight lib.
-- https://github.com/Tieske/Penlight
	if type(tb)~="table" then return tb end 
	if loopck[tb] then return loopck[tb]  end 
	--loopckはアドレスstrをkeyにしてloopを管理
	local mt = getmetatable(tb)
 	local res = {}	--for use pointer. dmytbl.
	loopck[tb]=res
 	for k,v in pairs(tb)  do do 
		k=ctxx.tdupsub(k, loopck)
		v=ctxx.tdupsub(v, loopck)
		res[k]=v
	 end::_luka_LOOPNEXT::end 
	setmetatable(res,mt)
	return res
 end


--[=[*
@_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
]=]
function ctxx.eqv(t1,t2)  return ctxx.tcompsub(t1,t2, {})  end
function ctxx.tcompsub(t1,t2, loopck) 
	if loopck[t1]  and  loopck[t1][t2]  then return true end 
	local ty1 = type(t1)
	local ty2 = type(t2)
	if ty1 ~= ty2 then return false end 
--non-table types can be directly compared
	if ty1 ~= 'table' then  return t1 == t2  end 
--as well as tables which have the metamethod __eq
	local mt = getmetatable(t1)
	for k1 in pairs(t1)  do do  if t2[k1]==nil then return false end   end::_luka_LOOPNEXT::end 
	for k2 in pairs(t2)  do do  if t1[k2]==nil then return false end   end::_luka_LOOPNEXT::end 
	loopck[t1] = loopck[t1]  or  {}
	loopck[t1][t2] = true
	for k1,v1 in pairs(t1) do do 
		local v2 = t2[k1]
		if  not  ctxx.tcompsub(v1,v2,loopck)  then return false end 
	 end::_luka_LOOPNEXT::end 
	return true
 end


--[=[*
@_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 function cmp_multitype(op1, op2) 
	local type1, type2 = type(op1), type(op2)
	if type1 ~= type2 then  return type1 < type2  
	elseif type1 == "number"  or  type1 == "string" then  return op1 < op2  
	elseif type1 == "boolean" then  return op1 == true  
	else  return tostring(op1) < tostring(op2)  end 
 end
local function __genOrderedIndex( t ) 
	local orderedIndex = {}
	for  key in pairs(t)  do do  table.insert( orderedIndex, key )  end::_luka_LOOPNEXT::end 
	table.sort( orderedIndex, cmp_multitype )
	return orderedIndex
 end
local function orderedNext(t, state) 
	local key = nil
	if state == nil then 
		t.__orderedIndex = __genOrderedIndex( t )
		key = t.__orderedIndex[1]
	 else 
		for i = 1,table.getn(t.__orderedIndex)  do do 
			if t.__orderedIndex[i] == state then  key = t.__orderedIndex[i+1]  end 
		 end::_luka_LOOPNEXT::end 
	 end 
	if key then  return key, t[key]  end 
	t.__orderedIndex = nil
	return
 end
function ctxx.sopairs(t) return orderedNext, t, nil  end

--[=[*
@_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
]=]
function ctxx.tsize(t) 
	if type(t)~="table"  and  t~=nil then  return nil, ctxx.fperr(nil, "bad argtype: "..ctxx.vinfo(t)) end 
	t=t or {}
	local i = 0
	for k in pairs(t) do do  i=i+1  end::_luka_LOOPNEXT::end 
	return i
 end

--[=[*
@_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
function ctxx.loadstring(cmd, name) 
	if loadstring==nil then  return load(cmd, name, "t", _G)  end 
	return loadstring(cmd, name)
 end

--[=[*
@_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
]=]
function ctxx.flwrite(tgt, str) 
	local rc, emsg, cflg=nil, nil, nil
	if type(tgt)=="string" then  cflg=1; tgt, emsg = io.open(tgt, "wb")  end 
	if io.type(tgt)~="file"  then 
		return nil, emsg or ctxx.fperr(nil, "invalid 1st arg file/fh: "..ctxx.vinfo(tgt), 2)
	 end 
	str=str or ""
	if type(str)~="string"  then 
		return nil, ctxx.fperr(nil, "2nd arg allows only string/nil: "..ctxx.vinfo(str), 2)
	 end 
	rc, emsg = tgt.write(tgt, str)
	tgt.flush(tgt)
	if emsg then  return nil, emsg  end 
	if cflg then  tgt.close(tgt)  end 
	return str
 end
function ctxx.flwrite_add(tgt, str) 
	local rc, emsg, cflg=nil, nil, nil
	if type(tgt)=="string" then  tgt, emsg = io.open(tgt, "ab"); cflg=1  end 
	if io.type(tgt)~="file"  then 
		return nil, emsg or ctxx.fperr(nil, "invalid 1st arg file/fh: "..ctxx.vinfo(tgt), 2)
	 end 
	str=str or ""
	if type(str)~="string"  then 
		return nil, ctxx.fperr(nil, "2nd arg allows only string/nil: "..ctxx.vinfo(str), 2)
	 end 
	rc, emsg = tgt.write(tgt, str)
	tgt.flush(tgt)
	if emsg then  return nil, emsg  end 
	if cflg then  tgt.close(tgt)  end 
	return str
 end

function ctxx.flread(tgt) 
	local str, emsg, cflg=nil, nil, nil
	if type(tgt)=="string" then  cflg=1; tgt, emsg = io.open(tgt, "rb")  end 
	if io.type(tgt)~="file" then 
		return nil, emsg or ctxx.fperr(nil, "invalid 1st arg file/fh: "..ctxx.vinfo(tgt), 2)
	 end 
	str, emsg = tgt.read(tgt, "*a")
	if emsg then  return nil, emsg  end 
	if cflg then  tgt.close(tgt)  end 
	return str
 end


--[=[*
@_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
]=]
function ctxx.sleep(num) 
	if  type(num) ~= "number"  then  ctxx.errstop("argtype isnt num: "..ctxx.vinfo(num) )  end 
	local s=os.time()
	while 1 do do 
		if  os.difftime( os.time(), s ) > num  then 
			 do break end 
		 end 
	 end::_luka_LOOPNEXT::end 
 end


--[=[*
@_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のカウント数でチェック
function ctxx.ismain(num) 
	if  type(num) ~= "number"  and  num ~= nil  then 
		ctxx.errstop("argtype isnt num/nil: "..ctxx.vinfo(num) )
	 end 
	num=num or 1
	local pos=1
	while  debug.getinfo(pos) do do pos=pos+1 end::_luka_LOOPNEXT::end 
	local mpos=pos-1
	while  debug.getinfo(mpos)  and  debug.getinfo(mpos).what ~= "main" do do mpos=mpos-1 end::_luka_LOOPNEXT::end 
	local cnt=0
	while  debug.getinfo(num+1+cnt)  do do cnt=cnt+1 end::_luka_LOOPNEXT::end 
	return (pos-mpos==cnt)
 end


--[=[*
@_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 function ismwd(num) 
	num=num or 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   not (debug.getinfo(3+num)==nil  and  debug.getinfo(2+num))  then 
		return false, info.."err. "..sname.." must be loaded as root func"
	 end 
	if string.sub(sname, 1, 1)~="@" then return false,"err: this code must be loaded as file: "..sname  end 
	sname = string.sub(sname, 2)
	local sep = string.match(package.path, ".%?")
	if sep=="" then  return false, "err: failed to get dirsep from path ENV (/ etc): "..sname  end 
	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  and  "."..sep..buf ~= sname then 
		return false, "err: this file must be loaded as './foo' or 'foo'. run from srcdir: "..sname
	 end 
	return true
 end


--[=[*
@_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
]=]
function ctxx.v2bool(obj) 
	if obj then return true end 
	return false
 end

--[=[*
@_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
]=]
function ctxx.istypes(obj, str) 
	local f1=0
	if str==nil then str="NbnstfuT";f1=1 end 
	if type(str)~="string" then  ctxx.errstop("2nd arg is not string: "..type(str), 2)  end 
	if  string.find(str, "A")  then str = "NbnstfuT"..str  end 
	if  string.match(str, "[^NbnstfuTAi]")  then 
		ctxx.errstop("bad typechar [NbnstfuTAi]: "..str, 2)
	 end 
	local tbl={}
	local res=type(obj)
	local ires="_"
	if  res=="nil"  then  res="N"  
	elseif  res=="boolean"  then  res="b"  
	elseif  res=="number"  then 
		res="n"
		local i,r = math.modf(obj)
		if r==0 then ires="i" end 
	 
	elseif  res=="string"  then  res="s"  
	elseif  res=="table"  then 	res="t"  
	elseif  res=="function"  then  res="f"  
	elseif  res=="userdata"  then  res="u"  
	elseif  res=="thread"  then 	res="T"  end 
	local buf = string.find(str, res)
	--救済。integer
	if buf==nil then 
		res=nil
		buf = string.find(str, ires)
		if buf~=nil then res=ires end 
	 end 
	if f1==1 and buf==nil then res="_" end 
	return res
 end


--[=[*
@_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
function ctxx.tb2va(tb) 
	if tb==nil then  tb={}  end 
	if type(tb)~="table" then  ctxx.errstop("1st arg allows only tb/nil: "..ctxx.vinfo(tb), 2)  end 
	if unpack_wrap==nil then unpack_wrap=table.unpack end 
	if unpack_wrap==nil then  ctxx.errstop("5.1/5.3 unpack/table.unpack is nil. new lua version?")  end 
	return unpack_wrap(tb)
 end

--[=[*
@_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
]=]

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

	if type(lv)=="nil" then lv=1 end 
	if type(lv)~="number" or  ctxx.istypes(key, "sN")==nil then 
		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)
	 end 
	local buf = debug.getinfo(1+lv)
	if lv<0 or buf==nil then 
		emsg=ctxx.fperr(nil, "1st arg, stack lv is out of stack depth", 2)
		return nil, emsg
	 end 
	while true do do 
		local k, v = debug.getlocal(1+lv, i)
		if k==nil then   do break end   end 
		rtn[k] = v
		i=i+1
	 end::_luka_LOOPNEXT::end 
	if key~=nil then rtn=rtn[key]  end 
	return rtn
 end

--[=[*
@_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
]=]
function ctxx.setlovars(lv, key, data) 
	local rtn = debug.getinfo(1+lv)
	local i=1
	if lv<0 or rtn==nil then 
		emsg=ctxx.fperr(nil, "1st arg, stack lv is out of stack depth", 2)
		return nil, emsg
	 end 
	while 1 do do 
		local k, v = debug.getlocal(1+lv, i)
		if k==key or k==nil then   do break end   end 
		i=i+1
	 end::_luka_LOOPNEXT::end 
	if k==nil then 
		emsg=ctxx.fperr(nil, "2nd arg, no such local name", 2)
		return nil, emsg
	 end 
	rtn = debug.setlocal(1+lv, i, data)
	return rtn
 end


--[=[*
@_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
]=]
function ctxx.getscvars(lv, key) 
	if type(lv)~="number"  then  ctxx.errstop("1st arg must be num: "..type(slv), 2)  end 
	if key~=nil  and  type(key)~="string"  and  type(key)~="number"  then 
		ctxx.errstop("2nd arg must be mun/str/nil: "..type(key), 2)
	 end 
	
	local rtn={}
	local emsg=nil
	
	lv=lv+1
	local buf = debug.getinfo(lv)
	if lv<=0 or buf==nil then 
		emsg=ctxx.fperr(nil, "1st arg, stklv is out of stkdepth", 2)
		return nil, emsg
	 end 
	local i=1
	--local
	while true do do 
		local k, v = debug.getlocal(lv, i)
		if k==nil then   do break end   end 
		rtn[k] = v
		i=i+1
	 end::_luka_LOOPNEXT::end 
	--scope
	local buf = debug.getinfo(lv).func
	i=1
	while true do do 
		local k, v = debug.getupvalue(buf, i)
		if k==nil then   do break end   end 
		if rtn[k] == nil then  rtn[k] = v  end 
		i=i+1
	 end::_luka_LOOPNEXT::end 
	--global
	i=1
	for k,v in pairs(_G)  do do  if rtn[k] == nil then  rtn[k] = v  end   end::_luka_LOOPNEXT::end 
	if key~=nil then rtn=rtn[key]  end 
	if rtn==nil then 
		emsg=ctxx.fperr(nil, "err. key isnt used: "..key , 2)
		return nil, emsg
	 end 
	return rtn
 end

--[=[*
@_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
]=]

function ctxx.setscvars(lv, key, data) 
	if type(lv)~="number"  then  ctxx.errstop("1st arg must be num: "..type(slv), 2)  end 
	if key~=nil  and  type(key)~="string"  and  type(key)~="number"  then 
		ctxx.errstop("2nd arg must be mun/str/nil: "..type(key), 2)
	 end 
	lv=lv+1
	local rtn = debug.getinfo(lv)
	local i=1
	if lv<=0 or rtn==nil then 
		emsg=ctxx.fperr(nil, "1st arg, stklv is out of stkdepth", 2)
		return nil, emsg
	 end 
	--local
	while 1 do do 
		local k, v = debug.getlocal(lv, i)
		if k==nil then   do break end   end 
		if k==key then 
			local rtn = debug.setlocal(lv, i, data)
			return rtn
		 end 
		i=i+1
	 end::_luka_LOOPNEXT::end 
	--scope
	i=1
	local buf = debug.getinfo(lv).func
	while 1 do do 
		local k, v = debug.getupvalue(buf, i)
		if k==nil then   do break end   end 
		if k==key then 
			local rtn = debug.setupvalue(buf, i, data)
			return rtn
		 end 
		i=i+1
	 end::_luka_LOOPNEXT::end 
	--global
	for k,v in pairs(_G)  do do 
		if k == key then 
			_G[k] = data
			return key
		 end 
	 end::_luka_LOOPNEXT::end 
	--nohit
	emsg=ctxx.fperr(nil, "err. key isnt used: "..key , 2)
	return nil, emsg
 end

--[=[*
@_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
]=]

function ctxx.S_FILE(i) 
	local rmsg=""
	if type(i)=="nil" then i=1 end 
	if type(i)=="number" then 
		rmsg = debug.getinfo(1+i,"S")
		if rmsg==nil then rmsg="nil" 
		elseif 1 then rmsg = rmsg.short_src end 
	 
	elseif true then  error(ctxx.S_FUNC(1).."(): 1st arg is not num/nil: "..type(i), 1)  end 
	if rmsg==nil then  rmsg = "nil"  end 
	return rmsg
 end
function ctxx.S_LINE(i) 
	local rmsg=""
	if type(i)=="nil" then i=1 end 
	if type(i)=="number" then 
		rmsg = debug.getinfo(1+i,"l")
		if rmsg==nil then rmsg="nil" 
		elseif 1 then rmsg = rmsg.currentline end 
	 
	elseif true then  error(ctxx.S_FUNC(1).."(): 1st arg is not num/nil: "..type(i), 1)  end 
	if rmsg==nil then  rmsg = "nil"  end 
	return rmsg
 end
function ctxx.S_FUNC(i) 
	local rmsg=""
--if(1){return "now_debugging..." }
	if type(i)=="nil" then i=1 end 
	if type(i)=="number" then 
		rmsg = debug.getinfo(1+i,"n")
		if rmsg==nil then  rmsg = "nil"  
		elseif 1 then rmsg = rmsg.name end 
	 
	elseif true then  error(ctxx.S_FUNC(1).."(): 1st arg is not num/nil: "..type(i), 1)  end 
	return rmsg
 end

--[=[*
@_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
]=]
function ctxx.tblconv(tbl, cmd) 
	local rtn={}
	local buf=nil
	local cnt=1
	local emsg=""
	
	if tbl==nil then tbl={}  end 
	if  type(tbl)~="table"  or  type(cmd)~="string"  then 
		ctxx.errstop("invaild args. f(tbl/nil, str): "..type(tbl)..", "..type(cmd), 2)
	 end 
	local b1, b2 = string.match (cmd, "([a-z]+):?([a-zA-Z]*)")
	if b1==nil then b1="" end 
	if b2==nil or b2=="" then  b2="A"  end 
	buf={ ["ind"]=1,["indn"]=1,["seq"]=1,["seqn"]=1,["key"]=1,["swap"]=1 }
	if buf[b1] == nil  or  string.match(b2, "[^NbnstfuTA]")  then 
		ctxx.errstop( "err. 2nd argfmt is invalid: "..cmd, 2 )
	 end 
	if b1 == "seq"  then 
		for k,v in ipairs(tbl)  do do  if  ctxx.istypes(v, b2)  then rtn[k]=v  end   end::_luka_LOOPNEXT::end 
		goto lbl_RTN
	 end 
	if b1 == "seqn"  then 
		for k,v in ipairs(tbl)  do do if  ctxx.istypes(v, b2)  then  rtn[1+#rtn]=v  end   end::_luka_LOOPNEXT::end 
		goto lbl_RTN
	 end 
	if b1 == "ind" then 
		for k,v in pairs(tbl)  do do 
			if type(k)=="number"  and  ctxx.istypes(v, b2)  then  rtn[k]=v  end 
		 end::_luka_LOOPNEXT::end 
		goto lbl_RTN
	 end 
	if b1 == "indn" then 
		--ソートと詰めなおしを兼ねる。1,3,4,5みたいな飛び飛びインデックスを1,2,3,4へ
		--詰め込んで行ってvalueでソートをかければいけるか
		local kbuf={}
		for k,v in pairs(tbl)  do do 
			if type(k)=="number"  and  ctxx.istypes(v, b2)  then 	kbuf[1+#kbuf] = k  end 
		 end::_luka_LOOPNEXT::end 
		table.sort(kbuf)	--キーは-1とか1の数値なので標準ソートでいける。
		local pbuf={}
		local nbuf={}
		--kbufのvalueはtblのキーなので、並び替えではこうなる。
		for k,v in ipairs(kbuf)  do do 
			if 0<v then  pbuf[1+#pbuf]=tbl[v]  
			elseif v<0 then  nbuf[1+#nbuf]=tbl[v]  end 
		 end::_luka_LOOPNEXT::end 
		--合体
		local buf= -#nbuf
		for k,v in ipairs(nbuf)  do do 
			pbuf[buf]=v
			buf=buf+1
		 end::_luka_LOOPNEXT::end 
		--[0]は無視してたので追加
		pbuf[0]=tbl[0]
		rtn=pbuf
		goto lbl_RTN
	 end 
	if b1 == "key" then 
		for k,v in pairs(tbl)  do do 
			if type(k)=="string"  and  ctxx.istypes(v, b2)  then  rtn[k]=v  end 
		 end::_luka_LOOPNEXT::end 
		goto lbl_RTN
	 end 
	if b1 == "swap" then 
		for k,v in pairs(tbl)  do do 
			if  ctxx.istypes(v, "sn")  and  ctxx.istypes(v, b2)  then 	rtn[v]=k  end 
		 end::_luka_LOOPNEXT::end 
		goto lbl_RTN
	 end 
	::lbl_RTN::
	return rtn
 end
-- add rename
ctxx.tbconv = ctxx.tblconv


--[=[*
@_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
]=]
function ctxx.vinfo(...) 
	local rtn = ""
	local i=1
	local max=select("#", ...)
	while i<=max do do 
		local v=select(i, ...)
		rtn=rtn.. ", "..tostring(v)..":"..ctxx.istypes(v)
		i=i+1
	 end::_luka_LOOPNEXT::end 
	return string.sub(rtn, 3)
 end

--[=[*
@_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
]=]
function ctxx.tbdump(tb) 
	if type(tb)~="table" then  return nil, ctxx.fperr(nil, "1st arg is not tb: "..ctxx.vinfo(tb), 2)  end 
	local memo={}
	local indent=""
	return ctxx.tbdumpsub(tb, indent, memo)
 end
function ctxx.tbdumpsub(tb, indent, memo) 
	memo[tb]=1
	local str="{\n"
	for k,v in pairs(tb)  do do 
		if type(k)=="number" then  k=tostring(k)  
		elseif type(k)=="string" then  k=string.format('%q',k)  
		elseif type(k)=="bool" then  k=tostring(k)  
		elseif 1 then  return nil, ctxx.fperr(nil, "bad key/val type, nsb/nsbt: "..ctxx.vinfo(k,v), 2)  end 
		k="["..k.."]"
		if type(v)=="number" then  v=tostring(v)  
		elseif type(v)=="string" then  v=string.format('%q',v)  
		elseif type(v)=="bool" then  v=tostring(v)  
		elseif type(v)=="table" then 
			if memo[v] then  return nil, ctxx.fperr(nil, "detect tb ptrloop: "..ctxx.vinfo(k,v), 2)  end 
			local emsg
			v, emsg=ctxx.tbdumpsub(v, indent.."\t", memo)
			if emsg then return nil, emsg end 
		 
		elseif 1 then  return nil, ctxx.fperr(nil, "bad key/val type, nsb/nsbt: "..ctxx.vinfo(k,v), 2)  end 
		str=str..indent..k.."="..v..",\n"
	 end::_luka_LOOPNEXT::end 
	memo[tb]=nil
	str=str..indent.."}"
	return str
 end



--[=[*
@_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
function tinfosub(tb, tbname, ofs, mtbl) 
	local tadd = string.match( tostring(tb), "0[0-9a-fA-FxX]+")
	mtbl[tadd]=1
	if type(tbname)=="string" then  tbname='"'..tbname..'"'  end 
	tbname=tostring(tbname)
	
	local str=ofs.."{ "..tbname.." :"..tadd.."\n"
	for k,v in pairs(tb)  do do 
		if type(v) == "table" then 
			tadd = string.match( tostring(v), "0[0-9a-fA-FxX]+")
			--loop系発見
			if mtbl[tadd] then 
				str = str..ofs.."\t{ "..tostring(k).." "..tostring(tadd).." (sametb) }\n"
			 
			else 
				str=str..tinfosub(v, k, ofs.."\t", mtbl)
			 end 
		 
		elseif 1 then 
			if type(k)~="number" then k='"'..tostring(k)..'"' end 
			local buf=ctxx.istypes(v)
			str=str..ofs..tostring(k).." = "..tostring(v)..":"..buf..",\n"
		 end 
	 end::_luka_LOOPNEXT::end 
	str=string.gsub(str,",\n$","\n")
	str=str..ofs.."}\n"
	return str
 end
function ctxx.tinfo(tb)  return ctxx.ftinfo(io.stderr, tb)  end
function ctxx.ftinfo(fh, tb) 
	if type(tb)~="table" then return nil, ctxx.fperr(nil, "2nd arg must be table: "..ctxx.vinfo(tb), 2)  end 
	local str = tinfosub(tb, "", "", {} )
	ctxx.fprintf(fh, "%s\n", str )
	return str
 end
ctxx.tbinfo=ctxx.tinfo
ctxx.ftbinfo=ctxx.ftinfo


--[=[*
@_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
]=]

function ctxx.fdbg(fh, ...) 
	if  not  io.type(fh)  and fh~=nil then  ctxx.errstop("1st arg is not filetype: "..ctxx.vinfo(fh), 2)  end 
	local msg="dbg( "
	local submsg=""
	local buf
	--#は渡された...の引数の個数マーク
	for i=1,select("#", ...)  do do 
		buf=select(i, ...)
		if type(buf)=="table" then 
			msg=msg.."tbl:"..i..", "
			submsg=submsg.."tbl"..i..":"..ctxx.ftinfo(nil, buf).."\n"
			goto lbl_NEXT
		 end 
		msg=msg..tostring(buf)..":"..ctxx.istypes(buf)..", "
		::lbl_NEXT::
	 end::_luka_LOOPNEXT::end 
	msg=string.gsub(msg, ", $", " )")
	if #submsg~=0 then 	msg=msg.."\n"..submsg  end 
	if  select("#", ...)==0 then 	msg="dbg()\n"  end 
--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) or "").." "..(ctxx.S_LINE(2) or "")..": "..(ctxx.S_FUNC(2) or "").."(): "..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 then 
--print("dbgfunc_2")
		fh.write(fh, msg)
--	ctxx.fprintf(fh,"%s\n",msg)
--print("dbgfunc_3")
--		fh.flush(fh)
	 end 
	return msg
 end
function ctxx.dbg(...)  return ctxx.fdbg(io.stderr, ...)  end

--[=[*
@_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
function sprintfsub(fmt, ...) 
	local emsg
	fmt, emsg=ctxx.clit(fmt)
	if fmt==nil then return nil, emsg end 
	return string.format(fmt, ...)
 end
function ctxx.sprintf(fmt, ...) 
	return sprintfsub(fmt, ...)
 end
function ctxx.fprintf(fh, fmt, ...) 
	local emsg
	fmt, emsg=sprintfsub(fmt, ...)
	if fh==nil then  return fmt  end 
	if fmt==nil then return nil, emsg end 
	fh.write(fh, fmt)
	return fmt
 end
function ctxx.printf(fmt, ...) 
	local buf, emsg = ctxx.fprintf(io.stdout, fmt, ...)
	if buf==nil then return nil, emsg end 
	return buf
 end

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

	while pos<=#fmt do do 
		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 or elit or eoct or ehex or euni or eUNI
		if raw then  sres=sres..s; pos=pos+string.len(s)  
		elseif elit then 
			pos=pos+string.len(s)
			local cbuf = string.sub(s, 2, 2)
			if cbuf=="\\" then  sres=sres.."\\" 
			elseif cbuf=="a" then sres=sres.."\a" 
			elseif cbuf=="b" then sres=sres.."\b" 
			elseif cbuf=="f" then sres=sres.."\f" 
			elseif cbuf=="n" then sres=sres.."\n" 
			elseif cbuf=="r" then sres=sres.."\r" 
			elseif cbuf=="t" then sres=sres.."\t" 
			elseif cbuf=="v" then sres=sres.."\v" end 
		 
		elseif eoct then 
			pos=pos+string.len(s)
			local buf = string.sub(s, 2)
			local num = tonumber(buf, 8)
			local cbuf = string.char(num)
			sres=sres..cbuf
		 
		elseif ehex then 
			pos=pos+string.len(s)
			local buf = string.sub(s, 3)
			local num=tonumber(buf, 16)
			local cbuf = string.char(num)
			sres=sres..cbuf
		 
		elseif euni or eUNI then 
			pos=pos+string.len(s)
			local buf = ctxx.strconv(s, "uc:b")
			sres=sres..buf
		 
		elseif s==nil then 
			--if s==nil, err
			local emsg = ctxx.fperr(nil,"bad escchar: "..string.sub(fmt,pos,pos+20),2)
			return nil, emsg
		 end 
	 end::_luka_LOOPNEXT::end 
	return sres
 end



--[=[*
@_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
]=]

function ctxx.matchlit(str, pos) 
	if pos==nil then pos=1 end 
	if type(str)~="string" then  ctxx.errstop("1st arg must be 'string': "..type(str),2 )  end 
	if type(pos)~="number" then  ctxx.errstop("2nd arg must be 'nil/num': "..type(pos),2 )  end 

	local rtn=nil
	local cc=nil
	local s,ss,e,ee, _ = nil,nil,nil,nil
	while pos <= #str do do 
		if cc==nil then 
			s = string.find(str, "['\"]", pos)
			if s==nil then  do break end  end 
			cc = string.sub(str, s,s)
			pos=s+1
			goto lbl_NEXT
		 end 
		if cc~=nil then 
			_, e = string.find(str, cc, pos, true)
			if e==nil then  do break end  end 
			_, ee = string.find(str, '\\\\', pos, true)
			if ee~=nil  and  ee<e then pos=ee+1; goto lbl_NEXT end 
			_, ee = string.find(str, '\\'..cc, pos, true)
			if ee==e then  pos=e+1;goto lbl_NEXT  end 
			rtn = string.sub(str, s+1, e-1)
			 do break end 
		 end 
		::lbl_NEXT::
	 end::_luka_LOOPNEXT::end 
	return rtn, s, e
 end

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

	if s~=nil  and  ss==nil  or  s~=nil  and  s<ss then 
		ss, ee = string.find(str, "\n", s)
		if ee~=nil then rtn = string.sub(str, s, ee) end 
	 end 
	return rtn, s, ee
 end

--multi系は[[--]]が同じなのでpre--で共通化
local
function match_sub(str, pos, pre) 
	if pos==nil then pos=1 end 
	if type(str)~="string" then  ctxx.errstop("1st arg must be 'string': "..type(str), 2 )  end 
	if type(pos)~="number" then  ctxx.errstop("2nd arg must be 'nil/num': "..type(pos), 2 )  end 
	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 then goto lbl_RTN end 
	spos=s

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


--[=[*
@_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
]=]

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

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

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

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


--[=[*
@_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)  do do  alttbl[k]={k, v}  end::_luka_LOOPNEXT::end 
	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 then  return ck1  end 
		return ( bf[1]<af[1] )
	 end
	table.sort(alttbl, altcomp)
	for k,v in ipairs(alttbl)  do do  tbl[k]=v[2]  end::_luka_LOOPNEXT::end 
 end



--[=[*
@_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
]=]

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

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

--[=[*
@_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
]=]

function ctxx.tsrm(tbl, keys) 
	local rtn= {}
	local cnt= ctxx.tsize(tbl)
	if tbl==nil then tbl={}  end 
	if keys==nil then leys={}  end 
	if type(tbl)~="table"  or  type(keys)~="table" then 
		local msg=ctxx.ferrdisp(io.stderr, "CODE_ERR: args is not tbl: "..a.."/"..b,1)
		rtn=nil
		cnt=msg
		goto lbl_RTN
	 end 
	for k,v in ipairs(keys) do do 
		if type(v)~="string"  and  type(v)~="number"  then 
			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
		 end 
		if tbl[v] ~= nil then  tbl[v]=nil; cnt=cnt-1  end 
	 end::_luka_LOOPNEXT::end 
	rtn=tbl
	::lbl_RTN::
	return rtn, cnt
 end

--[=[*
@_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
]=]
function ctxx.ttgrep(a,b) 
	if a==nil then a={} end 
	if b==nil then b={} end 
	local rtn= {}
	local cnt=0

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

--[=[*
@_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
]=]
function ctxx.tsgrep(tbl, keys) 
	local rtn= {}
	local cnt= 0
	if tbl==nil then tbl={} end 
	if keys==nil then leys={} end 
	if type(tbl)~="table"  or  type(keys)~="table" then 
		local msg=ctxx.ferrdisp(io.stderr, "CODE_ERR: args is not tbl: "..a.."/"..b,1)
		rtn=nil
		cnt=msg
		goto lbl_RTN
	 end 
	for k,v in ipairs(keys) do do 
		if type(v)~="string"  and  type(v)~="number"  then 
			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
		 end 
		if tbl[v] ~= nil then  rtn[v]=tbl[v]; cnt=cnt+1  end 
	 end::_luka_LOOPNEXT::end 
	::lbl_RTN::
	return rtn, cnt
 end


--[=[*
@_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
]=]
function ctxx.tmerge(...) 
	--#は渡された...の引数の個数マーク
	local ac=select("#", ...)
	local av = {...}
	local rtn={}
	local emsg=nil
	for i=1, ac do do 
		if type(av[i]) == "table" then 
			for k,v in pairs(av[i])  do do 
				rtn[k] = rtn[k]  or  v
			 end::_luka_LOOPNEXT::end 
		 
		elseif av[i]~=nil then 
			emsg=ctxx.sprintf("arg is not tbl/nil: av[%d]=%s", i, tostring(av[i]) )
			return nil, ctxx.fperr(nil, emsg, 2)
		 end 
	 end::_luka_LOOPNEXT::end 
	--ig nil
	::lbl_RTN::
	return rtn, ctxx.tsize(rtn)
 end

--[=[*
@_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
]=]
function ctxx.tmergeo(...) 
	local ac=select("#", ...)
	local av = {...}
	local rtn={}
	local emsg=nil
	for i=1, ac do do 
		local pos=ac+1-i
		if type(av[pos]) == "table" then 
			for k,v in pairs(av[pos])  do do 
				rtn[k] = rtn[k]  or  v
			 end::_luka_LOOPNEXT::end 
		 
		elseif av[pos]~=nil then 
			emsg=ctxx.sprintf("arg is not tbl/nil: av[%d]=%s", pos, tostring(av[pos]) )
			return nil, ctxx.fperr(nil, emsg, 2)
		 end 
		--ig nil
	 end::_luka_LOOPNEXT::end 
	::lbl_RTN::
	return rtn, ctxx.tsize(rtn)
 end


--[=[*
@_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
]=]

function ctxx.errstop(msg, n) 
	if n==nil then n=1 end 
	local fname=ctxx.S_FUNC(2)
	error(fname.."(): "..msg, n+1)
 end
function ctxx.perr(msg, n) 
	return ctxx.fperr(io.stderr, msg, n)
 end
function ctxx.fperr(fh, msg, n) 
	local rtn=nil
	local emsg=nil
	if fh~=nil  and  io.type(fh)==nil then 
		ctxx.errstop("1st arg is not file handle", 2)
	 end 
	if msg==nil then msg="" end 
	if type(msg)~="string" then 
		ctxx.errstop("2nd arg is not str/nil: "..type(msg), 2)
	 end 
	if n==nil then n=1 end 
	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
 end


--[=[*
@_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
function ctxx.laptime(key)  return ctxx.flaptime(io.stderr, key)  end
function ctxx.flaptime(fh, key) 
	local res=""
	if key==nil then key="-" end 
	if key==0 then  laptimebuff=os.clock()  
	elseif 1 then 
		local i=1
		res=os.clock()-laptimebuff
		local sym={[1]="s", [2]="ms", [3]="us", [4]="ns"}
		while i<2 do do 
			if res>=1 then  do break end  end 
			i=i+1
			res=res*1000
		 end::_luka_LOOPNEXT::end 
		res= ctxx.sprintf("sys	%.3f %s", res, sym[i])
		res=res..": "..tostring(key).."\n"
		ctxx.fprintf(fh, "%s", res)
		return res
	 end 
	return 0
 end


--[=[*
@_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
]=]

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

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



--[=[*
@_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
]=]

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

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

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


--[=[*
@_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
]=]
function ctxx.ag2opt(fmt, ag) 
	local ferr=ctxx.errstop
	if string.sub(fmt, 1,1) == ":"  then 
		ferr= function(a, b) return a end
		fmt=string.sub(fmt, 2)
	 end 
	
	local buf, bbuf, pos = nil, nil, 1
	local subflg={}	--subargを持つか否か
	local agtbl={}
	local optbl={}

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

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


--[=[*
@_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
function grepcmtc(str) 
	local r1=nil	--result mcmt string
	local r2=nil	-- lft str 食べ残し。再代入に必要になる。
	while 1 do do 
		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 or s2 or s3 or s4) == nil  then  r1=nil; r2=str;  do break end   end 	--該当なし
		--どれかしらヒットしてる. s1でなくても
		
		if s1==nil then s1=#str  end 
		if s2==nil then s2=#str  end 
		if s3==nil then s3=#str  end 
		if s4==nil then s4=#str  end 
		min=math.min(s1,s2,s3,s4)
		
		--本命 minの関係と--単独とのレースで先に出る必要がある
		if s1==min then 
			if e1==nil then r1=nil; r2=string.sub(str, s1);  do break end  end 
			r1=rtn
			r2 = string.sub(str, e1+1)
			 do break end 
		 end 
		if s2==min then 
			if e2==nil then r1=nil; r2=string.sub(str, s2);  do break end  end 
			str = string.sub(str, e2+1)
			goto lbl_NEXT
		 end 
		if s3==min then 
			if e3==nil then r1=nil; r2=string.sub(str, s3);  do break end  end 
			str = string.sub(str, e3+1)
			goto lbl_NEXT
		 end 
		if s4==min then 
			if e4==nil then r1=nil; r2=string.sub(str, s4);  do break end  end 
			str = string.sub(str, e4+1)
			goto lbl_NEXT
		 end 
		::lbl_NEXT::
	 end::_luka_LOOPNEXT::end 
	return r1, r2
 end

function ctxx.grepmcmts(str) 
	local tbl={}
	while 1 do do 
		rtn, str = grepcmtc(str)
		if rtn~=nil then tbl[#tbl+1]=rtn end 
		if rtn==nil then  do break end  end 
	 end::_luka_LOOPNEXT::end 
	return tbl
 end

function ctxx.grepmcmtf(fname, buffsize) 
	local rtn, emsg = io.open(fname, "r")
	if rtn==nil then return rtn, emsg end 
	local sv=io.input()
	io.input(rtn)	--fhをstdinに設定する
	local tbl={}
	local str=""
	rtn=""
	while 1 do do 
		local buf = io.read(buffsize)  or  ""
		str=str..buf
		rtn, str = grepcmtc(str)
		if rtn==nil  and  buf=="" then  do break end  end 
		if rtn~=nil then tbl[#tbl+1]=rtn end 
	 end::_luka_LOOPNEXT::end 
	io.input(sv)	--reset
	return tbl
 end

--[=[*
@_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
function 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 function rtn(bf, af) 
		--事前にwordは含有済
		for  k,v in ipairs(tbl)  do do 
			local s = string.find(bf, v)
			local ss = string.find(af, v)
			if s~=nil  and  ss==nil then return true end 	--bfが無いってことはそれ以上の格
			if s~=nil  and  ss~=nil  and  s<ss then return true end 
			if s==nil  and  ss~=nil then return nil end 	--bfが無いってことはそれ以上の格
			if s~=nil  and  ss~=nil  and  ss<s then return nil end 
			--if(s==ss) { return true }	//区別不能.次の単語でソート 
		 end::_luka_LOOPNEXT::end 
		return true	--全滅。一致処理は上流で始末する.stsort()を使う
	 end
	return rtn
 end

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



--[=[*
@_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
]=]

function ctxx.srcinfo(ctbl,lptn,tbl)  return ctxx.ffsrcinfo(io.stdout, nil, ctbl, lptn, tbl)  end
function ctxx.ffsrcinfo(fh, src, ctbl, lptn, argtbl) 
	local rtn, emsg = ctxx.cktp("A,fsN,tN,sN,tN")
	if rtn==nil then  ctxx.errstop(emsg, 2)  end 
	if  fh~=nil  and  io.type(fh) ~= "file"  then 
		ctxx.errstop("invalid filehandle: "..ctxx.vinfo(fh) )
	 end 
	--nil support
	fh=fh or io.stdout
	src=src or debug.getinfo(2, "f").func
	ctbl=ctbl or {}
	lptn=lptn or ""
	argtbl=argtbl or {}

	local lnum = tonumber( argtbl[#argtbl] )
	if lnum~=nil then  argtbl[#argtbl] = nil  end 
	for k,v in ipairs(argtbl)  do do  if v~=nil then  ctbl[1+#ctbl]=v  end 	 end::_luka_LOOPNEXT::end 
	local rtn, emsg = ctxx.lua_gazcmt(src, ctxx.tb2va(ctbl) )

	if rtn==nil then  return rtn, emsg  end 
	if #rtn==0 then  rtn = "no doccmt exists: "..src  
	elseif #rtn ~= 1  and  lnum==nil then 
		local buf=""
		for k,v in ipairs(rtn)  do do 
			local lbuf = string.match(v, lptn) or ""
			buf=buf..lbuf
		 end::_luka_LOOPNEXT::end 
		rtn=buf
	 
	elseif 1 then 
		lnum=lnum or 1
		rtn[lnum]=rtn[lnum] or "nodoc or select num is erange"
		rtn=rtn[lnum].."\n"
	 end 
	ctxx.fprintf(fh, "%s\n", rtn)
	return rtn
 end



--[=[*
@_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
function errfunc(mode, msg) 
	if mode~=nil then 
		ctxx.fperr(io.stderr, msg, 2)
		os.exit(1)
	 end 
	local rtn = ctxx.fperr(nil, msg, 2)
	return nil, rtn
 end
function ctxx.srcpath(mode) 
	local buf = debug.getinfo(2, "S").source
	local ck = string.match(buf, ".")
	if ck ~= "@" then 
		return errfunc(mode, "failed to get filename, srcchar: "..ck)
	 end 
	return string.sub(buf, 2)
 end
function ctxx.srcdirsep(mode) 
	local sep1 = string.find (package.path, "/", 1, true) or 0
	local sep2 = string.find (package.path, "\\", 1, true) or 0
	if sep1==0  and  sep2==0  then 
		return errfunc(mode, "failed to get dirsep from package.path, LUA_PATH='(blank)' ? ")
	 end 
	local sep = "/"
	if sep1<sep2 then  sep="\\"  end 
	return sep
 end
function ctxx.srcdir(mode) 
	local base, emsg = ctxx.srcpath(mode)
	if base==nil then return nil, emsg end 
	
	local sep = ctxx.srcdirsep(mode)
	if sep==nil then return nil, emsg end 
	
	local pos=0
	for i=1, #base, 1 do do 
		local buf = string.sub(base, i, i)
		if buf==sep then pos=i end 
	 end::_luka_LOOPNEXT::end 
	return string.sub(base, 1, pos)
 end

function ctxx.srcname(mode) 
	local base, emsg = ctxx.srcpath(mode)
	if base==nil then return nil, emsg end 
	
	local sep = ctxx.srcdirsep(mode)
	if sep==nil then return nil, emsg end 
	
	local pos=1
	for i=1, #base, 1 do do 
		local buf = string.sub(base, i, i)
		if buf==sep then pos=i+1 end 
	 end::_luka_LOOPNEXT::end 
	return string.sub(base, pos)
 end
function ctxx.srcnametop(mode) 
	local base, emsg = ctxx.srcname(mode)
	if base==nil then return nil, emsg end 
	return string.match (base, "[^%.]*")
 end
function ctxx.srcnamesuf(mode) 
	local base, emsg = ctxx.srcname(mode)
	if base==nil then return nil, emsg end 
	return string.match (base, "%..*")
 end


--[=[*
@_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 
function testeq_common(msg, n, a1, a2, ck) 
	local info=""
	if n==1 then 
		--agが一つだけinfoを変える
		if ck==true then info="eq_test" end 
		if ck==false then info="neq_test"; a1= not a1 end 
	--	a1= (a1== ck)
	 end 
	if n==2 then 
		local buf=""
		if ck==true then buf=" == " end 
		if ck==false then buf=" != " end 
		info=ctxx.sprintf("%s:%s"..buf.."%s:%s",tostring(a1),(ctxx.istypes(a1))
		, tostring(a2), (ctxx.istypes(a2)) )
		a1 = ((a1 == a2) == ck)
	 end 
	if msg~=nil then msg=" ("..tostring(msg)..")" end 
	msg=msg or ""
	
	local rmsg=""
	if a1 then 
		ctxx.lutest.testsuc=ctxx.lutest.testsuc+1
		if ctxx.lutest.dispflg==2 then  rmsg = "test suc: "..info..msg  end 
	 
	else 
		ctxx.lutest.testfail=ctxx.lutest.testfail+1
		if ctxx.lutest.dispflg~=0 then  rmsg = "test FAIL: "..info..msg  end 
	 end 
		--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~="" then  ctxx.lutest.rmsg = ctxx.lutest.rmsg .. buf  end 
	if  not a1  and  ctxx.lutest.dispflg~=0
		 or  a1  and  ctxx.lutest.dispflg==2	 then 
		local fh = ctxx.getlovars(3, "fh")
		ctxx.fprintf(fh, "%s", buf)
	 end 
--suc,failでres方式を変える
	if a1 then 	return buf end 
	return nil, buf
 end

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

function ctxx.lutest.run_test(flist, flg) 
	return ctxx.lutest.frun_test(io.stderr, flist, flg)
 end
function ctxx.lutest.frun_test(fh, flist, flg) 
	if  fh~=nil  and  io.type(fh)==nil  then  ctxx.errstop("1st arg is invaild file handle")  end 
	if  ctxx.istypes(flist, "Nt") == nil  then  ctxx.errstop("3nd arg must be nil/tbl")  end 
	if  ctxx.istypes(flg, "Nn") == nil  then  ctxx.errstop("3rd arg must be nil/num")  end 
	--set verbosemode
	if flg==nil or flg==1 then ctxx.lutest.dispflg=1 
	elseif flg==0 then ctxx.lutest.dispflg=0 
	elseif flg==2 then ctxx.lutest.dispflg=2 end 
	
	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  and  type(flist) ~= "table" then 
		ctxx.errstop("err. demands noarg or one seqtbl {'ts_f1', 'ts_f2' ...}")
	 end 
	if flist==nil or #fsq==0 then  tlist=ctxx.lutest.add_test  
	elseif 1 then 
-- grep
		tlist = ctxx.tsgrep(ctxx.addtest, fsq)
		if  ctxx.tsize(tlist) ~= ctxx.tsize(fsq)  then 
			local bad = ctxx.ttrm(fsq, tlist)
			ctxx.errstop("testfunc not found. typo?\n"..ctxx.tinfo(bad))
		 end 
	 end 
	for  k,v in pairs(tlist)  do do  v()  end::_luka_LOOPNEXT::end 
	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
 end

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


--called_as_main
if ctxx.ismain()  then 
	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)
 end 
--call_as_req
return ctxx

--[[
 change log
 --
2021-11-27  Momi-g	<dmy@dmy.dmy>

	* util.sh.lua (brp): update build code

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.
]]
