You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4339 lines
108 KiB
4339 lines
108 KiB
-- merged file : lualibs-extended-merged.lua
|
|
-- parent file : lualibs-extended.lua
|
|
-- merge date : 2020-05-01 15:49
|
|
|
|
do -- begin closure to overcome local limits and interference
|
|
|
|
if not modules then modules={} end modules ['util-str']={
|
|
version=1.001,
|
|
comment="companion to luat-lib.mkiv",
|
|
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
copyright="PRAGMA ADE / ConTeXt Development Team",
|
|
license="see context related readme files"
|
|
}
|
|
utilities=utilities or {}
|
|
utilities.strings=utilities.strings or {}
|
|
local strings=utilities.strings
|
|
local format,gsub,rep,sub,find=string.format,string.gsub,string.rep,string.sub,string.find
|
|
local load,dump=load,string.dump
|
|
local tonumber,type,tostring,next,setmetatable=tonumber,type,tostring,next,setmetatable
|
|
local unpack,concat=table.unpack,table.concat
|
|
local P,V,C,S,R,Ct,Cs,Cp,Carg,Cc=lpeg.P,lpeg.V,lpeg.C,lpeg.S,lpeg.R,lpeg.Ct,lpeg.Cs,lpeg.Cp,lpeg.Carg,lpeg.Cc
|
|
local patterns,lpegmatch=lpeg.patterns,lpeg.match
|
|
local utfchar,utfbyte,utflen=utf.char,utf.byte,utf.len
|
|
local loadstripped=function(str,shortcuts)
|
|
if shortcuts then
|
|
return load(dump(load(str),true),nil,nil,shortcuts)
|
|
else
|
|
return load(dump(load(str),true))
|
|
end
|
|
end
|
|
if not number then number={} end
|
|
local stripzero=patterns.stripzero
|
|
local stripzeros=patterns.stripzeros
|
|
local newline=patterns.newline
|
|
local endofstring=patterns.endofstring
|
|
local anything=patterns.anything
|
|
local whitespace=patterns.whitespace
|
|
local space=patterns.space
|
|
local spacer=patterns.spacer
|
|
local spaceortab=patterns.spaceortab
|
|
local digit=patterns.digit
|
|
local sign=patterns.sign
|
|
local period=patterns.period
|
|
local ptf=1/65536
|
|
local bpf=(7200/7227)/65536
|
|
local function points(n)
|
|
if n==0 then
|
|
return "0pt"
|
|
end
|
|
n=tonumber(n)
|
|
if not n or n==0 then
|
|
return "0pt"
|
|
end
|
|
n=n*ptf
|
|
if n%1==0 then
|
|
return format("%ipt",n)
|
|
end
|
|
return lpegmatch(stripzeros,format("%.5fpt",n))
|
|
end
|
|
local function basepoints(n)
|
|
if n==0 then
|
|
return "0pt"
|
|
end
|
|
n=tonumber(n)
|
|
if not n or n==0 then
|
|
return "0pt"
|
|
end
|
|
n=n*bpf
|
|
if n%1==0 then
|
|
return format("%ibp",n)
|
|
end
|
|
return lpegmatch(stripzeros,format("%.5fbp",n))
|
|
end
|
|
number.points=points
|
|
number.basepoints=basepoints
|
|
local rubish=spaceortab^0*newline
|
|
local anyrubish=spaceortab+newline
|
|
local stripped=(spaceortab^1/"")*newline
|
|
local leading=rubish^0/""
|
|
local trailing=(anyrubish^1*endofstring)/""
|
|
local redundant=rubish^3/"\n"
|
|
local pattern=Cs(leading*(trailing+redundant+stripped+anything)^0)
|
|
function strings.collapsecrlf(str)
|
|
return lpegmatch(pattern,str)
|
|
end
|
|
local repeaters={}
|
|
function strings.newrepeater(str,offset)
|
|
offset=offset or 0
|
|
local s=repeaters[str]
|
|
if not s then
|
|
s={}
|
|
repeaters[str]=s
|
|
end
|
|
local t=s[offset]
|
|
if t then
|
|
return t
|
|
end
|
|
t={}
|
|
setmetatable(t,{ __index=function(t,k)
|
|
if not k then
|
|
return ""
|
|
end
|
|
local n=k+offset
|
|
local s=n>0 and rep(str,n) or ""
|
|
t[k]=s
|
|
return s
|
|
end })
|
|
s[offset]=t
|
|
return t
|
|
end
|
|
local extra,tab,start=0,0,4,0
|
|
local nspaces=strings.newrepeater(" ")
|
|
string.nspaces=nspaces
|
|
local pattern=Carg(1)/function(t)
|
|
extra,tab,start=0,t or 7,1
|
|
end*Cs((
|
|
Cp()*patterns.tab/function(position)
|
|
local current=(position-start+1)+extra
|
|
local spaces=tab-(current-1)%tab
|
|
if spaces>0 then
|
|
extra=extra+spaces-1
|
|
return nspaces[spaces]
|
|
else
|
|
return ""
|
|
end
|
|
end+newline*Cp()/function(position)
|
|
extra,start=0,position
|
|
end+anything
|
|
)^1)
|
|
function strings.tabtospace(str,tab)
|
|
return lpegmatch(pattern,str,1,tab or 7)
|
|
end
|
|
function string.utfpadding(s,n)
|
|
if not n or n==0 then
|
|
return ""
|
|
end
|
|
local l=utflen(s)
|
|
if n>0 then
|
|
return nspaces[n-l]
|
|
else
|
|
return nspaces[-n-l]
|
|
end
|
|
end
|
|
local optionalspace=spacer^0
|
|
local nospace=optionalspace/""
|
|
local endofline=nospace*newline
|
|
local stripend=(whitespace^1*endofstring)/""
|
|
local normalline=(nospace*((1-optionalspace*(newline+endofstring))^1)*nospace)
|
|
local stripempty=endofline^1/""
|
|
local normalempty=endofline^1
|
|
local singleempty=endofline*(endofline^0/"")
|
|
local doubleempty=endofline*endofline^-1*(endofline^0/"")
|
|
local stripstart=stripempty^0
|
|
local intospace=whitespace^1/" "
|
|
local noleading=whitespace^1/""
|
|
local notrailing=noleading*endofstring
|
|
local p_prune_normal=Cs (stripstart*(stripend+normalline+normalempty )^0 )
|
|
local p_prune_collapse=Cs (stripstart*(stripend+normalline+doubleempty )^0 )
|
|
local p_prune_noempty=Cs (stripstart*(stripend+normalline+singleempty )^0 )
|
|
local p_prune_intospace=Cs (noleading*(notrailing+intospace+1 )^0 )
|
|
local p_retain_normal=Cs ((normalline+normalempty )^0 )
|
|
local p_retain_collapse=Cs ((normalline+doubleempty )^0 )
|
|
local p_retain_noempty=Cs ((normalline+singleempty )^0 )
|
|
local striplinepatterns={
|
|
["prune"]=p_prune_normal,
|
|
["prune and collapse"]=p_prune_collapse,
|
|
["prune and no empty"]=p_prune_noempty,
|
|
["prune and to space"]=p_prune_intospace,
|
|
["retain"]=p_retain_normal,
|
|
["retain and collapse"]=p_retain_collapse,
|
|
["retain and no empty"]=p_retain_noempty,
|
|
["collapse"]=patterns.collapser,
|
|
}
|
|
setmetatable(striplinepatterns,{ __index=function(t,k) return p_prune_collapse end })
|
|
strings.striplinepatterns=striplinepatterns
|
|
function strings.striplines(str,how)
|
|
return str and lpegmatch(striplinepatterns[how],str) or str
|
|
end
|
|
function strings.collapse(str)
|
|
return str and lpegmatch(p_prune_intospace,str) or str
|
|
end
|
|
strings.striplong=strings.striplines
|
|
function strings.nice(str)
|
|
str=gsub(str,"[:%-+_]+"," ")
|
|
return str
|
|
end
|
|
local n=0
|
|
local sequenced=table.sequenced
|
|
function string.autodouble(s,sep)
|
|
if s==nil then
|
|
return '""'
|
|
end
|
|
local t=type(s)
|
|
if t=="number" then
|
|
return tostring(s)
|
|
end
|
|
if t=="table" then
|
|
return ('"'..sequenced(s,sep or ",")..'"')
|
|
end
|
|
return ('"'..tostring(s)..'"')
|
|
end
|
|
function string.autosingle(s,sep)
|
|
if s==nil then
|
|
return "''"
|
|
end
|
|
local t=type(s)
|
|
if t=="number" then
|
|
return tostring(s)
|
|
end
|
|
if t=="table" then
|
|
return ("'"..sequenced(s,sep or ",").."'")
|
|
end
|
|
return ("'"..tostring(s).."'")
|
|
end
|
|
local tracedchars={ [0]=
|
|
"[null]","[soh]","[stx]","[etx]","[eot]","[enq]","[ack]","[bel]",
|
|
"[bs]","[ht]","[lf]","[vt]","[ff]","[cr]","[so]","[si]",
|
|
"[dle]","[dc1]","[dc2]","[dc3]","[dc4]","[nak]","[syn]","[etb]",
|
|
"[can]","[em]","[sub]","[esc]","[fs]","[gs]","[rs]","[us]",
|
|
"[space]",
|
|
}
|
|
string.tracedchars=tracedchars
|
|
strings.tracers=tracedchars
|
|
function string.tracedchar(b)
|
|
if type(b)=="number" then
|
|
return tracedchars[b] or (utfchar(b).." (U+"..format("%05X",b)..")")
|
|
else
|
|
local c=utfbyte(b)
|
|
return tracedchars[c] or (b.." (U+"..(c and format("%05X",c) or "?????")..")")
|
|
end
|
|
end
|
|
function number.signed(i)
|
|
if i>0 then
|
|
return "+",i
|
|
else
|
|
return "-",-i
|
|
end
|
|
end
|
|
local two=digit*digit
|
|
local three=two*digit
|
|
local prefix=(Carg(1)*three)^1
|
|
local splitter=Cs (
|
|
(((1-(three^1*period))^1+C(three))*prefix+C((1-period)^1))*(anything/""*Carg(2))*C(2)
|
|
)
|
|
local splitter3=Cs (
|
|
three*prefix*endofstring+two*prefix*endofstring+digit*prefix*endofstring+three+two+digit
|
|
)
|
|
patterns.formattednumber=splitter
|
|
function number.formatted(n,sep1,sep2)
|
|
if sep1==false then
|
|
if type(n)=="number" then
|
|
n=tostring(n)
|
|
end
|
|
return lpegmatch(splitter3,n,1,sep2 or ".")
|
|
else
|
|
if type(n)=="number" then
|
|
n=format("%0.2f",n)
|
|
end
|
|
if sep1==true then
|
|
return lpegmatch(splitter,n,1,".",",")
|
|
elseif sep1=="." then
|
|
return lpegmatch(splitter,n,1,sep1,sep2 or ",")
|
|
elseif sep1=="," then
|
|
return lpegmatch(splitter,n,1,sep1,sep2 or ".")
|
|
else
|
|
return lpegmatch(splitter,n,1,sep1 or ",",sep2 or ".")
|
|
end
|
|
end
|
|
end
|
|
local p=Cs(
|
|
P("-")^0*(P("0")^1/"")^0*(1-period)^0*(period*P("0")^1*endofstring/""+period^0)*P(1-P("0")^1*endofstring)^0
|
|
)
|
|
function number.compactfloat(n,fmt)
|
|
if n==0 then
|
|
return "0"
|
|
elseif n==1 then
|
|
return "1"
|
|
end
|
|
n=lpegmatch(p,format(fmt or "%0.3f",n))
|
|
if n=="." or n=="" or n=="-" then
|
|
return "0"
|
|
end
|
|
return n
|
|
end
|
|
local zero=P("0")^1/""
|
|
local plus=P("+")/""
|
|
local minus=P("-")
|
|
local separator=period
|
|
local trailing=zero^1*#S("eE")
|
|
local exponent=(S("eE")*(plus+Cs((minus*zero^0*endofstring)/"")+minus)*zero^0*(endofstring*Cc("0")+anything^1))
|
|
local pattern_a=Cs(minus^0*digit^1*(separator/""*trailing+separator*(trailing+digit)^0)*exponent)
|
|
local pattern_b=Cs((exponent+anything)^0)
|
|
function number.sparseexponent(f,n)
|
|
if not n then
|
|
n=f
|
|
f="%e"
|
|
end
|
|
local tn=type(n)
|
|
if tn=="string" then
|
|
local m=tonumber(n)
|
|
if m then
|
|
return lpegmatch((f=="%e" or f=="%E") and pattern_a or pattern_b,format(f,m))
|
|
end
|
|
elseif tn=="number" then
|
|
return lpegmatch((f=="%e" or f=="%E") and pattern_a or pattern_b,format(f,n))
|
|
end
|
|
return tostring(n)
|
|
end
|
|
local hf={}
|
|
local hs={}
|
|
setmetatable(hf,{ __index=function(t,k)
|
|
local v="%."..k.."f"
|
|
t[k]=v
|
|
return v
|
|
end } )
|
|
setmetatable(hs,{ __index=function(t,k)
|
|
local v="%"..k.."s"
|
|
t[k]=v
|
|
return v
|
|
end } )
|
|
function number.formattedfloat(n,b,a)
|
|
local s=format(hf[a],n)
|
|
local l=(b or 0)+(a or 0)+1
|
|
if #s<l then
|
|
return format(hs[l],s)
|
|
else
|
|
return s
|
|
end
|
|
end
|
|
local template=[[
|
|
%s
|
|
%s
|
|
return function(%s) return %s end
|
|
]]
|
|
local pattern=Cs(Cc('"')*(
|
|
(1-S('"\\\n\r'))^1+P('"')/'\\"'+P('\\')/'\\\\'+P('\n')/'\\n'+P('\r')/'\\r'
|
|
)^0*Cc('"'))
|
|
patterns.escapedquotes=pattern
|
|
function string.escapedquotes(s)
|
|
return lpegmatch(pattern,s)
|
|
end
|
|
local preamble=""
|
|
local environment={
|
|
global=global or _G,
|
|
lpeg=lpeg,
|
|
type=type,
|
|
tostring=tostring,
|
|
tonumber=tonumber,
|
|
format=string.format,
|
|
concat=table.concat,
|
|
signed=number.signed,
|
|
points=number.points,
|
|
basepoints=number.basepoints,
|
|
utfchar=utf.char,
|
|
utfbyte=utf.byte,
|
|
lpegmatch=lpeg.match,
|
|
nspaces=string.nspaces,
|
|
utfpadding=string.utfpadding,
|
|
tracedchar=string.tracedchar,
|
|
autosingle=string.autosingle,
|
|
autodouble=string.autodouble,
|
|
sequenced=table.sequenced,
|
|
formattednumber=number.formatted,
|
|
sparseexponent=number.sparseexponent,
|
|
formattedfloat=number.formattedfloat,
|
|
stripzero=patterns.stripzero,
|
|
stripzeros=patterns.stripzeros,
|
|
escapedquotes=string.escapedquotes,
|
|
FORMAT=string.f6,
|
|
}
|
|
local arguments={ "a1" }
|
|
setmetatable(arguments,{ __index=function(t,k)
|
|
local v=t[k-1]..",a"..k
|
|
t[k]=v
|
|
return v
|
|
end
|
|
})
|
|
local prefix_any=C((sign+space+period+digit)^0)
|
|
local prefix_sub=(C((sign+digit)^0)+Cc(0))*period*(C((sign+digit)^0)+Cc(0))
|
|
local prefix_tab=P("{")*C((1-P("}"))^0)*P("}")+C((1-R("az","AZ","09","%%"))^0)
|
|
local format_s=function(f)
|
|
n=n+1
|
|
if f and f~="" then
|
|
return format("format('%%%ss',a%s)",f,n)
|
|
else
|
|
return format("(a%s or '')",n)
|
|
end
|
|
end
|
|
local format_S=function(f)
|
|
n=n+1
|
|
if f and f~="" then
|
|
return format("format('%%%ss',tostring(a%s))",f,n)
|
|
else
|
|
return format("tostring(a%s)",n)
|
|
end
|
|
end
|
|
local format_right=function(f)
|
|
n=n+1
|
|
f=tonumber(f)
|
|
if not f or f==0 then
|
|
return format("(a%s or '')",n)
|
|
elseif f>0 then
|
|
return format("utfpadding(a%s,%i)..a%s",n,f,n)
|
|
else
|
|
return format("a%s..utfpadding(a%s,%i)",n,n,f)
|
|
end
|
|
end
|
|
local format_left=function(f)
|
|
n=n+1
|
|
f=tonumber(f)
|
|
if not f or f==0 then
|
|
return format("(a%s or '')",n)
|
|
end
|
|
if f<0 then
|
|
return format("utfpadding(a%s,%i)..a%s",n,-f,n)
|
|
else
|
|
return format("a%s..utfpadding(a%s,%i)",n,n,-f)
|
|
end
|
|
end
|
|
local format_q=JITSUPPORTED and function()
|
|
n=n+1
|
|
return format("(a%s ~= nil and format('%%q',tostring(a%s)) or '')",n,n)
|
|
end or function()
|
|
n=n+1
|
|
return format("(a%s ~= nil and format('%%q',a%s) or '')",n,n)
|
|
end
|
|
local format_Q=function()
|
|
n=n+1
|
|
return format("escapedquotes(tostring(a%s))",n)
|
|
end
|
|
local format_i=function(f)
|
|
n=n+1
|
|
if f and f~="" then
|
|
return format("format('%%%si',a%s)",f,n)
|
|
else
|
|
return format("format('%%i',a%s)",n)
|
|
end
|
|
end
|
|
local format_d=format_i
|
|
local format_I=function(f)
|
|
n=n+1
|
|
return format("format('%%s%%%si',signed(a%s))",f,n)
|
|
end
|
|
local format_f=function(f)
|
|
n=n+1
|
|
return format("format('%%%sf',a%s)",f,n)
|
|
end
|
|
local format_F=function(f)
|
|
n=n+1
|
|
if not f or f=="" then
|
|
return format("(((a%s > -0.0000000005 and a%s < 0.0000000005) and '0') or format((a%s %% 1 == 0) and '%%i' or '%%.9f',a%s))",n,n,n,n)
|
|
else
|
|
return format("format((a%s %% 1 == 0) and '%%i' or '%%%sf',a%s)",n,f,n)
|
|
end
|
|
end
|
|
local format_k=function(b,a)
|
|
n=n+1
|
|
return format("formattedfloat(a%s,%s,%s)",n,b or 0,a or 0)
|
|
end
|
|
local format_g=function(f)
|
|
n=n+1
|
|
return format("format('%%%sg',a%s)",f,n)
|
|
end
|
|
local format_G=function(f)
|
|
n=n+1
|
|
return format("format('%%%sG',a%s)",f,n)
|
|
end
|
|
local format_e=function(f)
|
|
n=n+1
|
|
return format("format('%%%se',a%s)",f,n)
|
|
end
|
|
local format_E=function(f)
|
|
n=n+1
|
|
return format("format('%%%sE',a%s)",f,n)
|
|
end
|
|
local format_j=function(f)
|
|
n=n+1
|
|
return format("sparseexponent('%%%se',a%s)",f,n)
|
|
end
|
|
local format_J=function(f)
|
|
n=n+1
|
|
return format("sparseexponent('%%%sE',a%s)",f,n)
|
|
end
|
|
local format_x=function(f)
|
|
n=n+1
|
|
return format("format('%%%sx',a%s)",f,n)
|
|
end
|
|
local format_X=function(f)
|
|
n=n+1
|
|
return format("format('%%%sX',a%s)",f,n)
|
|
end
|
|
local format_o=function(f)
|
|
n=n+1
|
|
return format("format('%%%so',a%s)",f,n)
|
|
end
|
|
local format_c=function()
|
|
n=n+1
|
|
return format("utfchar(a%s)",n)
|
|
end
|
|
local format_C=function()
|
|
n=n+1
|
|
return format("tracedchar(a%s)",n)
|
|
end
|
|
local format_r=function(f)
|
|
n=n+1
|
|
return format("format('%%%s.0f',a%s)",f,n)
|
|
end
|
|
local format_h=function(f)
|
|
n=n+1
|
|
if f=="-" then
|
|
f=sub(f,2)
|
|
return format("format('%%%sx',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
|
|
else
|
|
return format("format('0x%%%sx',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
|
|
end
|
|
end
|
|
local format_H=function(f)
|
|
n=n+1
|
|
if f=="-" then
|
|
f=sub(f,2)
|
|
return format("format('%%%sX',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
|
|
else
|
|
return format("format('0x%%%sX',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
|
|
end
|
|
end
|
|
local format_u=function(f)
|
|
n=n+1
|
|
if f=="-" then
|
|
f=sub(f,2)
|
|
return format("format('%%%sx',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
|
|
else
|
|
return format("format('u+%%%sx',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
|
|
end
|
|
end
|
|
local format_U=function(f)
|
|
n=n+1
|
|
if f=="-" then
|
|
f=sub(f,2)
|
|
return format("format('%%%sX',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
|
|
else
|
|
return format("format('U+%%%sX',type(a%s) == 'number' and a%s or utfbyte(a%s))",f=="" and "05" or f,n,n,n)
|
|
end
|
|
end
|
|
local format_p=function()
|
|
n=n+1
|
|
return format("points(a%s)",n)
|
|
end
|
|
local format_b=function()
|
|
n=n+1
|
|
return format("basepoints(a%s)",n)
|
|
end
|
|
local format_t=function(f)
|
|
n=n+1
|
|
if f and f~="" then
|
|
return format("concat(a%s,%q)",n,f)
|
|
else
|
|
return format("concat(a%s)",n)
|
|
end
|
|
end
|
|
local format_T=function(f)
|
|
n=n+1
|
|
if f and f~="" then
|
|
return format("sequenced(a%s,%q)",n,f)
|
|
else
|
|
return format("sequenced(a%s)",n)
|
|
end
|
|
end
|
|
local format_l=function()
|
|
n=n+1
|
|
return format("(a%s and 'true' or 'false')",n)
|
|
end
|
|
local format_L=function()
|
|
n=n+1
|
|
return format("(a%s and 'TRUE' or 'FALSE')",n)
|
|
end
|
|
local format_n=function()
|
|
n=n+1
|
|
return format("((a%s %% 1 == 0) and format('%%i',a%s) or tostring(a%s))",n,n,n)
|
|
end
|
|
local format_N if environment.FORMAT then
|
|
format_N=function(f)
|
|
n=n+1
|
|
if not f or f=="" then
|
|
return format("FORMAT(a%s,'%%.9f')",n)
|
|
elseif f==".6" or f=="0.6" then
|
|
return format("FORMAT(a%s)",n)
|
|
else
|
|
return format("FORMAT(a%s,'%%%sf')",n,f)
|
|
end
|
|
end
|
|
else
|
|
format_N=function(f)
|
|
n=n+1
|
|
if not f or f=="" then
|
|
f=".9"
|
|
end
|
|
return format("(((a%s %% 1 == 0) and format('%%i',a%s)) or lpegmatch(stripzero,format('%%%sf',a%s)))",n,n,f,n)
|
|
end
|
|
end
|
|
local format_a=function(f)
|
|
n=n+1
|
|
if f and f~="" then
|
|
return format("autosingle(a%s,%q)",n,f)
|
|
else
|
|
return format("autosingle(a%s)",n)
|
|
end
|
|
end
|
|
local format_A=function(f)
|
|
n=n+1
|
|
if f and f~="" then
|
|
return format("autodouble(a%s,%q)",n,f)
|
|
else
|
|
return format("autodouble(a%s)",n)
|
|
end
|
|
end
|
|
local format_w=function(f)
|
|
n=n+1
|
|
f=tonumber(f)
|
|
if f then
|
|
return format("nspaces[%s+a%s]",f,n)
|
|
else
|
|
return format("nspaces[a%s]",n)
|
|
end
|
|
end
|
|
local format_W=function(f)
|
|
return format("nspaces[%s]",tonumber(f) or 0)
|
|
end
|
|
local format_m=function(f)
|
|
n=n+1
|
|
if not f or f=="" then
|
|
f=","
|
|
end
|
|
if f=="0" then
|
|
return format([[formattednumber(a%s,false)]],n)
|
|
else
|
|
return format([[formattednumber(a%s,%q,".")]],n,f)
|
|
end
|
|
end
|
|
local format_M=function(f)
|
|
n=n+1
|
|
if not f or f=="" then
|
|
f="."
|
|
end
|
|
if f=="0" then
|
|
return format([[formattednumber(a%s,false)]],n)
|
|
else
|
|
return format([[formattednumber(a%s,%q,",")]],n,f)
|
|
end
|
|
end
|
|
local format_z=function(f)
|
|
n=n+(tonumber(f) or 1)
|
|
return "''"
|
|
end
|
|
local format_rest=function(s)
|
|
return format("%q",s)
|
|
end
|
|
local format_extension=function(extensions,f,name)
|
|
local extension=extensions[name] or "tostring(%s)"
|
|
local f=tonumber(f) or 1
|
|
local w=find(extension,"%.%.%.")
|
|
if w then
|
|
if f==0 then
|
|
extension=gsub(extension,"%.%.%.","")
|
|
return extension
|
|
elseif f==1 then
|
|
extension=gsub(extension,"%.%.%.","%%s")
|
|
n=n+1
|
|
local a="a"..n
|
|
return format(extension,a,a)
|
|
elseif f<0 then
|
|
local a="a"..(n+f+1)
|
|
return format(extension,a,a)
|
|
else
|
|
extension=gsub(extension,"%.%.%.",rep("%%s,",f-1).."%%s")
|
|
local t={}
|
|
for i=1,f do
|
|
n=n+1
|
|
t[i]="a"..n
|
|
end
|
|
return format(extension,unpack(t))
|
|
end
|
|
else
|
|
extension=gsub(extension,"%%s",function()
|
|
n=n+1
|
|
return "a"..n
|
|
end)
|
|
return extension
|
|
end
|
|
end
|
|
local builder=Cs { "start",
|
|
start=(
|
|
(
|
|
P("%")/""*(
|
|
V("!")
|
|
+V("s")+V("q")+V("i")+V("d")+V("f")+V("F")+V("g")+V("G")+V("e")+V("E")+V("x")+V("X")+V("o")
|
|
+V("c")+V("C")+V("S")
|
|
+V("Q")
|
|
+V("n")
|
|
+V("N")
|
|
+V("k")
|
|
+V("r")+V("h")+V("H")+V("u")+V("U")+V("p")+V("b")+V("t")+V("T")+V("l")+V("L")+V("I")+V("w")
|
|
+V("W")
|
|
+V("a")
|
|
+V("A")
|
|
+V("j")+V("J")
|
|
+V("m")+V("M")
|
|
+V("z")
|
|
+V(">")
|
|
+V("<")
|
|
)+V("*")
|
|
)*(endofstring+Carg(1))
|
|
)^0,
|
|
["s"]=(prefix_any*P("s"))/format_s,
|
|
["q"]=(prefix_any*P("q"))/format_q,
|
|
["i"]=(prefix_any*P("i"))/format_i,
|
|
["d"]=(prefix_any*P("d"))/format_d,
|
|
["f"]=(prefix_any*P("f"))/format_f,
|
|
["F"]=(prefix_any*P("F"))/format_F,
|
|
["g"]=(prefix_any*P("g"))/format_g,
|
|
["G"]=(prefix_any*P("G"))/format_G,
|
|
["e"]=(prefix_any*P("e"))/format_e,
|
|
["E"]=(prefix_any*P("E"))/format_E,
|
|
["x"]=(prefix_any*P("x"))/format_x,
|
|
["X"]=(prefix_any*P("X"))/format_X,
|
|
["o"]=(prefix_any*P("o"))/format_o,
|
|
["S"]=(prefix_any*P("S"))/format_S,
|
|
["Q"]=(prefix_any*P("Q"))/format_Q,
|
|
["n"]=(prefix_any*P("n"))/format_n,
|
|
["N"]=(prefix_any*P("N"))/format_N,
|
|
["k"]=(prefix_sub*P("k"))/format_k,
|
|
["c"]=(prefix_any*P("c"))/format_c,
|
|
["C"]=(prefix_any*P("C"))/format_C,
|
|
["r"]=(prefix_any*P("r"))/format_r,
|
|
["h"]=(prefix_any*P("h"))/format_h,
|
|
["H"]=(prefix_any*P("H"))/format_H,
|
|
["u"]=(prefix_any*P("u"))/format_u,
|
|
["U"]=(prefix_any*P("U"))/format_U,
|
|
["p"]=(prefix_any*P("p"))/format_p,
|
|
["b"]=(prefix_any*P("b"))/format_b,
|
|
["t"]=(prefix_tab*P("t"))/format_t,
|
|
["T"]=(prefix_tab*P("T"))/format_T,
|
|
["l"]=(prefix_any*P("l"))/format_l,
|
|
["L"]=(prefix_any*P("L"))/format_L,
|
|
["I"]=(prefix_any*P("I"))/format_I,
|
|
["w"]=(prefix_any*P("w"))/format_w,
|
|
["W"]=(prefix_any*P("W"))/format_W,
|
|
["j"]=(prefix_any*P("j"))/format_j,
|
|
["J"]=(prefix_any*P("J"))/format_J,
|
|
["m"]=(prefix_any*P("m"))/format_m,
|
|
["M"]=(prefix_any*P("M"))/format_M,
|
|
["z"]=(prefix_any*P("z"))/format_z,
|
|
["a"]=(prefix_any*P("a"))/format_a,
|
|
["A"]=(prefix_any*P("A"))/format_A,
|
|
["<"]=(prefix_any*P("<"))/format_left,
|
|
[">"]=(prefix_any*P(">"))/format_right,
|
|
["*"]=Cs(((1-P("%"))^1+P("%%")/"%%")^1)/format_rest,
|
|
["?"]=Cs(((1-P("%"))^1 )^1)/format_rest,
|
|
["!"]=Carg(2)*prefix_any*P("!")*C((1-P("!"))^1)*P("!")/format_extension,
|
|
}
|
|
local xx=setmetatable({},{ __index=function(t,k) local v=format("%02x",k) t[k]=v return v end })
|
|
local XX=setmetatable({},{ __index=function(t,k) local v=format("%02X",k) t[k]=v return v end })
|
|
local preset={
|
|
["%02x"]=function(n) return xx[n] end,
|
|
["%02X"]=function(n) return XX[n] end,
|
|
}
|
|
local direct=P("%")*(sign+space+period+digit)^0*S("sqidfgGeExXo")*endofstring/[[local format = string.format return function(str) return format("%0",str) end]]
|
|
local function make(t,str)
|
|
local f=preset[str]
|
|
if f then
|
|
return f
|
|
end
|
|
local p=lpegmatch(direct,str)
|
|
if p then
|
|
f=loadstripped(p)()
|
|
else
|
|
n=0
|
|
p=lpegmatch(builder,str,1,t._connector_,t._extensions_)
|
|
if n>0 then
|
|
p=format(template,preamble,t._preamble_,arguments[n],p)
|
|
f=loadstripped(p,t._environment_)()
|
|
else
|
|
f=function() return str end
|
|
end
|
|
end
|
|
t[str]=f
|
|
return f
|
|
end
|
|
local function use(t,fmt,...)
|
|
return t[fmt](...)
|
|
end
|
|
strings.formatters={}
|
|
function strings.formatters.new(noconcat)
|
|
local e={}
|
|
for k,v in next,environment do
|
|
e[k]=v
|
|
end
|
|
local t={
|
|
_type_="formatter",
|
|
_connector_=noconcat and "," or "..",
|
|
_extensions_={},
|
|
_preamble_="",
|
|
_environment_=e,
|
|
}
|
|
setmetatable(t,{ __index=make,__call=use })
|
|
return t
|
|
end
|
|
local formatters=strings.formatters.new()
|
|
string.formatters=formatters
|
|
string.formatter=function(str,...) return formatters[str](...) end
|
|
local function add(t,name,template,preamble)
|
|
if type(t)=="table" and t._type_=="formatter" then
|
|
t._extensions_[name]=template or "%s"
|
|
if type(preamble)=="string" then
|
|
t._preamble_=preamble.."\n"..t._preamble_
|
|
elseif type(preamble)=="table" then
|
|
for k,v in next,preamble do
|
|
t._environment_[k]=v
|
|
end
|
|
end
|
|
end
|
|
end
|
|
strings.formatters.add=add
|
|
patterns.xmlescape=Cs((P("<")/"<"+P(">")/">"+P("&")/"&"+P('"')/"""+anything)^0)
|
|
patterns.texescape=Cs((C(S("#$%\\{}"))/"\\%1"+anything)^0)
|
|
patterns.luaescape=Cs(((1-S('"\n'))^1+P('"')/'\\"'+P('\n')/'\\n"')^0)
|
|
patterns.luaquoted=Cs(Cc('"')*((1-S('"\n'))^1+P('"')/'\\"'+P('\n')/'\\n"')^0*Cc('"'))
|
|
add(formatters,"xml",[[lpegmatch(xmlescape,%s)]],{ xmlescape=patterns.xmlescape })
|
|
add(formatters,"tex",[[lpegmatch(texescape,%s)]],{ texescape=patterns.texescape })
|
|
add(formatters,"lua",[[lpegmatch(luaescape,%s)]],{ luaescape=patterns.luaescape })
|
|
local dquote=patterns.dquote
|
|
local equote=patterns.escaped+dquote/'\\"'+1
|
|
local cquote=Cc('"')
|
|
local pattern=Cs(dquote*(equote-P(-2))^0*dquote)
|
|
+Cs(cquote*(equote-space)^0*space*equote^0*cquote)
|
|
function string.optionalquoted(str)
|
|
return lpegmatch(pattern,str) or str
|
|
end
|
|
local pattern=Cs((newline/(os.newline or "\r")+1)^0)
|
|
function string.replacenewlines(str)
|
|
return lpegmatch(pattern,str)
|
|
end
|
|
function strings.newcollector()
|
|
local result,r={},0
|
|
return
|
|
function(fmt,str,...)
|
|
r=r+1
|
|
result[r]=str==nil and fmt or formatters[fmt](str,...)
|
|
end,
|
|
function(connector)
|
|
if result then
|
|
local str=concat(result,connector)
|
|
result,r={},0
|
|
return str
|
|
end
|
|
end
|
|
end
|
|
local f_16_16=formatters["%0.5N"]
|
|
function number.to16dot16(n)
|
|
return f_16_16(n/65536.0)
|
|
end
|
|
if not string.explode then
|
|
local tsplitat=lpeg.tsplitat
|
|
local p_utf=patterns.utf8character
|
|
local p_check=C(p_utf)*(P("+")*Cc(true))^0
|
|
local p_split=Ct(C(p_utf)^0)
|
|
local p_space=Ct((C(1-P(" ")^1)+P(" ")^1)^0)
|
|
function string.explode(str,symbol)
|
|
if symbol=="" then
|
|
return lpegmatch(p_split,str)
|
|
elseif symbol then
|
|
local a,b=lpegmatch(p_check,symbol)
|
|
if b then
|
|
return lpegmatch(tsplitat(P(a)^1),str)
|
|
else
|
|
return lpegmatch(tsplitat(a),str)
|
|
end
|
|
else
|
|
return lpegmatch(p_space,str)
|
|
end
|
|
end
|
|
end
|
|
|
|
end -- closure
|
|
|
|
do -- begin closure to overcome local limits and interference
|
|
|
|
if not modules then modules={} end modules ['util-fil']={
|
|
version=1.001,
|
|
comment="companion to luat-lib.mkiv",
|
|
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
copyright="PRAGMA ADE / ConTeXt Development Team",
|
|
license="see context related readme files"
|
|
}
|
|
local byte=string.byte
|
|
local char=string.char
|
|
utilities=utilities or {}
|
|
local files={}
|
|
utilities.files=files
|
|
local zerobased={}
|
|
function files.open(filename,zb)
|
|
local f=io.open(filename,"rb")
|
|
if f then
|
|
zerobased[f]=zb or false
|
|
end
|
|
return f
|
|
end
|
|
function files.close(f)
|
|
zerobased[f]=nil
|
|
f:close()
|
|
end
|
|
function files.size(f)
|
|
local current=f:seek()
|
|
local size=f:seek("end")
|
|
f:seek("set",current)
|
|
return size
|
|
end
|
|
files.getsize=files.size
|
|
function files.setposition(f,n)
|
|
if zerobased[f] then
|
|
f:seek("set",n)
|
|
else
|
|
f:seek("set",n-1)
|
|
end
|
|
end
|
|
function files.getposition(f)
|
|
if zerobased[f] then
|
|
return f:seek()
|
|
else
|
|
return f:seek()+1
|
|
end
|
|
end
|
|
function files.look(f,n,chars)
|
|
local p=f:seek()
|
|
local s=f:read(n)
|
|
f:seek("set",p)
|
|
if chars then
|
|
return s
|
|
else
|
|
return byte(s,1,#s)
|
|
end
|
|
end
|
|
function files.skip(f,n)
|
|
if n==1 then
|
|
f:read(n)
|
|
else
|
|
f:seek("set",f:seek()+n)
|
|
end
|
|
end
|
|
function files.readbyte(f)
|
|
return byte(f:read(1))
|
|
end
|
|
function files.readbytes(f,n)
|
|
return byte(f:read(n),1,n)
|
|
end
|
|
function files.readbytetable(f,n)
|
|
local s=f:read(n or 1)
|
|
return { byte(s,1,#s) }
|
|
end
|
|
function files.readchar(f)
|
|
return f:read(1)
|
|
end
|
|
function files.readstring(f,n)
|
|
return f:read(n or 1)
|
|
end
|
|
function files.readinteger1(f)
|
|
local n=byte(f:read(1))
|
|
if n>=0x80 then
|
|
return n-0x100
|
|
else
|
|
return n
|
|
end
|
|
end
|
|
files.readcardinal1=files.readbyte
|
|
files.readcardinal=files.readcardinal1
|
|
files.readinteger=files.readinteger1
|
|
files.readsignedbyte=files.readinteger1
|
|
function files.readcardinal2(f)
|
|
local a,b=byte(f:read(2),1,2)
|
|
return 0x100*a+b
|
|
end
|
|
function files.readcardinal2le(f)
|
|
local b,a=byte(f:read(2),1,2)
|
|
return 0x100*a+b
|
|
end
|
|
function files.readinteger2(f)
|
|
local a,b=byte(f:read(2),1,2)
|
|
if a>=0x80 then
|
|
return 0x100*a+b-0x10000
|
|
else
|
|
return 0x100*a+b
|
|
end
|
|
end
|
|
function files.readinteger2le(f)
|
|
local b,a=byte(f:read(2),1,2)
|
|
if a>=0x80 then
|
|
return 0x100*a+b-0x10000
|
|
else
|
|
return 0x100*a+b
|
|
end
|
|
end
|
|
function files.readcardinal3(f)
|
|
local a,b,c=byte(f:read(3),1,3)
|
|
return 0x10000*a+0x100*b+c
|
|
end
|
|
function files.readcardinal3le(f)
|
|
local c,b,a=byte(f:read(3),1,3)
|
|
return 0x10000*a+0x100*b+c
|
|
end
|
|
function files.readinteger3(f)
|
|
local a,b,c=byte(f:read(3),1,3)
|
|
if a>=0x80 then
|
|
return 0x10000*a+0x100*b+c-0x1000000
|
|
else
|
|
return 0x10000*a+0x100*b+c
|
|
end
|
|
end
|
|
function files.readinteger3le(f)
|
|
local c,b,a=byte(f:read(3),1,3)
|
|
if a>=0x80 then
|
|
return 0x10000*a+0x100*b+c-0x1000000
|
|
else
|
|
return 0x10000*a+0x100*b+c
|
|
end
|
|
end
|
|
function files.readcardinal4(f)
|
|
local a,b,c,d=byte(f:read(4),1,4)
|
|
return 0x1000000*a+0x10000*b+0x100*c+d
|
|
end
|
|
function files.readcardinal4le(f)
|
|
local d,c,b,a=byte(f:read(4),1,4)
|
|
return 0x1000000*a+0x10000*b+0x100*c+d
|
|
end
|
|
function files.readinteger4(f)
|
|
local a,b,c,d=byte(f:read(4),1,4)
|
|
if a>=0x80 then
|
|
return 0x1000000*a+0x10000*b+0x100*c+d-0x100000000
|
|
else
|
|
return 0x1000000*a+0x10000*b+0x100*c+d
|
|
end
|
|
end
|
|
function files.readinteger4le(f)
|
|
local d,c,b,a=byte(f:read(4),1,4)
|
|
if a>=0x80 then
|
|
return 0x1000000*a+0x10000*b+0x100*c+d-0x100000000
|
|
else
|
|
return 0x1000000*a+0x10000*b+0x100*c+d
|
|
end
|
|
end
|
|
function files.readfixed2(f)
|
|
local a,b=byte(f:read(2),1,2)
|
|
if a>=0x80 then
|
|
return (a-0x100)+b/0x100
|
|
else
|
|
return (a )+b/0x100
|
|
end
|
|
end
|
|
function files.readfixed4(f)
|
|
local a,b,c,d=byte(f:read(4),1,4)
|
|
if a>=0x80 then
|
|
return (0x100*a+b-0x10000)+(0x100*c+d)/0x10000
|
|
else
|
|
return (0x100*a+b )+(0x100*c+d)/0x10000
|
|
end
|
|
end
|
|
if bit32 then
|
|
local extract=bit32.extract
|
|
local band=bit32.band
|
|
function files.read2dot14(f)
|
|
local a,b=byte(f:read(2),1,2)
|
|
if a>=0x80 then
|
|
local n=-(0x100*a+b)
|
|
return-(extract(n,14,2)+(band(n,0x3FFF)/16384.0))
|
|
else
|
|
local n=0x100*a+b
|
|
return (extract(n,14,2)+(band(n,0x3FFF)/16384.0))
|
|
end
|
|
end
|
|
end
|
|
function files.skipshort(f,n)
|
|
f:read(2*(n or 1))
|
|
end
|
|
function files.skiplong(f,n)
|
|
f:read(4*(n or 1))
|
|
end
|
|
if bit32 then
|
|
local rshift=bit32.rshift
|
|
function files.writecardinal2(f,n)
|
|
local a=char(n%256)
|
|
n=rshift(n,8)
|
|
local b=char(n%256)
|
|
f:write(b,a)
|
|
end
|
|
else
|
|
local floor=math.floor
|
|
function files.writecardinal2(f,n)
|
|
local a=char(n%256)
|
|
n=floor(n/256)
|
|
local b=char(n%256)
|
|
f:write(b,a)
|
|
end
|
|
end
|
|
function files.writecardinal4(f,n)
|
|
local a=char(n%256)
|
|
n=rshift(n,8)
|
|
local b=char(n%256)
|
|
n=rshift(n,8)
|
|
local c=char(n%256)
|
|
n=rshift(n,8)
|
|
local d=char(n%256)
|
|
f:write(d,c,b,a)
|
|
end
|
|
function files.writestring(f,s)
|
|
f:write(char(byte(s,1,#s)))
|
|
end
|
|
function files.writebyte(f,b)
|
|
f:write(char(b))
|
|
end
|
|
if fio and fio.readcardinal1 then
|
|
files.readcardinal1=fio.readcardinal1
|
|
files.readcardinal2=fio.readcardinal2
|
|
files.readcardinal3=fio.readcardinal3
|
|
files.readcardinal4=fio.readcardinal4
|
|
files.readinteger1=fio.readinteger1
|
|
files.readinteger2=fio.readinteger2
|
|
files.readinteger3=fio.readinteger3
|
|
files.readinteger4=fio.readinteger4
|
|
files.readfixed2=fio.readfixed2
|
|
files.readfixed4=fio.readfixed4
|
|
files.read2dot14=fio.read2dot14
|
|
files.setposition=fio.setposition
|
|
files.getposition=fio.getposition
|
|
files.readbyte=files.readcardinal1
|
|
files.readsignedbyte=files.readinteger1
|
|
files.readcardinal=files.readcardinal1
|
|
files.readinteger=files.readinteger1
|
|
local skipposition=fio.skipposition
|
|
files.skipposition=skipposition
|
|
files.readbytes=fio.readbytes
|
|
files.readbytetable=fio.readbytetable
|
|
function files.skipshort(f,n)
|
|
skipposition(f,2*(n or 1))
|
|
end
|
|
function files.skiplong(f,n)
|
|
skipposition(f,4*(n or 1))
|
|
end
|
|
end
|
|
if fio and fio.readcardinaltable then
|
|
files.readcardinaltable=fio.readcardinaltable
|
|
files.readintegertable=fio.readintegertable
|
|
else
|
|
local readcardinal1=files.readcardinal1
|
|
local readcardinal2=files.readcardinal2
|
|
local readcardinal3=files.readcardinal3
|
|
local readcardinal4=files.readcardinal4
|
|
function files.readcardinaltable(f,n,b)
|
|
local t={}
|
|
if b==1 then for i=1,n do t[i]=readcardinal1(f) end
|
|
elseif b==2 then for i=1,n do t[i]=readcardinal2(f) end
|
|
elseif b==3 then for i=1,n do t[i]=readcardinal3(f) end
|
|
elseif b==4 then for i=1,n do t[i]=readcardinal4(f) end end
|
|
return t
|
|
end
|
|
local readinteger1=files.readinteger1
|
|
local readinteger2=files.readinteger2
|
|
local readinteger3=files.readinteger3
|
|
local readinteger4=files.readinteger4
|
|
function files.readintegertable(f,n,b)
|
|
local t={}
|
|
if b==1 then for i=1,n do t[i]=readinteger1(f) end
|
|
elseif b==2 then for i=1,n do t[i]=readinteger2(f) end
|
|
elseif b==3 then for i=1,n do t[i]=readinteger3(f) end
|
|
elseif b==4 then for i=1,n do t[i]=readinteger4(f) end end
|
|
return t
|
|
end
|
|
end
|
|
|
|
end -- closure
|
|
|
|
do -- begin closure to overcome local limits and interference
|
|
|
|
if not modules then modules={} end modules ['util-tab']={
|
|
version=1.001,
|
|
comment="companion to luat-lib.mkiv",
|
|
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
copyright="PRAGMA ADE / ConTeXt Development Team",
|
|
license="see context related readme files"
|
|
}
|
|
utilities=utilities or {}
|
|
utilities.tables=utilities.tables or {}
|
|
local tables=utilities.tables
|
|
local format,gmatch,gsub,sub=string.format,string.gmatch,string.gsub,string.sub
|
|
local concat,insert,remove,sort=table.concat,table.insert,table.remove,table.sort
|
|
local setmetatable,getmetatable,tonumber,tostring,rawget=setmetatable,getmetatable,tonumber,tostring,rawget
|
|
local type,next,rawset,tonumber,tostring,load,select=type,next,rawset,tonumber,tostring,load,select
|
|
local lpegmatch,P,Cs,Cc=lpeg.match,lpeg.P,lpeg.Cs,lpeg.Cc
|
|
local sortedkeys,sortedpairs=table.sortedkeys,table.sortedpairs
|
|
local formatters=string.formatters
|
|
local utftoeight=utf.toeight
|
|
local splitter=lpeg.tsplitat(".")
|
|
function utilities.tables.definetable(target,nofirst,nolast)
|
|
local composed=nil
|
|
local t={}
|
|
local snippets=lpegmatch(splitter,target)
|
|
for i=1,#snippets-(nolast and 1 or 0) do
|
|
local name=snippets[i]
|
|
if composed then
|
|
composed=composed.."."..name
|
|
t[#t+1]=formatters["if not %s then %s = { } end"](composed,composed)
|
|
else
|
|
composed=name
|
|
if not nofirst then
|
|
t[#t+1]=formatters["%s = %s or { }"](composed,composed)
|
|
end
|
|
end
|
|
end
|
|
if composed then
|
|
if nolast then
|
|
composed=composed.."."..snippets[#snippets]
|
|
end
|
|
return concat(t,"\n"),composed
|
|
else
|
|
return "",target
|
|
end
|
|
end
|
|
function tables.definedtable(...)
|
|
local t=_G
|
|
for i=1,select("#",...) do
|
|
local li=select(i,...)
|
|
local tl=t[li]
|
|
if not tl then
|
|
tl={}
|
|
t[li]=tl
|
|
end
|
|
t=tl
|
|
end
|
|
return t
|
|
end
|
|
function tables.accesstable(target,root)
|
|
local t=root or _G
|
|
for name in gmatch(target,"([^%.]+)") do
|
|
t=t[name]
|
|
if not t then
|
|
return
|
|
end
|
|
end
|
|
return t
|
|
end
|
|
function tables.migratetable(target,v,root)
|
|
local t=root or _G
|
|
local names=lpegmatch(splitter,target)
|
|
for i=1,#names-1 do
|
|
local name=names[i]
|
|
t[name]=t[name] or {}
|
|
t=t[name]
|
|
if not t then
|
|
return
|
|
end
|
|
end
|
|
t[names[#names]]=v
|
|
end
|
|
function tables.removevalue(t,value)
|
|
if value then
|
|
for i=1,#t do
|
|
if t[i]==value then
|
|
remove(t,i)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function tables.replacevalue(t,oldvalue,newvalue)
|
|
if oldvalue and newvalue then
|
|
for i=1,#t do
|
|
if t[i]==oldvalue then
|
|
t[i]=newvalue
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function tables.insertbeforevalue(t,value,extra)
|
|
for i=1,#t do
|
|
if t[i]==extra then
|
|
remove(t,i)
|
|
end
|
|
end
|
|
for i=1,#t do
|
|
if t[i]==value then
|
|
insert(t,i,extra)
|
|
return
|
|
end
|
|
end
|
|
insert(t,1,extra)
|
|
end
|
|
function tables.insertaftervalue(t,value,extra)
|
|
for i=1,#t do
|
|
if t[i]==extra then
|
|
remove(t,i)
|
|
end
|
|
end
|
|
for i=1,#t do
|
|
if t[i]==value then
|
|
insert(t,i+1,extra)
|
|
return
|
|
end
|
|
end
|
|
insert(t,#t+1,extra)
|
|
end
|
|
local escape=Cs(Cc('"')*((P('"')/'""'+P(1))^0)*Cc('"'))
|
|
function table.tocsv(t,specification)
|
|
if t and #t>0 then
|
|
local result={}
|
|
local r={}
|
|
specification=specification or {}
|
|
local fields=specification.fields
|
|
if type(fields)~="string" then
|
|
fields=sortedkeys(t[1])
|
|
end
|
|
local separator=specification.separator or ","
|
|
local noffields=#fields
|
|
if specification.preamble==true then
|
|
for f=1,noffields do
|
|
r[f]=lpegmatch(escape,tostring(fields[f]))
|
|
end
|
|
result[1]=concat(r,separator)
|
|
end
|
|
for i=1,#t do
|
|
local ti=t[i]
|
|
for f=1,noffields do
|
|
local field=ti[fields[f]]
|
|
if type(field)=="string" then
|
|
r[f]=lpegmatch(escape,field)
|
|
else
|
|
r[f]=tostring(field)
|
|
end
|
|
end
|
|
result[i+1]=concat(r,separator)
|
|
end
|
|
return concat(result,"\n")
|
|
else
|
|
return ""
|
|
end
|
|
end
|
|
local nspaces=utilities.strings.newrepeater(" ")
|
|
local function toxml(t,d,result,step)
|
|
local r=#result
|
|
for k,v in sortedpairs(t) do
|
|
local s=nspaces[d]
|
|
local tk=type(k)
|
|
local tv=type(v)
|
|
if tv=="table" then
|
|
if tk=="number" then
|
|
r=r+1 result[r]=formatters["%s<entry n='%s'>"](s,k)
|
|
toxml(v,d+step,result,step)
|
|
r=r+1 result[r]=formatters["%s</entry>"](s,k)
|
|
else
|
|
r=r+1 result[r]=formatters["%s<%s>"](s,k)
|
|
toxml(v,d+step,result,step)
|
|
r=r+1 result[r]=formatters["%s</%s>"](s,k)
|
|
end
|
|
elseif tv=="string" then
|
|
if tk=="number" then
|
|
r=r+1 result[r]=formatters["%s<entry n='%s'>%!xml!</entry>"](s,k,v,k)
|
|
else
|
|
r=r+1 result[r]=formatters["%s<%s>%!xml!</%s>"](s,k,v,k)
|
|
end
|
|
elseif tk=="number" then
|
|
r=r+1 result[r]=formatters["%s<entry n='%s'>%S</entry>"](s,k,v,k)
|
|
else
|
|
r=r+1 result[r]=formatters["%s<%s>%S</%s>"](s,k,v,k)
|
|
end
|
|
end
|
|
end
|
|
function table.toxml(t,specification)
|
|
specification=specification or {}
|
|
local name=specification.name
|
|
local noroot=name==false
|
|
local result=(specification.nobanner or noroot) and {} or { "<?xml version='1.0' standalone='yes' ?>" }
|
|
local indent=specification.indent or 0
|
|
local spaces=specification.spaces or 1
|
|
if noroot then
|
|
toxml(t,indent,result,spaces)
|
|
else
|
|
toxml({ [name or "data"]=t },indent,result,spaces)
|
|
end
|
|
return concat(result,"\n")
|
|
end
|
|
function tables.encapsulate(core,capsule,protect)
|
|
if type(capsule)~="table" then
|
|
protect=true
|
|
capsule={}
|
|
end
|
|
for key,value in next,core do
|
|
if capsule[key] then
|
|
print(formatters["\ninvalid %s %a in %a"]("inheritance",key,core))
|
|
os.exit()
|
|
else
|
|
capsule[key]=value
|
|
end
|
|
end
|
|
if protect then
|
|
for key,value in next,core do
|
|
core[key]=nil
|
|
end
|
|
setmetatable(core,{
|
|
__index=capsule,
|
|
__newindex=function(t,key,value)
|
|
if capsule[key] then
|
|
print(formatters["\ninvalid %s %a' in %a"]("overload",key,core))
|
|
os.exit()
|
|
else
|
|
rawset(t,key,value)
|
|
end
|
|
end
|
|
} )
|
|
end
|
|
end
|
|
if JITSUPPORTED then
|
|
local f_hashed_string=formatters["[%Q]=%Q,"]
|
|
local f_hashed_number=formatters["[%Q]=%s,"]
|
|
local f_hashed_boolean=formatters["[%Q]=%l,"]
|
|
local f_hashed_table=formatters["[%Q]="]
|
|
local f_indexed_string=formatters["[%s]=%Q,"]
|
|
local f_indexed_number=formatters["[%s]=%s,"]
|
|
local f_indexed_boolean=formatters["[%s]=%l,"]
|
|
local f_indexed_table=formatters["[%s]="]
|
|
local f_ordered_string=formatters["%Q,"]
|
|
local f_ordered_number=formatters["%s,"]
|
|
local f_ordered_boolean=formatters["%l,"]
|
|
function table.fastserialize(t,prefix)
|
|
local r={ type(prefix)=="string" and prefix or "return" }
|
|
local m=1
|
|
local function fastserialize(t,outer)
|
|
local n=#t
|
|
m=m+1
|
|
r[m]="{"
|
|
if n>0 then
|
|
local v=t[0]
|
|
if v then
|
|
local tv=type(v)
|
|
if tv=="string" then
|
|
m=m+1 r[m]=f_indexed_string(0,v)
|
|
elseif tv=="number" then
|
|
m=m+1 r[m]=f_indexed_number(0,v)
|
|
elseif tv=="table" then
|
|
m=m+1 r[m]=f_indexed_table(0)
|
|
fastserialize(v)
|
|
m=m+1 r[m]=f_indexed_table(0)
|
|
elseif tv=="boolean" then
|
|
m=m+1 r[m]=f_indexed_boolean(0,v)
|
|
end
|
|
end
|
|
for i=1,n do
|
|
local v=t[i]
|
|
local tv=type(v)
|
|
if tv=="string" then
|
|
m=m+1 r[m]=f_ordered_string(v)
|
|
elseif tv=="number" then
|
|
m=m+1 r[m]=f_ordered_number(v)
|
|
elseif tv=="table" then
|
|
fastserialize(v)
|
|
elseif tv=="boolean" then
|
|
m=m+1 r[m]=f_ordered_boolean(v)
|
|
end
|
|
end
|
|
end
|
|
for k,v in next,t do
|
|
local tk=type(k)
|
|
if tk=="number" then
|
|
if k>n or k<0 then
|
|
local tv=type(v)
|
|
if tv=="string" then
|
|
m=m+1 r[m]=f_indexed_string(k,v)
|
|
elseif tv=="number" then
|
|
m=m+1 r[m]=f_indexed_number(k,v)
|
|
elseif tv=="table" then
|
|
m=m+1 r[m]=f_indexed_table(k)
|
|
fastserialize(v)
|
|
elseif tv=="boolean" then
|
|
m=m+1 r[m]=f_indexed_boolean(k,v)
|
|
end
|
|
end
|
|
else
|
|
local tv=type(v)
|
|
if tv=="string" then
|
|
m=m+1 r[m]=f_hashed_string(k,v)
|
|
elseif tv=="number" then
|
|
m=m+1 r[m]=f_hashed_number(k,v)
|
|
elseif tv=="table" then
|
|
m=m+1 r[m]=f_hashed_table(k)
|
|
fastserialize(v)
|
|
elseif tv=="boolean" then
|
|
m=m+1 r[m]=f_hashed_boolean(k,v)
|
|
end
|
|
end
|
|
end
|
|
m=m+1
|
|
if outer then
|
|
r[m]="}"
|
|
else
|
|
r[m]="},"
|
|
end
|
|
return r
|
|
end
|
|
return concat(fastserialize(t,true))
|
|
end
|
|
else
|
|
local f_v=formatters["[%q]=%q,"]
|
|
local f_t=formatters["[%q]="]
|
|
local f_q=formatters["%q,"]
|
|
function table.fastserialize(t,prefix)
|
|
local r={ type(prefix)=="string" and prefix or "return" }
|
|
local m=1
|
|
local function fastserialize(t,outer)
|
|
local n=#t
|
|
m=m+1
|
|
r[m]="{"
|
|
if n>0 then
|
|
local v=t[0]
|
|
if v then
|
|
m=m+1
|
|
r[m]="[0]='"
|
|
if type(v)=="table" then
|
|
fastserialize(v)
|
|
else
|
|
r[m]=format("%q,",v)
|
|
end
|
|
end
|
|
for i=1,n do
|
|
local v=t[i]
|
|
m=m+1
|
|
if type(v)=="table" then
|
|
r[m]=format("[%i]=",i)
|
|
fastserialize(v)
|
|
else
|
|
r[m]=format("[%i]=%q,",i,v)
|
|
end
|
|
end
|
|
end
|
|
for k,v in next,t do
|
|
local tk=type(k)
|
|
if tk=="number" then
|
|
if k>n or k<0 then
|
|
m=m+1
|
|
if type(v)=="table" then
|
|
r[m]=format("[%i]=",k)
|
|
fastserialize(v)
|
|
else
|
|
r[m]=format("[%i]=%q,",k,v)
|
|
end
|
|
end
|
|
else
|
|
m=m+1
|
|
if type(v)=="table" then
|
|
r[m]=format("[%q]=",k)
|
|
fastserialize(v)
|
|
else
|
|
r[m]=format("[%q]=%q,",k,v)
|
|
end
|
|
end
|
|
end
|
|
m=m+1
|
|
if outer then
|
|
r[m]="}"
|
|
else
|
|
r[m]="},"
|
|
end
|
|
return r
|
|
end
|
|
return concat(fastserialize(t,true))
|
|
end
|
|
end
|
|
function table.deserialize(str)
|
|
if not str or str=="" then
|
|
return
|
|
end
|
|
local code=load(str)
|
|
if not code then
|
|
return
|
|
end
|
|
code=code()
|
|
if not code then
|
|
return
|
|
end
|
|
return code
|
|
end
|
|
function table.load(filename,loader)
|
|
if filename then
|
|
local t=(loader or io.loaddata)(filename)
|
|
if t and t~="" then
|
|
local t=utftoeight(t)
|
|
t=load(t)
|
|
if type(t)=="function" then
|
|
t=t()
|
|
if type(t)=="table" then
|
|
return t
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function table.save(filename,t,n,...)
|
|
io.savedata(filename,table.serialize(t,n==nil and true or n,...))
|
|
end
|
|
local f_key_value=formatters["%s=%q"]
|
|
local f_add_table=formatters[" {%t},\n"]
|
|
local f_return_table=formatters["return {\n%t}"]
|
|
local function slowdrop(t)
|
|
local r={}
|
|
local l={}
|
|
for i=1,#t do
|
|
local ti=t[i]
|
|
local j=0
|
|
for k,v in next,ti do
|
|
j=j+1
|
|
l[j]=f_key_value(k,v)
|
|
end
|
|
r[i]=f_add_table(l)
|
|
end
|
|
return f_return_table(r)
|
|
end
|
|
local function fastdrop(t)
|
|
local r={ "return {\n" }
|
|
local m=1
|
|
for i=1,#t do
|
|
local ti=t[i]
|
|
m=m+1 r[m]=" {"
|
|
for k,v in next,ti do
|
|
m=m+1 r[m]=f_key_value(k,v)
|
|
end
|
|
m=m+1 r[m]="},\n"
|
|
end
|
|
m=m+1
|
|
r[m]="}"
|
|
return concat(r)
|
|
end
|
|
function table.drop(t,slow)
|
|
if #t==0 then
|
|
return "return { }"
|
|
elseif slow==true then
|
|
return slowdrop(t)
|
|
else
|
|
return fastdrop(t)
|
|
end
|
|
end
|
|
local selfmapper={ __index=function(t,k) t[k]=k return k end }
|
|
function table.twowaymapper(t)
|
|
if not t then
|
|
t={}
|
|
else
|
|
local zero=rawget(t,0)
|
|
for i=zero and 0 or 1,#t do
|
|
local ti=t[i]
|
|
if ti then
|
|
local i=tostring(i)
|
|
t[i]=ti
|
|
t[ti]=i
|
|
end
|
|
end
|
|
end
|
|
setmetatable(t,selfmapper)
|
|
return t
|
|
end
|
|
local f_start_key_idx=formatters["%w{"]
|
|
local f_start_key_num=JITSUPPORTED and formatters["%w[%s]={"] or formatters["%w[%q]={"]
|
|
local f_start_key_str=formatters["%w[%q]={"]
|
|
local f_start_key_boo=formatters["%w[%l]={"]
|
|
local f_start_key_nop=formatters["%w{"]
|
|
local f_stop=formatters["%w},"]
|
|
local f_key_num_value_num=JITSUPPORTED and formatters["%w[%s]=%s,"] or formatters["%w[%s]=%q,"]
|
|
local f_key_str_value_num=JITSUPPORTED and formatters["%w[%Q]=%s,"] or formatters["%w[%Q]=%q,"]
|
|
local f_key_boo_value_num=JITSUPPORTED and formatters["%w[%l]=%s,"] or formatters["%w[%l]=%q,"]
|
|
local f_key_num_value_str=JITSUPPORTED and formatters["%w[%s]=%Q,"] or formatters["%w[%q]=%Q,"]
|
|
local f_key_str_value_str=formatters["%w[%Q]=%Q,"]
|
|
local f_key_boo_value_str=formatters["%w[%l]=%Q,"]
|
|
local f_key_num_value_boo=JITSUPPORTED and formatters["%w[%s]=%l,"] or formatters["%w[%q]=%l,"]
|
|
local f_key_str_value_boo=formatters["%w[%Q]=%l,"]
|
|
local f_key_boo_value_boo=formatters["%w[%l]=%l,"]
|
|
local f_key_num_value_not=JITSUPPORTED and formatters["%w[%s]={},"] or formatters["%w[%q]={},"]
|
|
local f_key_str_value_not=formatters["%w[%Q]={},"]
|
|
local f_key_boo_value_not=formatters["%w[%l]={},"]
|
|
local f_key_num_value_seq=JITSUPPORTED and formatters["%w[%s]={ %, t },"] or formatters["%w[%q]={ %, t },"]
|
|
local f_key_str_value_seq=formatters["%w[%Q]={ %, t },"]
|
|
local f_key_boo_value_seq=formatters["%w[%l]={ %, t },"]
|
|
local f_val_num=JITSUPPORTED and formatters["%w%s,"] or formatters["%w%q,"]
|
|
local f_val_str=formatters["%w%Q,"]
|
|
local f_val_boo=formatters["%w%l,"]
|
|
local f_val_not=formatters["%w{},"]
|
|
local f_val_seq=formatters["%w{ %, t },"]
|
|
local f_fin_seq=formatters[" %, t }"]
|
|
local f_table_return=formatters["return {"]
|
|
local f_table_name=formatters["%s={"]
|
|
local f_table_direct=formatters["{"]
|
|
local f_table_entry=formatters["[%Q]={"]
|
|
local f_table_finish=formatters["}"]
|
|
local spaces=utilities.strings.newrepeater(" ")
|
|
local original_serialize=table.serialize
|
|
local is_simple_table=table.is_simple_table
|
|
local function serialize(root,name,specification)
|
|
if type(specification)=="table" then
|
|
return original_serialize(root,name,specification)
|
|
end
|
|
local t
|
|
local n=1
|
|
local unknown=false
|
|
local function do_serialize(root,name,depth,level,indexed)
|
|
if level>0 then
|
|
n=n+1
|
|
if indexed then
|
|
t[n]=f_start_key_idx(depth)
|
|
else
|
|
local tn=type(name)
|
|
if tn=="number" then
|
|
t[n]=f_start_key_num(depth,name)
|
|
elseif tn=="string" then
|
|
t[n]=f_start_key_str(depth,name)
|
|
elseif tn=="boolean" then
|
|
t[n]=f_start_key_boo(depth,name)
|
|
else
|
|
t[n]=f_start_key_nop(depth)
|
|
end
|
|
end
|
|
depth=depth+1
|
|
end
|
|
if root and next(root)~=nil then
|
|
local first=nil
|
|
local last=#root
|
|
if last>0 then
|
|
for k=1,last do
|
|
if rawget(root,k)==nil then
|
|
last=k-1
|
|
break
|
|
end
|
|
end
|
|
if last>0 then
|
|
first=1
|
|
end
|
|
end
|
|
local sk=sortedkeys(root)
|
|
for i=1,#sk do
|
|
local k=sk[i]
|
|
local v=root[k]
|
|
local tv=type(v)
|
|
local tk=type(k)
|
|
if first and tk=="number" and k<=last and k>=first then
|
|
if tv=="number" then
|
|
n=n+1 t[n]=f_val_num(depth,v)
|
|
elseif tv=="string" then
|
|
n=n+1 t[n]=f_val_str(depth,v)
|
|
elseif tv=="table" then
|
|
if next(v)==nil then
|
|
n=n+1 t[n]=f_val_not(depth)
|
|
else
|
|
local st=is_simple_table(v)
|
|
if st then
|
|
n=n+1 t[n]=f_val_seq(depth,st)
|
|
else
|
|
do_serialize(v,k,depth,level+1,true)
|
|
end
|
|
end
|
|
elseif tv=="boolean" then
|
|
n=n+1 t[n]=f_val_boo(depth,v)
|
|
elseif unknown then
|
|
n=n+1 t[n]=f_val_str(depth,tostring(v))
|
|
end
|
|
elseif tv=="number" then
|
|
if tk=="number" then
|
|
n=n+1 t[n]=f_key_num_value_num(depth,k,v)
|
|
elseif tk=="string" then
|
|
n=n+1 t[n]=f_key_str_value_num(depth,k,v)
|
|
elseif tk=="boolean" then
|
|
n=n+1 t[n]=f_key_boo_value_num(depth,k,v)
|
|
elseif unknown then
|
|
n=n+1 t[n]=f_key_str_value_num(depth,tostring(k),v)
|
|
end
|
|
elseif tv=="string" then
|
|
if tk=="number" then
|
|
n=n+1 t[n]=f_key_num_value_str(depth,k,v)
|
|
elseif tk=="string" then
|
|
n=n+1 t[n]=f_key_str_value_str(depth,k,v)
|
|
elseif tk=="boolean" then
|
|
n=n+1 t[n]=f_key_boo_value_str(depth,k,v)
|
|
elseif unknown then
|
|
n=n+1 t[n]=f_key_str_value_str(depth,tostring(k),v)
|
|
end
|
|
elseif tv=="table" then
|
|
if next(v)==nil then
|
|
if tk=="number" then
|
|
n=n+1 t[n]=f_key_num_value_not(depth,k)
|
|
elseif tk=="string" then
|
|
n=n+1 t[n]=f_key_str_value_not(depth,k)
|
|
elseif tk=="boolean" then
|
|
n=n+1 t[n]=f_key_boo_value_not(depth,k)
|
|
elseif unknown then
|
|
n=n+1 t[n]=f_key_str_value_not(depth,tostring(k))
|
|
end
|
|
else
|
|
local st=is_simple_table(v)
|
|
if not st then
|
|
do_serialize(v,k,depth,level+1)
|
|
elseif tk=="number" then
|
|
n=n+1 t[n]=f_key_num_value_seq(depth,k,st)
|
|
elseif tk=="string" then
|
|
n=n+1 t[n]=f_key_str_value_seq(depth,k,st)
|
|
elseif tk=="boolean" then
|
|
n=n+1 t[n]=f_key_boo_value_seq(depth,k,st)
|
|
elseif unknown then
|
|
n=n+1 t[n]=f_key_str_value_seq(depth,tostring(k),st)
|
|
end
|
|
end
|
|
elseif tv=="boolean" then
|
|
if tk=="number" then
|
|
n=n+1 t[n]=f_key_num_value_boo(depth,k,v)
|
|
elseif tk=="string" then
|
|
n=n+1 t[n]=f_key_str_value_boo(depth,k,v)
|
|
elseif tk=="boolean" then
|
|
n=n+1 t[n]=f_key_boo_value_boo(depth,k,v)
|
|
elseif unknown then
|
|
n=n+1 t[n]=f_key_str_value_boo(depth,tostring(k),v)
|
|
end
|
|
else
|
|
if tk=="number" then
|
|
n=n+1 t[n]=f_key_num_value_str(depth,k,tostring(v))
|
|
elseif tk=="string" then
|
|
n=n+1 t[n]=f_key_str_value_str(depth,k,tostring(v))
|
|
elseif tk=="boolean" then
|
|
n=n+1 t[n]=f_key_boo_value_str(depth,k,tostring(v))
|
|
elseif unknown then
|
|
n=n+1 t[n]=f_key_str_value_str(depth,tostring(k),tostring(v))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if level>0 then
|
|
n=n+1 t[n]=f_stop(depth-1)
|
|
end
|
|
end
|
|
local tname=type(name)
|
|
if tname=="string" then
|
|
if name=="return" then
|
|
t={ f_table_return() }
|
|
else
|
|
t={ f_table_name(name) }
|
|
end
|
|
elseif tname=="number" then
|
|
t={ f_table_entry(name) }
|
|
elseif tname=="boolean" then
|
|
if name then
|
|
t={ f_table_return() }
|
|
else
|
|
t={ f_table_direct() }
|
|
end
|
|
else
|
|
t={ f_table_name("t") }
|
|
end
|
|
if root then
|
|
if getmetatable(root) then
|
|
local dummy=root._w_h_a_t_e_v_e_r_
|
|
root._w_h_a_t_e_v_e_r_=nil
|
|
end
|
|
if next(root)~=nil then
|
|
local st=is_simple_table(root)
|
|
if st then
|
|
return t[1]..f_fin_seq(st)
|
|
else
|
|
do_serialize(root,name,1,0)
|
|
end
|
|
end
|
|
end
|
|
n=n+1
|
|
t[n]=f_table_finish()
|
|
return concat(t,"\n")
|
|
end
|
|
table.serialize=serialize
|
|
if setinspector then
|
|
setinspector("table",function(v)
|
|
if type(v)=="table" then
|
|
print(serialize(v,"table",{ metacheck=false }))
|
|
return true
|
|
end
|
|
end)
|
|
end
|
|
local mt={
|
|
__newindex=function(t,k,v)
|
|
local n=t.last+1
|
|
t.last=n
|
|
t.list[n]=k
|
|
t.hash[k]=v
|
|
end,
|
|
__index=function(t,k)
|
|
return t.hash[k]
|
|
end,
|
|
__len=function(t)
|
|
return t.last
|
|
end,
|
|
}
|
|
function table.orderedhash()
|
|
return setmetatable({ list={},hash={},last=0 },mt)
|
|
end
|
|
function table.ordered(t)
|
|
local n=t.last
|
|
if n>0 then
|
|
local l=t.list
|
|
local i=1
|
|
local h=t.hash
|
|
local f=function()
|
|
if i<=n then
|
|
local k=i
|
|
local v=h[l[k]]
|
|
i=i+1
|
|
return k,v
|
|
end
|
|
end
|
|
return f,1,h[l[1]]
|
|
else
|
|
return function() end
|
|
end
|
|
end
|
|
|
|
end -- closure
|
|
|
|
do -- begin closure to overcome local limits and interference
|
|
|
|
if not modules then modules={} end modules ['util-sto']={
|
|
version=1.001,
|
|
comment="companion to luat-lib.mkiv",
|
|
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
copyright="PRAGMA ADE / ConTeXt Development Team",
|
|
license="see context related readme files"
|
|
}
|
|
local setmetatable,getmetatable,rawset,type=setmetatable,getmetatable,rawset,type
|
|
utilities=utilities or {}
|
|
utilities.storage=utilities.storage or {}
|
|
local storage=utilities.storage
|
|
function storage.mark(t)
|
|
if not t then
|
|
print("\nfatal error: storage cannot be marked\n")
|
|
os.exit()
|
|
return
|
|
end
|
|
local m=getmetatable(t)
|
|
if not m then
|
|
m={}
|
|
setmetatable(t,m)
|
|
end
|
|
m.__storage__=true
|
|
return t
|
|
end
|
|
function storage.allocate(t)
|
|
t=t or {}
|
|
local m=getmetatable(t)
|
|
if not m then
|
|
m={}
|
|
setmetatable(t,m)
|
|
end
|
|
m.__storage__=true
|
|
return t
|
|
end
|
|
function storage.marked(t)
|
|
local m=getmetatable(t)
|
|
return m and m.__storage__
|
|
end
|
|
function storage.checked(t)
|
|
if not t then
|
|
report("\nfatal error: storage has not been allocated\n")
|
|
os.exit()
|
|
return
|
|
end
|
|
return t
|
|
end
|
|
function storage.setinitializer(data,initialize)
|
|
local m=getmetatable(data) or {}
|
|
m.__index=function(data,k)
|
|
m.__index=nil
|
|
initialize()
|
|
return data[k]
|
|
end
|
|
setmetatable(data,m)
|
|
end
|
|
local keyisvalue={ __index=function(t,k)
|
|
t[k]=k
|
|
return k
|
|
end }
|
|
function storage.sparse(t)
|
|
t=t or {}
|
|
setmetatable(t,keyisvalue)
|
|
return t
|
|
end
|
|
local function f_empty () return "" end
|
|
local function f_self (t,k) t[k]=k return k end
|
|
local function f_table (t,k) local v={} t[k]=v return v end
|
|
local function f_number(t,k) t[k]=0 return 0 end
|
|
local function f_ignore() end
|
|
local f_index={
|
|
["empty"]=f_empty,
|
|
["self"]=f_self,
|
|
["table"]=f_table,
|
|
["number"]=f_number,
|
|
}
|
|
function table.setmetatableindex(t,f)
|
|
if type(t)~="table" then
|
|
f,t=t,{}
|
|
end
|
|
local m=getmetatable(t)
|
|
local i=f_index[f] or f
|
|
if m then
|
|
m.__index=i
|
|
else
|
|
setmetatable(t,{ __index=i })
|
|
end
|
|
return t
|
|
end
|
|
local f_index={
|
|
["ignore"]=f_ignore,
|
|
}
|
|
function table.setmetatablenewindex(t,f)
|
|
if type(t)~="table" then
|
|
f,t=t,{}
|
|
end
|
|
local m=getmetatable(t)
|
|
local i=f_index[f] or f
|
|
if m then
|
|
m.__newindex=i
|
|
else
|
|
setmetatable(t,{ __newindex=i })
|
|
end
|
|
return t
|
|
end
|
|
function table.setmetatablecall(t,f)
|
|
if type(t)~="table" then
|
|
f,t=t,{}
|
|
end
|
|
local m=getmetatable(t)
|
|
if m then
|
|
m.__call=f
|
|
else
|
|
setmetatable(t,{ __call=f })
|
|
end
|
|
return t
|
|
end
|
|
function table.setmetatableindices(t,f,n,c)
|
|
if type(t)~="table" then
|
|
f,t=t,{}
|
|
end
|
|
local m=getmetatable(t)
|
|
local i=f_index[f] or f
|
|
if m then
|
|
m.__index=i
|
|
m.__newindex=n
|
|
m.__call=c
|
|
else
|
|
setmetatable(t,{
|
|
__index=i,
|
|
__newindex=n,
|
|
__call=c,
|
|
})
|
|
end
|
|
return t
|
|
end
|
|
function table.setmetatablekey(t,key,value)
|
|
local m=getmetatable(t)
|
|
if not m then
|
|
m={}
|
|
setmetatable(t,m)
|
|
end
|
|
m[key]=value
|
|
return t
|
|
end
|
|
function table.getmetatablekey(t,key,value)
|
|
local m=getmetatable(t)
|
|
return m and m[key]
|
|
end
|
|
function table.makeweak(t)
|
|
if not t then
|
|
t={}
|
|
end
|
|
local m=getmetatable(t)
|
|
if m then
|
|
m.__mode="v"
|
|
else
|
|
setmetatable(t,{ __mode="v" })
|
|
end
|
|
return t
|
|
end
|
|
|
|
end -- closure
|
|
|
|
do -- begin closure to overcome local limits and interference
|
|
|
|
if not modules then modules={} end modules ['util-prs']={
|
|
version=1.001,
|
|
comment="companion to luat-lib.mkiv",
|
|
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
copyright="PRAGMA ADE / ConTeXt Development Team",
|
|
license="see context related readme files"
|
|
}
|
|
local lpeg,table,string=lpeg,table,string
|
|
local P,R,V,S,C,Ct,Cs,Carg,Cc,Cg,Cf,Cp=lpeg.P,lpeg.R,lpeg.V,lpeg.S,lpeg.C,lpeg.Ct,lpeg.Cs,lpeg.Carg,lpeg.Cc,lpeg.Cg,lpeg.Cf,lpeg.Cp
|
|
local lpegmatch,lpegpatterns=lpeg.match,lpeg.patterns
|
|
local concat,gmatch,find=table.concat,string.gmatch,string.find
|
|
local tostring,type,next,rawset=tostring,type,next,rawset
|
|
local mod,div=math.mod,math.div
|
|
utilities=utilities or {}
|
|
local parsers=utilities.parsers or {}
|
|
utilities.parsers=parsers
|
|
local patterns=parsers.patterns or {}
|
|
parsers.patterns=patterns
|
|
local setmetatableindex=table.setmetatableindex
|
|
local sortedhash=table.sortedhash
|
|
local sortedkeys=table.sortedkeys
|
|
local tohash=table.tohash
|
|
local hashes={}
|
|
parsers.hashes=hashes
|
|
local digit=R("09")
|
|
local space=P(' ')
|
|
local equal=P("=")
|
|
local colon=P(":")
|
|
local comma=P(",")
|
|
local lbrace=P("{")
|
|
local rbrace=P("}")
|
|
local lparent=P("(")
|
|
local rparent=P(")")
|
|
local lbracket=P("[")
|
|
local rbracket=P("]")
|
|
local period=S(".")
|
|
local punctuation=S(".,:;")
|
|
local spacer=lpegpatterns.spacer
|
|
local whitespace=lpegpatterns.whitespace
|
|
local newline=lpegpatterns.newline
|
|
local anything=lpegpatterns.anything
|
|
local endofstring=lpegpatterns.endofstring
|
|
local nobrace=1-(lbrace+rbrace )
|
|
local noparent=1-(lparent+rparent)
|
|
local nobracket=1-(lbracket+rbracket)
|
|
local escape,left,right=P("\\"),P('{'),P('}')
|
|
lpegpatterns.balanced=P {
|
|
[1]=((escape*(left+right))+(1-(left+right))+V(2))^0,
|
|
[2]=left*V(1)*right
|
|
}
|
|
local nestedbraces=P { lbrace*(nobrace+V(1))^0*rbrace }
|
|
local nestedparents=P { lparent*(noparent+V(1))^0*rparent }
|
|
local nestedbrackets=P { lbracket*(nobracket+V(1))^0*rbracket }
|
|
local spaces=space^0
|
|
local argument=Cs((lbrace/"")*((nobrace+nestedbraces)^0)*(rbrace/""))
|
|
local content=(1-endofstring)^0
|
|
lpegpatterns.nestedbraces=nestedbraces
|
|
lpegpatterns.nestedparents=nestedparents
|
|
lpegpatterns.nested=nestedbraces
|
|
lpegpatterns.argument=argument
|
|
lpegpatterns.content=content
|
|
local value=lbrace*C((nobrace+nestedbraces)^0)*rbrace+C((nestedbraces+(1-comma))^0)
|
|
local key=C((1-equal-comma)^1)
|
|
local pattern_a=(space+comma)^0*(key*equal*value+key*C(""))
|
|
local pattern_c=(space+comma)^0*(key*equal*value)
|
|
local pattern_d=(space+comma)^0*(key*(equal+colon)*value+key*C(""))
|
|
local key=C((1-space-equal-comma)^1)
|
|
local pattern_b=spaces*comma^0*spaces*(key*((spaces*equal*spaces*value)+C("")))
|
|
local hash={}
|
|
local function set(key,value)
|
|
hash[key]=value
|
|
end
|
|
local pattern_a_s=(pattern_a/set)^1
|
|
local pattern_b_s=(pattern_b/set)^1
|
|
local pattern_c_s=(pattern_c/set)^1
|
|
local pattern_d_s=(pattern_d/set)^1
|
|
patterns.settings_to_hash_a=pattern_a_s
|
|
patterns.settings_to_hash_b=pattern_b_s
|
|
patterns.settings_to_hash_c=pattern_c_s
|
|
patterns.settings_to_hash_d=pattern_d_s
|
|
function parsers.make_settings_to_hash_pattern(set,how)
|
|
if how=="strict" then
|
|
return (pattern_c/set)^1
|
|
elseif how=="tolerant" then
|
|
return (pattern_b/set)^1
|
|
else
|
|
return (pattern_a/set)^1
|
|
end
|
|
end
|
|
function parsers.settings_to_hash(str,existing)
|
|
if not str or str=="" then
|
|
return {}
|
|
elseif type(str)=="table" then
|
|
if existing then
|
|
for k,v in next,str do
|
|
existing[k]=v
|
|
end
|
|
return exiting
|
|
else
|
|
return str
|
|
end
|
|
else
|
|
hash=existing or {}
|
|
lpegmatch(pattern_a_s,str)
|
|
return hash
|
|
end
|
|
end
|
|
function parsers.settings_to_hash_colon_too(str)
|
|
if not str or str=="" then
|
|
return {}
|
|
elseif type(str)=="table" then
|
|
return str
|
|
else
|
|
hash={}
|
|
lpegmatch(pattern_d_s,str)
|
|
return hash
|
|
end
|
|
end
|
|
function parsers.settings_to_hash_tolerant(str,existing)
|
|
if not str or str=="" then
|
|
return {}
|
|
elseif type(str)=="table" then
|
|
if existing then
|
|
for k,v in next,str do
|
|
existing[k]=v
|
|
end
|
|
return exiting
|
|
else
|
|
return str
|
|
end
|
|
else
|
|
hash=existing or {}
|
|
lpegmatch(pattern_b_s,str)
|
|
return hash
|
|
end
|
|
end
|
|
function parsers.settings_to_hash_strict(str,existing)
|
|
if not str or str=="" then
|
|
return nil
|
|
elseif type(str)=="table" then
|
|
if existing then
|
|
for k,v in next,str do
|
|
existing[k]=v
|
|
end
|
|
return exiting
|
|
else
|
|
return str
|
|
end
|
|
elseif str and str~="" then
|
|
hash=existing or {}
|
|
lpegmatch(pattern_c_s,str)
|
|
return next(hash) and hash
|
|
end
|
|
end
|
|
local separator=comma*space^0
|
|
local value=lbrace*C((nobrace+nestedbraces)^0)*rbrace+C((nestedbraces+(1-comma))^0)
|
|
local pattern=spaces*Ct(value*(separator*value)^0)
|
|
patterns.settings_to_array=pattern
|
|
function parsers.settings_to_array(str,strict)
|
|
if not str or str=="" then
|
|
return {}
|
|
elseif type(str)=="table" then
|
|
return str
|
|
elseif strict then
|
|
if find(str,"{",1,true) then
|
|
return lpegmatch(pattern,str)
|
|
else
|
|
return { str }
|
|
end
|
|
elseif find(str,",",1,true) then
|
|
return lpegmatch(pattern,str)
|
|
else
|
|
return { str }
|
|
end
|
|
end
|
|
function parsers.settings_to_numbers(str)
|
|
if not str or str=="" then
|
|
return {}
|
|
end
|
|
if type(str)=="table" then
|
|
elseif find(str,",",1,true) then
|
|
str=lpegmatch(pattern,str)
|
|
else
|
|
return { tonumber(str) }
|
|
end
|
|
for i=1,#str do
|
|
str[i]=tonumber(str[i])
|
|
end
|
|
return str
|
|
end
|
|
local value=lbrace*C((nobrace+nestedbraces)^0)*rbrace+C((nestedbraces+nestedbrackets+nestedparents+(1-comma))^0)
|
|
local pattern=spaces*Ct(value*(separator*value)^0)
|
|
function parsers.settings_to_array_obey_fences(str)
|
|
return lpegmatch(pattern,str)
|
|
end
|
|
local cache_a={}
|
|
local cache_b={}
|
|
function parsers.groupedsplitat(symbol,withaction)
|
|
if not symbol then
|
|
symbol=","
|
|
end
|
|
local pattern=(withaction and cache_b or cache_a)[symbol]
|
|
if not pattern then
|
|
local symbols=S(symbol)
|
|
local separator=space^0*symbols*space^0
|
|
local value=lbrace*C((nobrace+nestedbraces)^0)*rbrace+C((nestedbraces+(1-(space^0*(symbols+P(-1)))))^0)
|
|
if withaction then
|
|
local withvalue=Carg(1)*value/function(f,s) return f(s) end
|
|
pattern=spaces*withvalue*(separator*withvalue)^0
|
|
cache_b[symbol]=pattern
|
|
else
|
|
pattern=spaces*Ct(value*(separator*value)^0)
|
|
cache_a[symbol]=pattern
|
|
end
|
|
end
|
|
return pattern
|
|
end
|
|
local pattern_a=parsers.groupedsplitat(",",false)
|
|
local pattern_b=parsers.groupedsplitat(",",true)
|
|
function parsers.stripped_settings_to_array(str)
|
|
if not str or str=="" then
|
|
return {}
|
|
else
|
|
return lpegmatch(pattern_a,str)
|
|
end
|
|
end
|
|
function parsers.process_stripped_settings(str,action)
|
|
if not str or str=="" then
|
|
return {}
|
|
else
|
|
return lpegmatch(pattern_b,str,1,action)
|
|
end
|
|
end
|
|
local function set(t,v)
|
|
t[#t+1]=v
|
|
end
|
|
local value=P(Carg(1)*value)/set
|
|
local pattern=value*(separator*value)^0*Carg(1)
|
|
function parsers.add_settings_to_array(t,str)
|
|
return lpegmatch(pattern,str,nil,t)
|
|
end
|
|
function parsers.hash_to_string(h,separator,yes,no,strict,omit)
|
|
if h then
|
|
local t={}
|
|
local tn=0
|
|
local s=sortedkeys(h)
|
|
omit=omit and tohash(omit)
|
|
for i=1,#s do
|
|
local key=s[i]
|
|
if not omit or not omit[key] then
|
|
local value=h[key]
|
|
if type(value)=="boolean" then
|
|
if yes and no then
|
|
if value then
|
|
tn=tn+1
|
|
t[tn]=key..'='..yes
|
|
elseif not strict then
|
|
tn=tn+1
|
|
t[tn]=key..'='..no
|
|
end
|
|
elseif value or not strict then
|
|
tn=tn+1
|
|
t[tn]=key..'='..tostring(value)
|
|
end
|
|
else
|
|
tn=tn+1
|
|
t[tn]=key..'='..value
|
|
end
|
|
end
|
|
end
|
|
return concat(t,separator or ",")
|
|
else
|
|
return ""
|
|
end
|
|
end
|
|
function parsers.array_to_string(a,separator)
|
|
if a then
|
|
return concat(a,separator or ",")
|
|
else
|
|
return ""
|
|
end
|
|
end
|
|
local pattern=Cf(Ct("")*Cg(C((1-S(", "))^1)*S(", ")^0*Cc(true))^1,rawset)
|
|
function parsers.settings_to_set(str)
|
|
return str and lpegmatch(pattern,str) or {}
|
|
end
|
|
hashes.settings_to_set=table.setmetatableindex(function(t,k)
|
|
local v=k and lpegmatch(pattern,k) or {}
|
|
t[k]=v
|
|
return v
|
|
end)
|
|
getmetatable(hashes.settings_to_set).__mode="kv"
|
|
function parsers.simple_hash_to_string(h,separator)
|
|
local t={}
|
|
local tn=0
|
|
for k,v in sortedhash(h) do
|
|
if v then
|
|
tn=tn+1
|
|
t[tn]=k
|
|
end
|
|
end
|
|
return concat(t,separator or ",")
|
|
end
|
|
local str=Cs(lpegpatterns.unquoted)+C((1-whitespace-equal)^1)
|
|
local setting=Cf(Carg(1)*(whitespace^0*Cg(str*whitespace^0*(equal*whitespace^0*str+Cc(""))))^1,rawset)
|
|
local splitter=setting^1
|
|
function parsers.options_to_hash(str,target)
|
|
return str and lpegmatch(splitter,str,1,target or {}) or {}
|
|
end
|
|
local splitter=lpeg.tsplitat(" ")
|
|
function parsers.options_to_array(str)
|
|
return str and lpegmatch(splitter,str) or {}
|
|
end
|
|
local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+C(digit^1*lparent*(noparent+nestedparents)^1*rparent)+C((nestedbraces+(1-comma))^1)
|
|
local pattern_a=spaces*Ct(value*(separator*value)^0)
|
|
local function repeater(n,str)
|
|
if not n then
|
|
return str
|
|
else
|
|
local s=lpegmatch(pattern_a,str)
|
|
if n==1 then
|
|
return unpack(s)
|
|
else
|
|
local t={}
|
|
local tn=0
|
|
for i=1,n do
|
|
for j=1,#s do
|
|
tn=tn+1
|
|
t[tn]=s[j]
|
|
end
|
|
end
|
|
return unpack(t)
|
|
end
|
|
end
|
|
end
|
|
local value=P(lbrace*C((nobrace+nestedbraces)^0)*rbrace)+(C(digit^1)/tonumber*lparent*Cs((noparent+nestedparents)^1)*rparent)/repeater+C((nestedbraces+(1-comma))^1)
|
|
local pattern_b=spaces*Ct(value*(separator*value)^0)
|
|
function parsers.settings_to_array_with_repeat(str,expand)
|
|
if expand then
|
|
return lpegmatch(pattern_b,str) or {}
|
|
else
|
|
return lpegmatch(pattern_a,str) or {}
|
|
end
|
|
end
|
|
local value=lbrace*C((nobrace+nestedbraces)^0)*rbrace
|
|
local pattern=Ct((space+value)^0)
|
|
function parsers.arguments_to_table(str)
|
|
return lpegmatch(pattern,str)
|
|
end
|
|
function parsers.getparameters(self,class,parentclass,settings)
|
|
local sc=self[class]
|
|
if not sc then
|
|
sc={}
|
|
self[class]=sc
|
|
if parentclass then
|
|
local sp=self[parentclass]
|
|
if not sp then
|
|
sp={}
|
|
self[parentclass]=sp
|
|
end
|
|
setmetatableindex(sc,sp)
|
|
end
|
|
end
|
|
parsers.settings_to_hash(settings,sc)
|
|
end
|
|
function parsers.listitem(str)
|
|
return gmatch(str,"[^, ]+")
|
|
end
|
|
local pattern=Cs { "start",
|
|
start=V("one")+V("two")+V("three"),
|
|
rest=(Cc(",")*V("thousand"))^0*(P(".")+endofstring)*anything^0,
|
|
thousand=digit*digit*digit,
|
|
one=digit*V("rest"),
|
|
two=digit*digit*V("rest"),
|
|
three=V("thousand")*V("rest"),
|
|
}
|
|
lpegpatterns.splitthousands=pattern
|
|
function parsers.splitthousands(str)
|
|
return lpegmatch(pattern,str) or str
|
|
end
|
|
local optionalwhitespace=whitespace^0
|
|
lpegpatterns.words=Ct((Cs((1-punctuation-whitespace)^1)+anything)^1)
|
|
lpegpatterns.sentences=Ct((optionalwhitespace*Cs((1-period)^0*period))^1)
|
|
lpegpatterns.paragraphs=Ct((optionalwhitespace*Cs((whitespace^1*endofstring/""+1-(spacer^0*newline*newline))^1))^1)
|
|
local dquote=P('"')
|
|
local equal=P('=')
|
|
local escape=P('\\')
|
|
local separator=S(' ,')
|
|
local key=C((1-equal)^1)
|
|
local value=dquote*C((1-dquote-escape*dquote)^0)*dquote
|
|
local pattern=Cf(Ct("")*(Cg(key*equal*value)*separator^0)^1,rawset)^0*P(-1)
|
|
function parsers.keq_to_hash(str)
|
|
if str and str~="" then
|
|
return lpegmatch(pattern,str)
|
|
else
|
|
return {}
|
|
end
|
|
end
|
|
local defaultspecification={ separator=",",quote='"' }
|
|
function parsers.csvsplitter(specification)
|
|
specification=specification and setmetatableindex(specification,defaultspecification) or defaultspecification
|
|
local separator=specification.separator
|
|
local quotechar=specification.quote
|
|
local separator=S(separator~="" and separator or ",")
|
|
local whatever=C((1-separator-newline)^0)
|
|
if quotechar and quotechar~="" then
|
|
local quotedata=nil
|
|
for chr in gmatch(quotechar,".") do
|
|
local quotechar=P(chr)
|
|
local quoteword=quotechar*C((1-quotechar)^0)*quotechar
|
|
if quotedata then
|
|
quotedata=quotedata+quoteword
|
|
else
|
|
quotedata=quoteword
|
|
end
|
|
end
|
|
whatever=quotedata+whatever
|
|
end
|
|
local parser=Ct((Ct(whatever*(separator*whatever)^0)*S("\n\r")^1)^0 )
|
|
return function(data)
|
|
return lpegmatch(parser,data)
|
|
end
|
|
end
|
|
function parsers.rfc4180splitter(specification)
|
|
specification=specification and setmetatableindex(specification,defaultspecification) or defaultspecification
|
|
local separator=specification.separator
|
|
local quotechar=P(specification.quote)
|
|
local dquotechar=quotechar*quotechar
|
|
/specification.quote
|
|
local separator=S(separator~="" and separator or ",")
|
|
local escaped=quotechar*Cs((dquotechar+(1-quotechar))^0)*quotechar
|
|
local non_escaped=C((1-quotechar-newline-separator)^1)
|
|
local field=escaped+non_escaped+Cc("")
|
|
local record=Ct(field*(separator*field)^1)
|
|
local headerline=record*Cp()
|
|
local morerecords=(newline^(specification.strict and -1 or 1)*record)^0
|
|
local headeryes=Ct(morerecords)
|
|
local headernop=Ct(record*morerecords)
|
|
return function(data,getheader)
|
|
if getheader then
|
|
local header,position=lpegmatch(headerline,data)
|
|
local data=lpegmatch(headeryes,data,position)
|
|
return data,header
|
|
else
|
|
return lpegmatch(headernop,data)
|
|
end
|
|
end
|
|
end
|
|
local function ranger(first,last,n,action)
|
|
if not first then
|
|
elseif last==true then
|
|
for i=first,n or first do
|
|
action(i)
|
|
end
|
|
elseif last then
|
|
for i=first,last do
|
|
action(i)
|
|
end
|
|
else
|
|
action(first)
|
|
end
|
|
end
|
|
local cardinal=lpegpatterns.cardinal/tonumber
|
|
local spacers=lpegpatterns.spacer^0
|
|
local endofstring=lpegpatterns.endofstring
|
|
local stepper=spacers*(cardinal*(spacers*S(":-")*spacers*(cardinal+Cc(true) )+Cc(false) )*Carg(1)*Carg(2)/ranger*S(", ")^0 )^1
|
|
local stepper=spacers*(cardinal*(spacers*S(":-")*spacers*(cardinal+(P("*")+endofstring)*Cc(true) )+Cc(false) )*Carg(1)*Carg(2)/ranger*S(", ")^0 )^1*endofstring
|
|
function parsers.stepper(str,n,action)
|
|
if type(n)=="function" then
|
|
lpegmatch(stepper,str,1,false,n or print)
|
|
else
|
|
lpegmatch(stepper,str,1,n,action or print)
|
|
end
|
|
end
|
|
local pattern_math=Cs((P("%")/"\\percent "+P("^")*Cc("{")*lpegpatterns.integer*Cc("}")+anything)^0)
|
|
local pattern_text=Cs((P("%")/"\\percent "+(P("^")/"\\high")*Cc("{")*lpegpatterns.integer*Cc("}")+anything)^0)
|
|
patterns.unittotex=pattern
|
|
function parsers.unittotex(str,textmode)
|
|
return lpegmatch(textmode and pattern_text or pattern_math,str)
|
|
end
|
|
local pattern=Cs((P("^")/"<sup>"*lpegpatterns.integer*Cc("</sup>")+anything)^0)
|
|
function parsers.unittoxml(str)
|
|
return lpegmatch(pattern,str)
|
|
end
|
|
local cache={}
|
|
local spaces=lpegpatterns.space^0
|
|
local dummy=function() end
|
|
setmetatableindex(cache,function(t,k)
|
|
local separator=P(k)
|
|
local value=(1-separator)^0
|
|
local pattern=spaces*C(value)*separator^0*Cp()
|
|
t[k]=pattern
|
|
return pattern
|
|
end)
|
|
local commalistiterator=cache[","]
|
|
function parsers.iterator(str,separator)
|
|
local n=#str
|
|
if n==0 then
|
|
return dummy
|
|
else
|
|
local pattern=separator and cache[separator] or commalistiterator
|
|
local p=1
|
|
return function()
|
|
if p<=n then
|
|
local s,e=lpegmatch(pattern,str,p)
|
|
if e then
|
|
p=e
|
|
return s
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function initialize(t,name)
|
|
local source=t[name]
|
|
if source then
|
|
local result={}
|
|
for k,v in next,t[name] do
|
|
result[k]=v
|
|
end
|
|
return result
|
|
else
|
|
return {}
|
|
end
|
|
end
|
|
local function fetch(t,name)
|
|
return t[name] or {}
|
|
end
|
|
local function process(result,more)
|
|
for k,v in next,more do
|
|
result[k]=v
|
|
end
|
|
return result
|
|
end
|
|
local name=C((1-S(", "))^1)
|
|
local parser=(Carg(1)*name/initialize)*(S(", ")^1*(Carg(1)*name/fetch))^0
|
|
local merge=Cf(parser,process)
|
|
function parsers.mergehashes(hash,list)
|
|
return lpegmatch(merge,list,1,hash)
|
|
end
|
|
function parsers.runtime(time)
|
|
if not time then
|
|
time=os.runtime()
|
|
end
|
|
local days=div(time,24*60*60)
|
|
time=mod(time,24*60*60)
|
|
local hours=div(time,60*60)
|
|
time=mod(time,60*60)
|
|
local minutes=div(time,60)
|
|
local seconds=mod(time,60)
|
|
return days,hours,minutes,seconds
|
|
end
|
|
local spacing=whitespace^0
|
|
local apply=P("->")
|
|
local method=C((1-apply)^1)
|
|
local token=lbrace*C((1-rbrace)^1)*rbrace+C(anything^1)
|
|
local pattern=spacing*(method*spacing*apply+Carg(1))*spacing*token
|
|
function parsers.splitmethod(str,default)
|
|
if str then
|
|
return lpegmatch(pattern,str,1,default or false)
|
|
else
|
|
return default or false,""
|
|
end
|
|
end
|
|
local p_year=lpegpatterns.digit^4/tonumber
|
|
local pattern=Cf(Ct("")*(
|
|
(Cg(Cc("year")*p_year)*S("-/")*Cg(Cc("month")*cardinal)*S("-/")*Cg(Cc("day")*cardinal)
|
|
)+(Cg(Cc("day")*cardinal)*S("-/")*Cg(Cc("month")*cardinal)*S("-/")*Cg(Cc("year")*p_year)
|
|
)
|
|
)*P(" ")*Cg(Cc("hour")*cardinal)*P(":")*Cg(Cc("min")*cardinal)*(P(":")*Cg(Cc("sec")*cardinal))^-1
|
|
,rawset)
|
|
lpegpatterns.splittime=pattern
|
|
function parsers.totime(str)
|
|
return lpegmatch(pattern,str)
|
|
end
|
|
|
|
end -- closure
|
|
|
|
do -- begin closure to overcome local limits and interference
|
|
|
|
if not modules then modules={} end modules ['util-dim']={
|
|
version=1.001,
|
|
comment="support for dimensions",
|
|
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
copyright="PRAGMA ADE / ConTeXt Development Team",
|
|
license="see context related readme files"
|
|
}
|
|
local format,match,gsub,type,setmetatable=string.format,string.match,string.gsub,type,setmetatable
|
|
local P,S,R,Cc,C,lpegmatch=lpeg.P,lpeg.S,lpeg.R,lpeg.Cc,lpeg.C,lpeg.match
|
|
local allocate=utilities.storage.allocate
|
|
local setmetatableindex=table.setmetatableindex
|
|
local formatters=string.formatters
|
|
local texget=tex and tex.get or function() return 65536*10*100 end
|
|
local p_stripzeros=lpeg.patterns.stripzeros
|
|
number=number or {}
|
|
local number=number
|
|
number.tonumberf=function(n) return lpegmatch(p_stripzeros,format("%.20f",n)) end
|
|
number.tonumberg=function(n) return format("%.20g",n) end
|
|
local dimenfactors=allocate {
|
|
["pt"]=1/65536,
|
|
["in"]=(100/7227)/65536,
|
|
["cm"]=(254/7227)/65536,
|
|
["mm"]=(2540/7227)/65536,
|
|
["sp"]=1,
|
|
["bp"]=(7200/7227)/65536,
|
|
["pc"]=(1/12)/65536,
|
|
["dd"]=(1157/1238)/65536,
|
|
["cc"]=(1157/14856)/65536,
|
|
["nd"]=(20320/21681)/65536,
|
|
["nc"]=(5080/65043)/65536
|
|
}
|
|
local f_none=formatters["%s%s"]
|
|
local f_true=formatters["%0.5F%s"]
|
|
local function numbertodimen(n,unit,fmt)
|
|
if type(n)=='string' then
|
|
return n
|
|
else
|
|
unit=unit or 'pt'
|
|
n=n*dimenfactors[unit]
|
|
if not fmt then
|
|
fmt=f_none(n,unit)
|
|
elseif fmt==true then
|
|
fmt=f_true(n,unit)
|
|
else
|
|
return formatters[fmt](n,unit)
|
|
end
|
|
end
|
|
end
|
|
number.maxdimen=1073741823
|
|
number.todimen=numbertodimen
|
|
number.dimenfactors=dimenfactors
|
|
function number.topoints (n,fmt) return numbertodimen(n,"pt",fmt) end
|
|
function number.toinches (n,fmt) return numbertodimen(n,"in",fmt) end
|
|
function number.tocentimeters (n,fmt) return numbertodimen(n,"cm",fmt) end
|
|
function number.tomillimeters (n,fmt) return numbertodimen(n,"mm",fmt) end
|
|
function number.toscaledpoints(n,fmt) return numbertodimen(n,"sp",fmt) end
|
|
function number.toscaledpoints(n) return n.."sp" end
|
|
function number.tobasepoints (n,fmt) return numbertodimen(n,"bp",fmt) end
|
|
function number.topicas (n,fmt) return numbertodimen(n "pc",fmt) end
|
|
function number.todidots (n,fmt) return numbertodimen(n,"dd",fmt) end
|
|
function number.tociceros (n,fmt) return numbertodimen(n,"cc",fmt) end
|
|
function number.tonewdidots (n,fmt) return numbertodimen(n,"nd",fmt) end
|
|
function number.tonewciceros (n,fmt) return numbertodimen(n,"nc",fmt) end
|
|
local amount=(S("+-")^0*R("09")^0*P(".")^0*R("09")^0)+Cc("0")
|
|
local unit=R("az")^1+P("%")
|
|
local dimenpair=amount/tonumber*(unit^1/dimenfactors+Cc(1))
|
|
lpeg.patterns.dimenpair=dimenpair
|
|
local splitter=amount/tonumber*C(unit^1)
|
|
function number.splitdimen(str)
|
|
return lpegmatch(splitter,str)
|
|
end
|
|
setmetatableindex(dimenfactors,function(t,s)
|
|
return false
|
|
end)
|
|
local stringtodimen
|
|
local amount=S("+-")^0*R("09")^0*S(".,")^0*R("09")^0
|
|
local unit=P("pt")+P("cm")+P("mm")+P("sp")+P("bp")+P("in")+P("pc")+P("dd")+P("cc")+P("nd")+P("nc")
|
|
local validdimen=amount*unit
|
|
lpeg.patterns.validdimen=validdimen
|
|
local dimensions={}
|
|
function dimensions.__add(a,b)
|
|
local ta,tb=type(a),type(b)
|
|
if ta=="string" then a=stringtodimen(a) elseif ta=="table" then a=a[1] end
|
|
if tb=="string" then b=stringtodimen(b) elseif tb=="table" then b=b[1] end
|
|
return setmetatable({ a+b },dimensions)
|
|
end
|
|
function dimensions.__sub(a,b)
|
|
local ta,tb=type(a),type(b)
|
|
if ta=="string" then a=stringtodimen(a) elseif ta=="table" then a=a[1] end
|
|
if tb=="string" then b=stringtodimen(b) elseif tb=="table" then b=b[1] end
|
|
return setmetatable({ a-b },dimensions)
|
|
end
|
|
function dimensions.__mul(a,b)
|
|
local ta,tb=type(a),type(b)
|
|
if ta=="string" then a=stringtodimen(a) elseif ta=="table" then a=a[1] end
|
|
if tb=="string" then b=stringtodimen(b) elseif tb=="table" then b=b[1] end
|
|
return setmetatable({ a*b },dimensions)
|
|
end
|
|
function dimensions.__div(a,b)
|
|
local ta,tb=type(a),type(b)
|
|
if ta=="string" then a=stringtodimen(a) elseif ta=="table" then a=a[1] end
|
|
if tb=="string" then b=stringtodimen(b) elseif tb=="table" then b=b[1] end
|
|
return setmetatable({ a/b },dimensions)
|
|
end
|
|
function dimensions.__unm(a)
|
|
local ta=type(a)
|
|
if ta=="string" then a=stringtodimen(a) elseif ta=="table" then a=a[1] end
|
|
return setmetatable({-a },dimensions)
|
|
end
|
|
function dimensions.__lt(a,b)
|
|
return a[1]<b[1]
|
|
end
|
|
function dimensions.__eq(a,b)
|
|
return a[1]==b[1]
|
|
end
|
|
function dimensions.__tostring(a)
|
|
return a[1]/65536 .."pt"
|
|
end
|
|
function dimensions.__index(tab,key)
|
|
local d=dimenfactors[key]
|
|
if not d then
|
|
error("illegal property of dimen: "..key)
|
|
d=1
|
|
end
|
|
return 1/d
|
|
end
|
|
dimenfactors["ex"]=4*1/65536
|
|
dimenfactors["em"]=10*1/65536
|
|
local known={} setmetatable(known,{ __mode="v" })
|
|
function dimen(a)
|
|
if a then
|
|
local ta=type(a)
|
|
if ta=="string" then
|
|
local k=known[a]
|
|
if k then
|
|
a=k
|
|
else
|
|
local value,unit=lpegmatch(dimenpair,a)
|
|
if value and unit then
|
|
k=value/unit
|
|
else
|
|
k=0
|
|
end
|
|
known[a]=k
|
|
a=k
|
|
end
|
|
elseif ta=="table" then
|
|
a=a[1]
|
|
end
|
|
return setmetatable({ a },dimensions)
|
|
else
|
|
return setmetatable({ 0 },dimensions)
|
|
end
|
|
end
|
|
function string.todimen(str)
|
|
local t=type(str)
|
|
if t=="number" then
|
|
return str
|
|
else
|
|
local k=known[str]
|
|
if not k then
|
|
if t=="string" then
|
|
local value,unit=lpegmatch(dimenpair,str)
|
|
if value and unit then
|
|
k=value/unit
|
|
else
|
|
k=0
|
|
end
|
|
else
|
|
k=0
|
|
end
|
|
known[str]=k
|
|
end
|
|
return k
|
|
end
|
|
end
|
|
stringtodimen=string.todimen
|
|
function number.toscaled(d)
|
|
return format("%0.5f",d/0x10000)
|
|
end
|
|
function number.percent(n,d)
|
|
d=d or texget("hsize")
|
|
if type(d)=="string" then
|
|
d=stringtodimen(d)
|
|
end
|
|
return (n/100)*d
|
|
end
|
|
number["%"]=number.percent
|
|
|
|
end -- closure
|
|
|
|
do -- begin closure to overcome local limits and interference
|
|
|
|
if not modules then modules={} end modules ['util-jsn']={
|
|
version=1.001,
|
|
comment="companion to m-json.mkiv",
|
|
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
copyright="PRAGMA ADE / ConTeXt Development Team",
|
|
license="see context related readme files"
|
|
}
|
|
local P,V,R,S,C,Cc,Cs,Ct,Cf,Cg=lpeg.P,lpeg.V,lpeg.R,lpeg.S,lpeg.C,lpeg.Cc,lpeg.Cs,lpeg.Ct,lpeg.Cf,lpeg.Cg
|
|
local lpegmatch=lpeg.match
|
|
local format,gsub=string.format,string.gsub
|
|
local formatters=string.formatters
|
|
local utfchar=utf.char
|
|
local concat,sortedkeys=table.concat,table.sortedkeys
|
|
local tonumber,tostring,rawset,type,next=tonumber,tostring,rawset,type,next
|
|
local json=utilities.json or {}
|
|
utilities.json=json
|
|
do
|
|
local lbrace=P("{")
|
|
local rbrace=P("}")
|
|
local lparent=P("[")
|
|
local rparent=P("]")
|
|
local comma=P(",")
|
|
local colon=P(":")
|
|
local dquote=P('"')
|
|
local whitespace=lpeg.patterns.whitespace
|
|
local optionalws=whitespace^0
|
|
local escapes={
|
|
["b"]="\010",
|
|
["f"]="\014",
|
|
["n"]="\n",
|
|
["r"]="\r",
|
|
["t"]="\t",
|
|
}
|
|
local escape_un=P("\\u")/""*(C(R("09","AF","af")^-4)/function(s)
|
|
return utfchar(tonumber(s,16))
|
|
end)
|
|
local escape_bs=P([[\]])/""*(P(1)/escapes)
|
|
local jstring=dquote*Cs((escape_un+escape_bs+(1-dquote))^0)*dquote
|
|
local jtrue=P("true")*Cc(true)
|
|
local jfalse=P("false")*Cc(false)
|
|
local jnull=P("null")*Cc(nil)
|
|
local jnumber=(1-whitespace-rparent-rbrace-comma)^1/tonumber
|
|
local key=jstring
|
|
local jsonconverter={ "value",
|
|
hash=lbrace*Cf(Ct("")*(V("pair")*(comma*V("pair"))^0+optionalws),rawset)*rbrace,
|
|
pair=Cg(optionalws*key*optionalws*colon*V("value")),
|
|
array=Ct(lparent*(V("value")*(comma*V("value"))^0+optionalws)*rparent),
|
|
value=optionalws*(jstring+V("hash")+V("array")+jtrue+jfalse+jnull+jnumber)*optionalws,
|
|
}
|
|
function json.tolua(str)
|
|
return lpegmatch(jsonconverter,str)
|
|
end
|
|
function json.load(filename)
|
|
local data=io.loaddata(filename)
|
|
if data then
|
|
return lpegmatch(jsonconverter,data)
|
|
end
|
|
end
|
|
end
|
|
do
|
|
local escaper
|
|
local f_start_hash=formatters[ '%w{' ]
|
|
local f_start_array=formatters[ '%w[' ]
|
|
local f_start_hash_new=formatters[ "\n"..'%w{' ]
|
|
local f_start_array_new=formatters[ "\n"..'%w[' ]
|
|
local f_start_hash_key=formatters[ "\n"..'%w"%s" : {' ]
|
|
local f_start_array_key=formatters[ "\n"..'%w"%s" : [' ]
|
|
local f_stop_hash=formatters[ "\n"..'%w}' ]
|
|
local f_stop_array=formatters[ "\n"..'%w]' ]
|
|
local f_key_val_seq=formatters[ "\n"..'%w"%s" : %s' ]
|
|
local f_key_val_str=formatters[ "\n"..'%w"%s" : "%s"' ]
|
|
local f_key_val_num=f_key_val_seq
|
|
local f_key_val_yes=formatters[ "\n"..'%w"%s" : true' ]
|
|
local f_key_val_nop=formatters[ "\n"..'%w"%s" : false' ]
|
|
local f_key_val_null=formatters[ "\n"..'%w"%s" : null' ]
|
|
local f_val_num=formatters[ "\n"..'%w%s' ]
|
|
local f_val_str=formatters[ "\n"..'%w"%s"' ]
|
|
local f_val_yes=formatters[ "\n"..'%wtrue' ]
|
|
local f_val_nop=formatters[ "\n"..'%wfalse' ]
|
|
local f_val_null=formatters[ "\n"..'%wnull' ]
|
|
local f_val_empty=formatters[ "\n"..'%w{ }' ]
|
|
local f_val_seq=f_val_num
|
|
local t={}
|
|
local n=0
|
|
local function is_simple_table(tt)
|
|
local l=#tt
|
|
if l>0 then
|
|
for i=1,l do
|
|
if type(tt[i])=="table" then
|
|
return false
|
|
end
|
|
end
|
|
local nn=n
|
|
n=n+1 t[n]="[ "
|
|
for i=1,l do
|
|
if i>1 then
|
|
n=n+1 t[n]=", "
|
|
end
|
|
local v=tt[i]
|
|
local tv=type(v)
|
|
if tv=="number" then
|
|
n=n+1 t[n]=v
|
|
elseif tv=="string" then
|
|
n=n+1 t[n]='"'
|
|
n=n+1 t[n]=lpegmatch(escaper,v) or v
|
|
n=n+1 t[n]='"'
|
|
elseif tv=="boolean" then
|
|
n=n+1 t[n]=v and "true" or "false"
|
|
elseif v then
|
|
n=n+1 t[n]=tostring(v)
|
|
else
|
|
n=n+1 t[n]="null"
|
|
end
|
|
end
|
|
n=n+1 t[n]=" ]"
|
|
local s=concat(t,"",nn+1,n)
|
|
n=nn
|
|
return s
|
|
end
|
|
return false
|
|
end
|
|
local function tojsonpp(root,name,depth,level,size)
|
|
if root then
|
|
local indexed=size>0
|
|
n=n+1
|
|
if level==0 then
|
|
if indexed then
|
|
t[n]=f_start_array(depth)
|
|
else
|
|
t[n]=f_start_hash(depth)
|
|
end
|
|
elseif name then
|
|
if tn=="string" then
|
|
name=lpegmatch(escaper,name) or name
|
|
elseif tn~="number" then
|
|
name=tostring(name)
|
|
end
|
|
if indexed then
|
|
t[n]=f_start_array_key(depth,name)
|
|
else
|
|
t[n]=f_start_hash_key(depth,name)
|
|
end
|
|
else
|
|
if indexed then
|
|
t[n]=f_start_array_new(depth)
|
|
else
|
|
t[n]=f_start_hash_new(depth)
|
|
end
|
|
end
|
|
depth=depth+1
|
|
if indexed then
|
|
for i=1,size do
|
|
if i>1 then
|
|
n=n+1 t[n]=","
|
|
end
|
|
local v=root[i]
|
|
local tv=type(v)
|
|
if tv=="number" then
|
|
n=n+1 t[n]=f_val_num(depth,v)
|
|
elseif tv=="string" then
|
|
v=lpegmatch(escaper,v) or v
|
|
n=n+1 t[n]=f_val_str(depth,v)
|
|
elseif tv=="table" then
|
|
if next(v) then
|
|
local st=is_simple_table(v)
|
|
if st then
|
|
n=n+1 t[n]=f_val_seq(depth,st)
|
|
else
|
|
tojsonpp(v,nil,depth,level+1,#v)
|
|
end
|
|
else
|
|
n=n+1
|
|
t[n]=f_val_empty(depth)
|
|
end
|
|
elseif tv=="boolean" then
|
|
n=n+1
|
|
if v then
|
|
t[n]=f_val_yes(depth,v)
|
|
else
|
|
t[n]=f_val_nop(depth,v)
|
|
end
|
|
else
|
|
n=n+1
|
|
t[n]=f_val_null(depth)
|
|
end
|
|
end
|
|
elseif next(root) then
|
|
local sk=sortedkeys(root)
|
|
for i=1,#sk do
|
|
if i>1 then
|
|
n=n+1 t[n]=","
|
|
end
|
|
local k=sk[i]
|
|
local v=root[k]
|
|
local tv=type(v)
|
|
local tk=type(k)
|
|
if tv=="number" then
|
|
if tk=="number" then
|
|
n=n+1 t[n]=f_key_val_num(depth,k,v)
|
|
elseif tk=="string" then
|
|
k=lpegmatch(escaper,k) or k
|
|
n=n+1 t[n]=f_key_val_num(depth,k,v)
|
|
end
|
|
elseif tv=="string" then
|
|
if tk=="number" then
|
|
v=lpegmatch(escaper,v) or v
|
|
n=n+1 t[n]=f_key_val_str(depth,k,v)
|
|
elseif tk=="string" then
|
|
k=lpegmatch(escaper,k) or k
|
|
v=lpegmatch(escaper,v) or v
|
|
n=n+1 t[n]=f_key_val_str(depth,k,v)
|
|
end
|
|
elseif tv=="table" then
|
|
local l=#v
|
|
if l>0 then
|
|
local st=is_simple_table(v)
|
|
if not st then
|
|
tojsonpp(v,k,depth,level+1,l)
|
|
elseif tk=="number" then
|
|
n=n+1 t[n]=f_key_val_seq(depth,k,st)
|
|
elseif tk=="string" then
|
|
k=lpegmatch(escaper,k) or k
|
|
n=n+1 t[n]=f_key_val_seq(depth,k,st)
|
|
end
|
|
elseif next(v) then
|
|
tojsonpp(v,k,depth,level+1,0)
|
|
end
|
|
elseif tv=="boolean" then
|
|
if tk=="number" then
|
|
n=n+1
|
|
if v then
|
|
t[n]=f_key_val_yes(depth,k)
|
|
else
|
|
t[n]=f_key_val_nop(depth,k)
|
|
end
|
|
elseif tk=="string" then
|
|
k=lpegmatch(escaper,k) or k
|
|
n=n+1
|
|
if v then
|
|
t[n]=f_key_val_yes(depth,k)
|
|
else
|
|
t[n]=f_key_val_nop(depth,k)
|
|
end
|
|
end
|
|
else
|
|
if tk=="number" then
|
|
n=n+1
|
|
t[n]=f_key_val_null(depth,k)
|
|
elseif tk=="string" then
|
|
k=lpegmatch(escaper,k) or k
|
|
n=n+1
|
|
t[n]=f_key_val_null(depth,k)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
n=n+1
|
|
if indexed then
|
|
t[n]=f_stop_array(depth-1)
|
|
else
|
|
t[n]=f_stop_hash(depth-1)
|
|
end
|
|
end
|
|
end
|
|
local function tojson(value,n)
|
|
local kind=type(value)
|
|
if kind=="table" then
|
|
local done=false
|
|
local size=#value
|
|
if size==0 then
|
|
for k,v in next,value do
|
|
if done then
|
|
n=n+1;t[n]=',"'
|
|
else
|
|
n=n+1;t[n]='{"'
|
|
done=true
|
|
end
|
|
n=n+1;t[n]=lpegmatch(escaper,k) or k
|
|
n=n+1;t[n]='":'
|
|
t,n=tojson(v,n)
|
|
end
|
|
if done then
|
|
n=n+1;t[n]="}"
|
|
else
|
|
n=n+1;t[n]="{}"
|
|
end
|
|
elseif size==1 then
|
|
n=n+1;t[n]="["
|
|
t,n=tojson(value[1],n)
|
|
n=n+1;t[n]="]"
|
|
else
|
|
for i=1,size do
|
|
if done then
|
|
n=n+1;t[n]=","
|
|
else
|
|
n=n+1;t[n]="["
|
|
done=true
|
|
end
|
|
t,n=tojson(value[i],n)
|
|
end
|
|
n=n+1;t[n]="]"
|
|
end
|
|
elseif kind=="string" then
|
|
n=n+1;t[n]='"'
|
|
n=n+1;t[n]=lpegmatch(escaper,value) or value
|
|
n=n+1;t[n]='"'
|
|
elseif kind=="number" then
|
|
n=n+1;t[n]=value
|
|
elseif kind=="boolean" then
|
|
n=n+1;t[n]=tostring(value)
|
|
else
|
|
n=n+1;t[n]="null"
|
|
end
|
|
return t,n
|
|
end
|
|
local function jsontostring(value,pretty)
|
|
local kind=type(value)
|
|
if kind=="table" then
|
|
if not escaper then
|
|
local escapes={
|
|
["\\"]="\\u005C",
|
|
["\""]="\\u0022",
|
|
}
|
|
for i=0,0x1F do
|
|
escapes[utfchar(i)]=format("\\u%04X",i)
|
|
end
|
|
escaper=Cs((
|
|
(R('\0\x20')+S('\"\\'))/escapes+P(1)
|
|
)^1 )
|
|
end
|
|
t={}
|
|
n=0
|
|
if pretty then
|
|
tojsonpp(value,name,0,0,#value)
|
|
value=concat(t,"",1,n)
|
|
else
|
|
t,n=tojson(value,0)
|
|
value=concat(t,"",1,n)
|
|
end
|
|
t=nil
|
|
n=0
|
|
return value
|
|
elseif kind=="string" or kind=="number" then
|
|
return lpegmatch(escaper,value) or value
|
|
else
|
|
return tostring(value)
|
|
end
|
|
end
|
|
json.tostring=jsontostring
|
|
function json.tojson(value)
|
|
return jsontostring(value,true)
|
|
end
|
|
end
|
|
return json
|
|
|
|
end -- closure
|
|
|
|
do -- begin closure to overcome local limits and interference
|
|
|
|
if not modules then modules={} end modules ['trac-inf']={
|
|
version=1.001,
|
|
comment="companion to trac-inf.mkiv",
|
|
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
copyright="PRAGMA ADE / ConTeXt Development Team",
|
|
license="see context related readme files"
|
|
}
|
|
local type,tonumber,select=type,tonumber,select
|
|
local format,lower,find=string.format,string.lower,string.find
|
|
local concat=table.concat
|
|
local clock=os.gettimeofday or os.clock
|
|
local setmetatableindex=table.setmetatableindex
|
|
local serialize=table.serialize
|
|
local formatters=string.formatters
|
|
statistics=statistics or {}
|
|
local statistics=statistics
|
|
statistics.enable=true
|
|
statistics.threshold=0.01
|
|
local statusinfo,n,registered,timers={},0,{},{}
|
|
setmetatableindex(timers,function(t,k)
|
|
local v={ timing=0,loadtime=0,offset=0 }
|
|
t[k]=v
|
|
return v
|
|
end)
|
|
local function hastiming(instance)
|
|
return instance and timers[instance]
|
|
end
|
|
local function resettiming(instance)
|
|
timers[instance or "notimer"]={ timing=0,loadtime=0,offset=0 }
|
|
end
|
|
local ticks=clock
|
|
local seconds=function(n) return n or 0 end
|
|
if os.type~="windows" then
|
|
elseif lua.getpreciseticks then
|
|
ticks=lua.getpreciseticks
|
|
seconds=lua.getpreciseseconds
|
|
elseif FFISUPPORTED then
|
|
local okay,kernel=pcall(ffi.load,"kernel32")
|
|
if kernel then
|
|
local tonumber=ffi.number or tonumber
|
|
ffi.cdef[[
|
|
int QueryPerformanceFrequency(int64_t *lpFrequency);
|
|
int QueryPerformanceCounter(int64_t *lpPerformanceCount);
|
|
]]
|
|
local target=ffi.new("__int64[1]")
|
|
ticks=function()
|
|
if kernel.QueryPerformanceCounter(target)==1 then
|
|
return tonumber(target[0])
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
local target=ffi.new("__int64[1]")
|
|
seconds=function(ticks)
|
|
if kernel.QueryPerformanceFrequency(target)==1 then
|
|
return ticks/tonumber(target[0])
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
end
|
|
else
|
|
end
|
|
local function starttiming(instance,reset)
|
|
local timer=timers[instance or "notimer"]
|
|
local it=timer.timing
|
|
if reset then
|
|
it=0
|
|
timer.loadtime=0
|
|
end
|
|
if it==0 then
|
|
timer.starttime=ticks()
|
|
if not timer.loadtime then
|
|
timer.loadtime=0
|
|
end
|
|
end
|
|
timer.timing=it+1
|
|
end
|
|
local function stoptiming(instance)
|
|
local timer=timers[instance or "notimer"]
|
|
local it=timer.timing
|
|
if it>1 then
|
|
timer.timing=it-1
|
|
else
|
|
local starttime=timer.starttime
|
|
if starttime and starttime>0 then
|
|
local stoptime=ticks()
|
|
local loadtime=stoptime-starttime
|
|
timer.stoptime=stoptime
|
|
timer.loadtime=timer.loadtime+loadtime
|
|
timer.timing=0
|
|
timer.starttime=0
|
|
return loadtime
|
|
end
|
|
end
|
|
return 0
|
|
end
|
|
local function benchmarktimer(instance)
|
|
local timer=timers[instance or "notimer"]
|
|
local it=timer.timing
|
|
if it>1 then
|
|
timer.timing=it-1
|
|
else
|
|
local starttime=timer.starttime
|
|
if starttime and starttime>0 then
|
|
timer.offset=ticks()-starttime
|
|
else
|
|
timer.offset=0
|
|
end
|
|
end
|
|
end
|
|
local function elapsed(instance)
|
|
if type(instance)=="number" then
|
|
return instance
|
|
else
|
|
local timer=timers[instance or "notimer"]
|
|
return timer and seconds(timer.loadtime-2*(timer.offset or 0)) or 0
|
|
end
|
|
end
|
|
local function currenttime(instance)
|
|
if type(instance)=="number" then
|
|
return instance
|
|
else
|
|
local timer=timers[instance or "notimer"]
|
|
local it=timer.timing
|
|
if it>1 then
|
|
else
|
|
local starttime=timer.starttime
|
|
if starttime and starttime>0 then
|
|
return seconds(timer.loadtime+ticks()-starttime-2*(timer.offset or 0))
|
|
end
|
|
end
|
|
return 0
|
|
end
|
|
end
|
|
local function elapsedtime(instance)
|
|
return format("%0.3f",elapsed(instance))
|
|
end
|
|
local function elapsedindeed(instance)
|
|
return elapsed(instance)>statistics.threshold
|
|
end
|
|
local function elapsedseconds(instance,rest)
|
|
if elapsedindeed(instance) then
|
|
return format("%0.3f seconds %s",elapsed(instance),rest or "")
|
|
end
|
|
end
|
|
statistics.hastiming=hastiming
|
|
statistics.resettiming=resettiming
|
|
statistics.starttiming=starttiming
|
|
statistics.stoptiming=stoptiming
|
|
statistics.currenttime=currenttime
|
|
statistics.elapsed=elapsed
|
|
statistics.elapsedtime=elapsedtime
|
|
statistics.elapsedindeed=elapsedindeed
|
|
statistics.elapsedseconds=elapsedseconds
|
|
statistics.benchmarktimer=benchmarktimer
|
|
function statistics.register(tag,fnc)
|
|
if statistics.enable and type(fnc)=="function" then
|
|
local rt=registered[tag] or (#statusinfo+1)
|
|
statusinfo[rt]={ tag,fnc }
|
|
registered[tag]=rt
|
|
if #tag>n then n=#tag end
|
|
end
|
|
end
|
|
local report=logs.reporter("mkiv lua stats")
|
|
function statistics.show()
|
|
if statistics.enable then
|
|
local register=statistics.register
|
|
register("used platform",function()
|
|
return format("%s, type: %s, binary subtree: %s",
|
|
os.platform or "unknown",os.type or "unknown",environment.texos or "unknown")
|
|
end)
|
|
if LUATEXENGINE=="luametatex" then
|
|
register("used engine",function()
|
|
return format("%s version: %s, functionality level: %s, format id: %s, compiler: %s",
|
|
LUATEXENGINE,LUATEXVERSION,LUATEXFUNCTIONALITY,LUATEXFORMATID,status.used_compiler)
|
|
end)
|
|
else
|
|
register("used engine",function()
|
|
return format("%s version: %s, functionality level: %s, banner: %s",
|
|
LUATEXENGINE,LUATEXVERSION,LUATEXFUNCTIONALITY,lower(status.banner))
|
|
end)
|
|
end
|
|
register("control sequences",function()
|
|
return format("%s of %s + %s",status.cs_count,status.hash_size,status.hash_extra)
|
|
end)
|
|
register("callbacks",statistics.callbacks)
|
|
if JITSUPPORTED then
|
|
local jitstatus=jit.status
|
|
if jitstatus then
|
|
local jitstatus={ jitstatus() }
|
|
if jitstatus[1] then
|
|
register("luajit options",concat(jitstatus," ",2))
|
|
end
|
|
end
|
|
end
|
|
register("lua properties",function()
|
|
local hashchar=tonumber(status.luatex_hashchars)
|
|
local mask=lua.mask or "ascii"
|
|
return format("engine: %s %s, used memory: %s, hash chars: min(%i,40), symbol mask: %s (%s)",
|
|
jit and "luajit" or "lua",
|
|
LUAVERSION,
|
|
statistics.memused(),
|
|
hashchar and 2^hashchar or "unknown",
|
|
mask,
|
|
mask=="utf" and "τεχ" or "tex")
|
|
end)
|
|
register("runtime",statistics.runtime)
|
|
logs.newline()
|
|
for i=1,#statusinfo do
|
|
local s=statusinfo[i]
|
|
local r=s[2]()
|
|
if r then
|
|
report("%s: %s",s[1],r)
|
|
end
|
|
end
|
|
statistics.enable=false
|
|
end
|
|
end
|
|
function statistics.memused()
|
|
local round=math.round or math.floor
|
|
return format("%s MB, ctx: %s MB, max: %s MB",
|
|
round(collectgarbage("count")/1000),
|
|
round(status.luastate_bytes/1000000),
|
|
status.luastate_bytes_max and round(status.luastate_bytes_max/1000000) or "unknown"
|
|
)
|
|
end
|
|
starttiming(statistics)
|
|
function statistics.formatruntime(runtime)
|
|
return format("%s seconds",runtime)
|
|
end
|
|
function statistics.runtime()
|
|
stoptiming(statistics)
|
|
local runtime=lua.getruntime and lua.getruntime() or elapsedtime(statistics)
|
|
return statistics.formatruntime(runtime)
|
|
end
|
|
local report=logs.reporter("system")
|
|
function statistics.timed(action,all)
|
|
starttiming("run")
|
|
action()
|
|
stoptiming("run")
|
|
local runtime=tonumber(elapsedtime("run"))
|
|
if all then
|
|
local alltime=tonumber(lua.getruntime and lua.getruntime() or elapsedtime(statistics))
|
|
if alltime and alltime>0 then
|
|
report("total runtime: %0.3f seconds of %0.3f seconds",runtime,alltime)
|
|
return
|
|
end
|
|
end
|
|
report("total runtime: %0.3f seconds",runtime)
|
|
end
|
|
function statistics.tracefunction(base,tag,...)
|
|
for i=1,select("#",...) do
|
|
local name=select(i,...)
|
|
local stat={}
|
|
local func=base[name]
|
|
setmetatableindex(stat,function(t,k) t[k]=0 return 0 end)
|
|
base[name]=function(n,k,v) stat[k]=stat[k]+1 return func(n,k,v) end
|
|
statistics.register(formatters["%s.%s"](tag,name),function() return serialize(stat,"calls") end)
|
|
end
|
|
end
|
|
|
|
end -- closure
|
|
|
|
do -- begin closure to overcome local limits and interference
|
|
|
|
if not modules then modules={} end modules ['util-lua']={
|
|
version=1.001,
|
|
comment="companion to luat-lib.mkiv",
|
|
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
comment="the strip code is written by Peter Cawley",
|
|
copyright="PRAGMA ADE / ConTeXt Development Team",
|
|
license="see context related readme files"
|
|
}
|
|
local rep,sub,byte,dump,format=string.rep,string.sub,string.byte,string.dump,string.format
|
|
local load,loadfile,type,collectgarbage=load,loadfile,type,collectgarbage
|
|
utilities=utilities or {}
|
|
utilities.lua=utilities.lua or {}
|
|
local luautilities=utilities.lua
|
|
local report_lua=logs.reporter("system","lua")
|
|
local report_mem=logs.reporter("system","lua memory")
|
|
local tracestripping=false
|
|
local tracememory=false
|
|
luautilities.stripcode=true
|
|
luautilities.alwaysstripcode=false
|
|
luautilities.nofstrippedchunks=0
|
|
luautilities.nofstrippedbytes=0
|
|
local strippedchunks={}
|
|
luautilities.strippedchunks=strippedchunks
|
|
if not LUATEXENGINE then
|
|
LUATEXENGINE=status.luatex_engine and string.lower(status.luatex_engine)
|
|
JITSUPPORTED=LUATEXENGINE=="luajittex" or jit
|
|
CONTEXTLMTXMODE=CONTEXTLMTXMODE or (LUATEXENGINE=="luametatex" and 1) or 0
|
|
end
|
|
luautilities.suffixes={
|
|
tma="tma",
|
|
tmc=(CONTEXTLMTXMODE and CONTEXTLMTXMODE>0 and "tmd") or (jit and "tmb") or "tmc",
|
|
lua="lua",
|
|
luc=(CONTEXTLMTXMODE and CONTEXTLMTXMODE>0 and "lud") or (jit and "lub") or "luc",
|
|
lui="lui",
|
|
luv="luv",
|
|
luj="luj",
|
|
tua="tua",
|
|
tuc=(CONTEXTLMTXMODE and CONTEXTLMTXMODE>0 and "tud") or (jit and "tub") or "tuc",
|
|
}
|
|
local function register(name)
|
|
if tracestripping then
|
|
report_lua("stripped bytecode from %a",name or "unknown")
|
|
end
|
|
strippedchunks[#strippedchunks+1]=name
|
|
luautilities.nofstrippedchunks=luautilities.nofstrippedchunks+1
|
|
end
|
|
local function stupidcompile(luafile,lucfile,strip)
|
|
local code=io.loaddata(luafile)
|
|
if code and code~="" then
|
|
code=load(code)
|
|
if code then
|
|
code=dump(code,strip and luautilities.stripcode or luautilities.alwaysstripcode)
|
|
if code and code~="" then
|
|
register(name)
|
|
io.savedata(lucfile,code)
|
|
return true,0
|
|
end
|
|
else
|
|
report_lua("fatal error %a in file %a",1,luafile)
|
|
end
|
|
else
|
|
report_lua("fatal error %a in file %a",2,luafile)
|
|
end
|
|
return false,0
|
|
end
|
|
function luautilities.loadedluacode(fullname,forcestrip,name,macros)
|
|
name=name or fullname
|
|
if macros then
|
|
macros=lua.macros
|
|
end
|
|
local code,message
|
|
if macros then
|
|
code,message=macros.loaded(fullname,true,false)
|
|
else
|
|
code,message=loadfile(fullname)
|
|
end
|
|
if code then
|
|
code()
|
|
else
|
|
report_lua("loading of file %a failed:\n\t%s",fullname,message or "no message")
|
|
code,message=loadfile(fullname)
|
|
end
|
|
if forcestrip and luautilities.stripcode then
|
|
if type(forcestrip)=="function" then
|
|
forcestrip=forcestrip(fullname)
|
|
end
|
|
if forcestrip or luautilities.alwaysstripcode then
|
|
register(name)
|
|
return load(dump(code,true)),0
|
|
else
|
|
return code,0
|
|
end
|
|
elseif luautilities.alwaysstripcode then
|
|
register(name)
|
|
return load(dump(code,true)),0
|
|
else
|
|
return code,0
|
|
end
|
|
end
|
|
function luautilities.strippedloadstring(code,name,forcestrip)
|
|
local code,message=load(code)
|
|
if not code then
|
|
report_lua("loading of file %a failed:\n\t%s",name,message or "no message")
|
|
end
|
|
if forcestrip and luautilities.stripcode or luautilities.alwaysstripcode then
|
|
register(name)
|
|
return load(dump(code,true)),0
|
|
else
|
|
return code,0
|
|
end
|
|
end
|
|
function luautilities.loadstring(code,name)
|
|
local code,message=load(code)
|
|
if not code then
|
|
report_lua("loading of file %a failed:\n\t%s",name,message or "no message")
|
|
end
|
|
return code,0
|
|
end
|
|
function luautilities.compile(luafile,lucfile,cleanup,strip,fallback)
|
|
report_lua("compiling %a into %a",luafile,lucfile)
|
|
os.remove(lucfile)
|
|
local done=stupidcompile(luafile,lucfile,strip~=false)
|
|
if done then
|
|
report_lua("dumping %a into %a stripped",luafile,lucfile)
|
|
if cleanup==true and lfs.isfile(lucfile) and lfs.isfile(luafile) then
|
|
report_lua("removing %a",luafile)
|
|
os.remove(luafile)
|
|
end
|
|
end
|
|
return done
|
|
end
|
|
function luautilities.loadstripped(...)
|
|
local l=load(...)
|
|
if l then
|
|
return load(dump(l,true))
|
|
end
|
|
end
|
|
local finalizers={}
|
|
setmetatable(finalizers,{
|
|
__gc=function(t)
|
|
for i=1,#t do
|
|
pcall(t[i])
|
|
end
|
|
end
|
|
} )
|
|
function luautilities.registerfinalizer(f)
|
|
finalizers[#finalizers+1]=f
|
|
end
|
|
function luautilities.checkmemory(previous,threshold,trace)
|
|
local current=collectgarbage("count")
|
|
if previous then
|
|
local checked=(threshold or 64)*1024
|
|
local delta=current-previous
|
|
if current-previous>checked then
|
|
collectgarbage("collect")
|
|
local afterwards=collectgarbage("count")
|
|
if trace or tracememory then
|
|
report_mem("previous %r MB, current %r MB, delta %r MB, threshold %r MB, afterwards %r MB",
|
|
previous/1024,current/1024,delta/1024,threshold,afterwards)
|
|
end
|
|
return afterwards
|
|
elseif trace or tracememory then
|
|
report_mem("previous %r MB, current %r MB, delta %r MB, threshold %r MB",
|
|
previous/1024,current/1024,delta/1024,threshold)
|
|
end
|
|
end
|
|
return current
|
|
end
|
|
|
|
end -- closure
|
|
|
|
do -- begin closure to overcome local limits and interference
|
|
|
|
if not modules then modules={} end modules ['util-deb']={
|
|
version=1.001,
|
|
comment="companion to luat-lib.mkiv",
|
|
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
copyright="PRAGMA ADE / ConTeXt Development Team",
|
|
license="see context related readme files"
|
|
}
|
|
local type,next,tostring,tonumber=type,next,tostring,tonumber
|
|
local format,find,sub,gsub=string.format,string.find,string.sub,string.gsub
|
|
local insert,remove,sort=table.insert,table.remove,table.sort
|
|
local setmetatableindex=table.setmetatableindex
|
|
utilities=utilities or {}
|
|
local debugger=utilities.debugger or {}
|
|
utilities.debugger=debugger
|
|
local report=logs.reporter("debugger")
|
|
local ticks=os.gettimeofday or os.clock
|
|
local seconds=function(n) return n or 0 end
|
|
local overhead=0
|
|
local dummycalls=10*1000
|
|
local nesting=0
|
|
local names={}
|
|
local initialize=false
|
|
if lua.getpreciseticks then
|
|
initialize=function()
|
|
ticks=lua.getpreciseticks
|
|
seconds=lua.getpreciseseconds
|
|
initialize=false
|
|
end
|
|
elseif not (FFISUPPORTED and ffi) then
|
|
elseif os.type=="windows" then
|
|
initialize=function()
|
|
local kernel=ffilib("kernel32","system")
|
|
if kernel then
|
|
local tonumber=ffi.number or tonumber
|
|
ffi.cdef[[
|
|
int QueryPerformanceFrequency(int64_t *lpFrequency);
|
|
int QueryPerformanceCounter(int64_t *lpPerformanceCount);
|
|
]]
|
|
local target=ffi.new("__int64[1]")
|
|
ticks=function()
|
|
if kernel.QueryPerformanceCounter(target)==1 then
|
|
return tonumber(target[0])
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
local target=ffi.new("__int64[1]")
|
|
seconds=function(ticks)
|
|
if kernel.QueryPerformanceFrequency(target)==1 then
|
|
return ticks/tonumber(target[0])
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
end
|
|
initialize=false
|
|
end
|
|
elseif os.type=="unix" then
|
|
initialize=function()
|
|
local C=ffi.C
|
|
local tonumber=ffi.number or tonumber
|
|
ffi.cdef [[
|
|
/* what a mess */
|
|
typedef int clk_id_t;
|
|
typedef enum { CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID } clk_id;
|
|
typedef struct timespec { long sec; long nsec; } ctx_timespec;
|
|
int clock_gettime(clk_id_t timerid, struct timespec *t);
|
|
]]
|
|
local target=ffi.new("ctx_timespec[?]",1)
|
|
local clock=C.CLOCK_PROCESS_CPUTIME_ID
|
|
ticks=function ()
|
|
C.clock_gettime(clock,target)
|
|
return tonumber(target[0].sec*1000000000+target[0].nsec)
|
|
end
|
|
seconds=function(ticks)
|
|
return ticks/1000000000
|
|
end
|
|
initialize=false
|
|
end
|
|
end
|
|
setmetatableindex(names,function(t,name)
|
|
local v=setmetatableindex(function(t,source)
|
|
local v=setmetatableindex(function(t,line)
|
|
local v={ total=0,count=0,nesting=0 }
|
|
t[line]=v
|
|
return v
|
|
end)
|
|
t[source]=v
|
|
return v
|
|
end)
|
|
t[name]=v
|
|
return v
|
|
end)
|
|
local getinfo=nil
|
|
local sethook=nil
|
|
local function hook(where)
|
|
local f=getinfo(2,"nSl")
|
|
if f then
|
|
local source=f.short_src
|
|
if not source then
|
|
return
|
|
end
|
|
local line=f.linedefined or 0
|
|
local name=f.name
|
|
if not name then
|
|
local what=f.what
|
|
if what=="C" then
|
|
name="<anonymous>"
|
|
else
|
|
name=f.namewhat or what or "<unknown>"
|
|
end
|
|
end
|
|
local data=names[name][source][line]
|
|
if where=="call" then
|
|
local nesting=data.nesting
|
|
if nesting==0 then
|
|
data.count=data.count+1
|
|
insert(data,ticks())
|
|
data.nesting=1
|
|
else
|
|
data.nesting=nesting+1
|
|
end
|
|
elseif where=="return" then
|
|
local nesting=data.nesting
|
|
if nesting==1 then
|
|
local t=remove(data)
|
|
if t then
|
|
data.total=data.total+ticks()-t
|
|
end
|
|
data.nesting=0
|
|
else
|
|
data.nesting=nesting-1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
function debugger.showstats(printer,threshold)
|
|
local printer=printer or report
|
|
local calls=0
|
|
local functions=0
|
|
local dataset={}
|
|
local length=0
|
|
local realtime=0
|
|
local totaltime=0
|
|
local threshold=threshold or 0
|
|
for name,sources in next,names do
|
|
for source,lines in next,sources do
|
|
for line,data in next,lines do
|
|
local count=data.count
|
|
if count>threshold then
|
|
if #name>length then
|
|
length=#name
|
|
end
|
|
local total=data.total
|
|
local real=total
|
|
if real>0 then
|
|
real=total-(count*overhead/dummycalls)
|
|
if real<0 then
|
|
real=0
|
|
end
|
|
realtime=realtime+real
|
|
end
|
|
totaltime=totaltime+total
|
|
if line<0 then
|
|
line=0
|
|
end
|
|
dataset[#dataset+1]={ real,total,count,name,source,line }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
sort(dataset,function(a,b)
|
|
if a[1]==b[1] then
|
|
if a[2]==b[2] then
|
|
if a[3]==b[3] then
|
|
if a[4]==b[4] then
|
|
if a[5]==b[5] then
|
|
return a[6]<b[6]
|
|
else
|
|
return a[5]<b[5]
|
|
end
|
|
else
|
|
return a[4]<b[4]
|
|
end
|
|
else
|
|
return b[3]<a[3]
|
|
end
|
|
else
|
|
return b[2]<a[2]
|
|
end
|
|
else
|
|
return b[1]<a[1]
|
|
end
|
|
end)
|
|
if length>50 then
|
|
length=50
|
|
end
|
|
local fmt=string.formatters["%4.9k s %3.3k %% %4.9k s %3.3k %% %8i # %-"..length.."s %4i %s"]
|
|
for i=1,#dataset do
|
|
local data=dataset[i]
|
|
local real=data[1]
|
|
local total=data[2]
|
|
local count=data[3]
|
|
local name=data[4]
|
|
local source=data[5]
|
|
local line=data[6]
|
|
calls=calls+count
|
|
functions=functions+1
|
|
name=gsub(name,"%s+"," ")
|
|
if #name>length then
|
|
name=sub(name,1,length)
|
|
end
|
|
printer(fmt(seconds(total),100*total/totaltime,seconds(real),100*real/realtime,count,name,line,source))
|
|
end
|
|
printer("")
|
|
printer(format("functions : %i",functions))
|
|
printer(format("calls : %i",calls))
|
|
printer(format("overhead : %f",seconds(overhead/1000)))
|
|
end
|
|
local function getdebug()
|
|
if sethook and getinfo then
|
|
return
|
|
end
|
|
if not debug then
|
|
local okay
|
|
okay,debug=pcall(require,"debug")
|
|
end
|
|
if type(debug)~="table" then
|
|
return
|
|
end
|
|
getinfo=debug.getinfo
|
|
sethook=debug.sethook
|
|
if type(getinfo)~="function" then
|
|
getinfo=nil
|
|
end
|
|
if type(sethook)~="function" then
|
|
sethook=nil
|
|
end
|
|
end
|
|
function debugger.savestats(filename,threshold)
|
|
local f=io.open(filename,'w')
|
|
if f then
|
|
debugger.showstats(function(str) f:write(str,"\n") end,threshold)
|
|
f:close()
|
|
end
|
|
end
|
|
function debugger.enable()
|
|
getdebug()
|
|
if sethook and getinfo and nesting==0 then
|
|
running=true
|
|
if initialize then
|
|
initialize()
|
|
end
|
|
sethook(hook,"cr")
|
|
local function dummy() end
|
|
local t=ticks()
|
|
for i=1,dummycalls do
|
|
dummy()
|
|
end
|
|
overhead=ticks()-t
|
|
end
|
|
if nesting>0 then
|
|
nesting=nesting+1
|
|
end
|
|
end
|
|
function debugger.disable()
|
|
if nesting>0 then
|
|
nesting=nesting-1
|
|
end
|
|
if sethook and getinfo and nesting==0 then
|
|
sethook()
|
|
end
|
|
end
|
|
local function showtraceback(rep)
|
|
getdebug()
|
|
if getinfo then
|
|
local level=2
|
|
local reporter=rep or report
|
|
while true do
|
|
local info=getinfo(level,"Sl")
|
|
if not info then
|
|
break
|
|
elseif info.what=="C" then
|
|
reporter("%2i : %s",level-1,"C function")
|
|
else
|
|
reporter("%2i : %s : %s",level-1,info.short_src,info.currentline)
|
|
end
|
|
level=level+1
|
|
end
|
|
end
|
|
end
|
|
debugger.showtraceback=showtraceback
|
|
|
|
end -- closure
|
|
|
|
do -- begin closure to overcome local limits and interference
|
|
|
|
if not modules then modules={} end modules ['util-tpl']={
|
|
version=1.001,
|
|
comment="companion to luat-lib.mkiv",
|
|
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
copyright="PRAGMA ADE / ConTeXt Development Team",
|
|
license="see context related readme files"
|
|
}
|
|
utilities.templates=utilities.templates or {}
|
|
local templates=utilities.templates
|
|
local trace_template=false trackers.register("templates.trace",function(v) trace_template=v end)
|
|
local report_template=logs.reporter("template")
|
|
local tostring,next=tostring,next
|
|
local format,sub,byte=string.format,string.sub,string.byte
|
|
local P,C,R,Cs,Cc,Carg,lpegmatch,lpegpatterns=lpeg.P,lpeg.C,lpeg.R,lpeg.Cs,lpeg.Cc,lpeg.Carg,lpeg.match,lpeg.patterns
|
|
local formatters=string.formatters
|
|
local replacer
|
|
local function replacekey(k,t,how,recursive)
|
|
local v=t[k]
|
|
if not v then
|
|
if trace_template then
|
|
report_template("unknown key %a",k)
|
|
end
|
|
return ""
|
|
else
|
|
v=tostring(v)
|
|
if trace_template then
|
|
report_template("setting key %a to value %a",k,v)
|
|
end
|
|
if recursive then
|
|
return lpegmatch(replacer,v,1,t,how,recursive)
|
|
else
|
|
return v
|
|
end
|
|
end
|
|
end
|
|
local sqlescape=lpeg.replacer {
|
|
{ "'","''" },
|
|
{ "\\","\\\\" },
|
|
{ "\r\n","\\n" },
|
|
{ "\r","\\n" },
|
|
}
|
|
local sqlquoted=Cs(Cc("'")*sqlescape*Cc("'"))
|
|
lpegpatterns.sqlescape=sqlescape
|
|
lpegpatterns.sqlquoted=sqlquoted
|
|
local luaescape=lpegpatterns.luaescape
|
|
local escapers={
|
|
lua=function(s)
|
|
return lpegmatch(luaescape,s)
|
|
end,
|
|
sql=function(s)
|
|
return lpegmatch(sqlescape,s)
|
|
end,
|
|
}
|
|
local quotedescapers={
|
|
lua=function(s)
|
|
return format("%q",s)
|
|
end,
|
|
sql=function(s)
|
|
return lpegmatch(sqlquoted,s)
|
|
end,
|
|
}
|
|
local luaescaper=escapers.lua
|
|
local quotedluaescaper=quotedescapers.lua
|
|
local function replacekeyunquoted(s,t,how,recurse)
|
|
if how==false then
|
|
return replacekey(s,t,how,recurse)
|
|
else
|
|
local escaper=how and escapers[how] or luaescaper
|
|
return escaper(replacekey(s,t,how,recurse))
|
|
end
|
|
end
|
|
local function replacekeyquoted(s,t,how,recurse)
|
|
if how==false then
|
|
return replacekey(s,t,how,recurse)
|
|
else
|
|
local escaper=how and quotedescapers[how] or quotedluaescaper
|
|
return escaper(replacekey(s,t,how,recurse))
|
|
end
|
|
end
|
|
local function replaceoptional(l,m,r,t,how,recurse)
|
|
local v=t[l]
|
|
return v and v~="" and lpegmatch(replacer,r,1,t,how or "lua",recurse or false) or ""
|
|
end
|
|
local function replaceformatted(l,m,r,t,how,recurse)
|
|
local v=t[r]
|
|
return v and formatters[l](v)
|
|
end
|
|
local single=P("%")
|
|
local double=P("%%")
|
|
local lquoted=P("%[")
|
|
local rquoted=P("]%")
|
|
local lquotedq=P("%(")
|
|
local rquotedq=P(")%")
|
|
local escape=double/'%%'
|
|
local nosingle=single/''
|
|
local nodouble=double/''
|
|
local nolquoted=lquoted/''
|
|
local norquoted=rquoted/''
|
|
local nolquotedq=lquotedq/''
|
|
local norquotedq=rquotedq/''
|
|
local nolformatted=P(":")/"%%"
|
|
local norformatted=P(":")/""
|
|
local noloptional=P("%?")/''
|
|
local noroptional=P("?%")/''
|
|
local nomoptional=P(":")/''
|
|
local args=Carg(1)*Carg(2)*Carg(3)
|
|
local key=nosingle*((C((1-nosingle)^1)*args)/replacekey)*nosingle
|
|
local quoted=nolquotedq*((C((1-norquotedq)^1)*args)/replacekeyquoted)*norquotedq
|
|
local unquoted=nolquoted*((C((1-norquoted)^1)*args)/replacekeyunquoted)*norquoted
|
|
local optional=noloptional*((C((1-nomoptional)^1)*nomoptional*C((1-noroptional)^1)*args)/replaceoptional)*noroptional
|
|
local formatted=nosingle*((Cs(nolformatted*(1-norformatted )^1)*norformatted*C((1-nosingle)^1)*args)/replaceformatted)*nosingle
|
|
local any=P(1)
|
|
replacer=Cs((unquoted+quoted+formatted+escape+optional+key+any)^0)
|
|
local function replace(str,mapping,how,recurse)
|
|
if mapping and str then
|
|
return lpegmatch(replacer,str,1,mapping,how or "lua",recurse or false) or str
|
|
else
|
|
return str
|
|
end
|
|
end
|
|
templates.replace=replace
|
|
function templates.replacer(str,how,recurse)
|
|
return function(mapping)
|
|
return lpegmatch(replacer,str,1,mapping,how or "lua",recurse or false) or str
|
|
end
|
|
end
|
|
function templates.load(filename,mapping,how,recurse)
|
|
local data=io.loaddata(filename) or ""
|
|
if mapping and next(mapping) then
|
|
return replace(data,mapping,how,recurse)
|
|
else
|
|
return data
|
|
end
|
|
end
|
|
function templates.resolve(t,mapping,how,recurse)
|
|
if not mapping then
|
|
mapping=t
|
|
end
|
|
for k,v in next,t do
|
|
t[k]=replace(v,mapping,how,recurse)
|
|
end
|
|
return t
|
|
end
|
|
|
|
end -- closure
|
|
|
|
do -- begin closure to overcome local limits and interference
|
|
|
|
if not modules then modules={} end modules ['util-sta']={
|
|
version=1.001,
|
|
comment="companion to util-ini.mkiv",
|
|
author="Hans Hagen, PRAGMA-ADE, Hasselt NL",
|
|
copyright="PRAGMA ADE / ConTeXt Development Team",
|
|
license="see context related readme files"
|
|
}
|
|
local insert,remove,fastcopy,concat=table.insert,table.remove,table.fastcopy,table.concat
|
|
local format=string.format
|
|
local select,tostring=select,tostring
|
|
local trace_stacker=false trackers.register("stacker.resolve",function(v) trace_stacker=v end)
|
|
local stacker=stacker or {}
|
|
utilities.stacker=stacker
|
|
local function start(s,t,first,last)
|
|
if s.mode=="switch" then
|
|
local n=tostring(t[last])
|
|
if trace_stacker then
|
|
s.report("start: %s",n)
|
|
end
|
|
return n
|
|
else
|
|
local r={}
|
|
for i=first,last do
|
|
r[#r+1]=tostring(t[i])
|
|
end
|
|
local n=concat(r," ")
|
|
if trace_stacker then
|
|
s.report("start: %s",n)
|
|
end
|
|
return n
|
|
end
|
|
end
|
|
local function stop(s,t,first,last)
|
|
if s.mode=="switch" then
|
|
local n=tostring(false)
|
|
if trace_stacker then
|
|
s.report("stop: %s",n)
|
|
end
|
|
return n
|
|
else
|
|
local r={}
|
|
for i=last,first,-1 do
|
|
r[#r+1]=tostring(false)
|
|
end
|
|
local n=concat(r," ")
|
|
if trace_stacker then
|
|
s.report("stop: %s",n)
|
|
end
|
|
return n
|
|
end
|
|
end
|
|
local function change(s,t1,first1,last1,t2,first2,last2)
|
|
if s.mode=="switch" then
|
|
local n=tostring(t2[last2])
|
|
if trace_stacker then
|
|
s.report("change: %s",n)
|
|
end
|
|
return n
|
|
else
|
|
local r={}
|
|
for i=last1,first1,-1 do
|
|
r[#r+1]=tostring(false)
|
|
end
|
|
local n=concat(r," ")
|
|
for i=first2,last2 do
|
|
r[#r+1]=tostring(t2[i])
|
|
end
|
|
if trace_stacker then
|
|
s.report("change: %s",n)
|
|
end
|
|
return n
|
|
end
|
|
end
|
|
function stacker.new(name)
|
|
local report=logs.reporter("stacker",name or nil)
|
|
local s
|
|
local stack={}
|
|
local list={}
|
|
local ids={}
|
|
local hash={}
|
|
local hashing=true
|
|
local function push(...)
|
|
for i=1,select("#",...) do
|
|
insert(stack,(select(i,...)))
|
|
end
|
|
if hashing then
|
|
local c=concat(stack,"|")
|
|
local n=hash[c]
|
|
if not n then
|
|
n=#list+1
|
|
hash[c]=n
|
|
list[n]=fastcopy(stack)
|
|
end
|
|
insert(ids,n)
|
|
return n
|
|
else
|
|
local n=#list+1
|
|
list[n]=fastcopy(stack)
|
|
insert(ids,n)
|
|
return n
|
|
end
|
|
end
|
|
local function pop()
|
|
remove(stack)
|
|
remove(ids)
|
|
return ids[#ids] or s.unset or -1
|
|
end
|
|
local function clean()
|
|
if #stack==0 then
|
|
if trace_stacker then
|
|
s.report("%s list entries, %s stack entries",#list,#stack)
|
|
end
|
|
end
|
|
end
|
|
local tops={}
|
|
local top=nil
|
|
local switch=nil
|
|
local function resolve_reset(mode)
|
|
if #tops>0 then
|
|
report("resetting %s left-over states of %a",#tops,name)
|
|
end
|
|
tops={}
|
|
top=nil
|
|
switch=nil
|
|
end
|
|
local function resolve_begin(mode)
|
|
if mode then
|
|
switch=mode=="switch"
|
|
else
|
|
switch=s.mode=="switch"
|
|
end
|
|
top={ switch=switch }
|
|
insert(tops,top)
|
|
end
|
|
local function resolve_step(ti)
|
|
if not top then
|
|
return
|
|
end
|
|
local result=nil
|
|
local noftop=#top
|
|
if ti>0 then
|
|
local current=list[ti]
|
|
if current then
|
|
local noflist=#current
|
|
local nofsame=0
|
|
if noflist>noftop then
|
|
for i=1,noflist do
|
|
if current[i]==top[i] then
|
|
nofsame=i
|
|
else
|
|
break
|
|
end
|
|
end
|
|
else
|
|
for i=1,noflist do
|
|
if current[i]==top[i] then
|
|
nofsame=i
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
local plus=nofsame+1
|
|
if plus<=noftop then
|
|
if plus<=noflist then
|
|
if switch then
|
|
result=s.change(s,top,plus,noftop,current,nofsame,noflist)
|
|
else
|
|
result=s.change(s,top,plus,noftop,current,plus,noflist)
|
|
end
|
|
else
|
|
if switch then
|
|
result=s.change(s,top,plus,noftop,current,nofsame,noflist)
|
|
else
|
|
result=s.stop(s,top,plus,noftop)
|
|
end
|
|
end
|
|
elseif plus<=noflist then
|
|
if switch then
|
|
result=s.start(s,current,nofsame,noflist)
|
|
else
|
|
result=s.start(s,current,plus,noflist)
|
|
end
|
|
end
|
|
top=current
|
|
else
|
|
if 1<=noftop then
|
|
result=s.stop(s,top,1,noftop)
|
|
end
|
|
top={}
|
|
end
|
|
return result
|
|
else
|
|
if 1<=noftop then
|
|
result=s.stop(s,top,1,noftop)
|
|
end
|
|
top={}
|
|
return result
|
|
end
|
|
end
|
|
local function resolve_end()
|
|
if #tops>0 then
|
|
local result=s.stop(s,top,1,#top)
|
|
remove(tops)
|
|
top=tops[#tops]
|
|
switch=top and top.switch
|
|
return result
|
|
end
|
|
end
|
|
local function resolve(t)
|
|
resolve_begin()
|
|
for i=1,#t do
|
|
resolve_step(t[i])
|
|
end
|
|
resolve_end()
|
|
end
|
|
s={
|
|
name=name or "unknown",
|
|
unset=-1,
|
|
report=report,
|
|
start=start,
|
|
stop=stop,
|
|
change=change,
|
|
push=push,
|
|
pop=pop,
|
|
clean=clean,
|
|
resolve=resolve,
|
|
resolve_begin=resolve_begin,
|
|
resolve_step=resolve_step,
|
|
resolve_end=resolve_end,
|
|
resolve_reset=resolve_reset,
|
|
}
|
|
return s
|
|
end
|
|
|
|
end -- closure
|
|
|