-- merged file : lualibs-basic-merged.lua -- parent file : lualibs-basic.lua -- merge date : Fri Feb 22 23:27:00 2019 do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-lua']={ 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 next,type,tonumber=next,type,tonumber LUAMAJORVERSION,LUAMINORVERSION=string.match(_VERSION,"^[^%d]+(%d+)%.(%d+).*$") LUAMAJORVERSION=tonumber(LUAMAJORVERSION) or 5 LUAMINORVERSION=tonumber(LUAMINORVERSION) or 1 LUAVERSION=LUAMAJORVERSION+LUAMINORVERSION/10 if LUAVERSION<5.2 and jit then MINORVERSION=2 LUAVERSION=5.2 end if not lpeg then lpeg=require("lpeg") end if loadstring then local loadnormal=load function load(first,...) if type(first)=="string" then return loadstring(first,...) else return loadnormal(first,...) end end else loadstring=load end if not ipairs then local function iterate(a,i) i=i+1 local v=a[i] if v~=nil then return i,v end end function ipairs(a) return iterate,a,0 end end if not pairs then function pairs(t) return next,t end end if not table.unpack then table.unpack=_G.unpack elseif not unpack then _G.unpack=table.unpack end if not package.loaders then package.loaders=package.searchers end local print,select,tostring=print,select,tostring local inspectors={} function setinspector(kind,inspector) inspectors[kind]=inspector end function inspect(...) for s=1,select("#",...) do local value=select(s,...) if value==nil then print("nil") else local done=false local kind=type(value) local inspector=inspectors[kind] if inspector then done=inspector(value) if done then break end end for kind,inspector in next,inspectors do done=inspector(value) if done then break end end if not done then print(tostring(value)) end end end end local dummy=function() end function optionalrequire(...) local ok,result=xpcall(require,dummy,...) if ok then return result end end if lua then lua.mask=load([[τεχ = 1]]) and "utf" or "ascii" end local flush=io.flush if flush then local execute=os.execute if execute then function os.execute(...) flush() return execute(...) end end local exec=os.exec if exec then function os.exec (...) flush() return exec (...) end end local spawn=os.spawn if spawn then function os.spawn (...) flush() return spawn (...) end end local popen=io.popen if popen then function io.popen (...) flush() return popen (...) end end end FFISUPPORTED=type(ffi)=="table" and ffi.os~="" and ffi.arch~="" and ffi.load if not FFISUPPORTED then local okay;okay,ffi=pcall(require,"ffi") FFISUPPORTED=type(ffi)=="table" and ffi.os~="" and ffi.arch~="" and ffi.load end if not FFISUPPORTED then ffi=nil elseif not ffi.number then ffi.number=tonumber end if not bit32 then bit32=require("l-bit32") end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-package']={ 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=type local gsub,format,find=string.gsub,string.format,string.find local insert,remove=table.insert,table.remove local P,S,Cs,lpegmatch=lpeg.P,lpeg.S,lpeg.Cs,lpeg.match local package=package local searchers=package.searchers or package.loaders local filejoin=file and file.join or function(path,name) return path.."/"..name end local isreadable=file and file.is_readable or function(name) local f=io.open(name) if f then f:close() return true end end local addsuffix=file and file.addsuffix or function(name,suffix) return name.."."..suffix end local function cleanpath(path) return path end local pattern=Cs((((1-S("\\/"))^0*(S("\\/")^1/"/"))^0*(P(".")^1/"/"+P(1))^1)*-1) local function lualibfile(name) return lpegmatch(pattern,name) or name end local offset=luarocks and 1 or 0 local helpers=package.helpers or { cleanpath=cleanpath, lualibfile=lualibfile, trace=false, report=function(...) print(format(...)) end, builtin={ ["preload table"]=searchers[1+offset], ["path specification"]=searchers[2+offset], ["cpath specification"]=searchers[3+offset], ["all in one fallback"]=searchers[4+offset], }, methods={}, sequence={ "already loaded", "preload table", "qualified path", "lua extra list", "lib extra list", "path specification", "cpath specification", "all in one fallback", "not loaded", } } package.helpers=helpers local methods=helpers.methods local builtin=helpers.builtin local extraluapaths={} local extralibpaths={} local luapaths=nil local libpaths=nil local oldluapath=nil local oldlibpath=nil local nofextralua=-1 local nofextralib=-1 local nofpathlua=-1 local nofpathlib=-1 local function listpaths(what,paths) local nofpaths=#paths if nofpaths>0 then for i=1,nofpaths do helpers.report("using %s path %i: %s",what,i,paths[i]) end else helpers.report("no %s paths defined",what) end return nofpaths end local function getextraluapaths() if helpers.trace and #extraluapaths~=nofextralua then nofextralua=listpaths("extra lua",extraluapaths) end return extraluapaths end local function getextralibpaths() if helpers.trace and #extralibpaths~=nofextralib then nofextralib=listpaths("extra lib",extralibpaths) end return extralibpaths end local function getluapaths() local luapath=package.path or "" if oldluapath~=luapath then luapaths=file.splitpath(luapath,";") oldluapath=luapath nofpathlua=-1 end if helpers.trace and #luapaths~=nofpathlua then nofpathlua=listpaths("builtin lua",luapaths) end return luapaths end local function getlibpaths() local libpath=package.cpath or "" if oldlibpath~=libpath then libpaths=file.splitpath(libpath,";") oldlibpath=libpath nofpathlib=-1 end if helpers.trace and #libpaths~=nofpathlib then nofpathlib=listpaths("builtin lib",libpaths) end return libpaths end package.luapaths=getluapaths package.libpaths=getlibpaths package.extraluapaths=getextraluapaths package.extralibpaths=getextralibpaths local hashes={ lua={}, lib={}, } local function registerpath(tag,what,target,...) local pathlist={... } local cleanpath=helpers.cleanpath local trace=helpers.trace local report=helpers.report local hash=hashes[what] local function add(path) local path=cleanpath(path) if not hash[path] then target[#target+1]=path hash[path]=true if trace then report("registered %s path %s: %s",tag,#target,path) end else if trace then report("duplicate %s path: %s",tag,path) end end end for p=1,#pathlist do local path=pathlist[p] if type(path)=="table" then for i=1,#path do add(path[i]) end else add(path) end end end local function pushpath(tag,what,target,path) local path=helpers.cleanpath(path) insert(target,1,path) if helpers.trace then helpers.report("pushing %s path in front: %s",tag,path) end end local function poppath(tag,what,target) local path=remove(target,1) if helpers.trace then if path then helpers.report("popping %s path from front: %s",tag,path) else helpers.report("no %s path to pop",tag) end end end helpers.registerpath=registerpath function package.extraluapath(...) registerpath("extra lua","lua",extraluapaths,...) end function package.pushluapath(path) pushpath("extra lua","lua",extraluapaths,path) end function package.popluapath() poppath("extra lua","lua",extraluapaths) end function package.extralibpath(...) registerpath("extra lib","lib",extralibpaths,...) end function package.pushlibpath(path) pushpath("extra lib","lib",extralibpaths,path) end function package.poplibpath() poppath("extra lib","lua",extralibpaths) end local function loadedaslib(resolved,rawname) local base=gsub(rawname,"%.","_") local init="luaopen_"..gsub(base,"%.","_") if helpers.trace then helpers.report("calling loadlib with '%s' with init '%s'",resolved,init) end return package.loadlib(resolved,init) end helpers.loadedaslib=loadedaslib local function loadedbypath(name,rawname,paths,islib,what) local trace=helpers.trace for p=1,#paths do local path=paths[p] local resolved=filejoin(path,name) if trace then helpers.report("%s path, identifying '%s' on '%s'",what,name,path) end if isreadable(resolved) then if trace then helpers.report("%s path, '%s' found on '%s'",what,name,resolved) end if islib then return loadedaslib(resolved,rawname) else return loadfile(resolved) end end end end helpers.loadedbypath=loadedbypath local function loadedbyname(name,rawname) if find(name,"^/") or find(name,"^[a-zA-Z]:/") then local trace=helpers.trace if trace then helpers.report("qualified name, identifying '%s'",what,name) end if isreadable(name) then if trace then helpers.report("qualified name, '%s' found",what,name) end return loadfile(name) end end end helpers.loadedbyname=loadedbyname methods["already loaded"]=function(name) return package.loaded[name] end methods["preload table"]=function(name) return builtin["preload table"](name) end methods["qualified path"]=function(name) return loadedbyname(addsuffix(lualibfile(name),"lua"),name) end methods["lua extra list"]=function(name) return loadedbypath(addsuffix(lualibfile(name),"lua"),name,getextraluapaths(),false,"lua") end methods["lib extra list"]=function(name) return loadedbypath(addsuffix(lualibfile(name),os.libsuffix),name,getextralibpaths(),true,"lib") end methods["path specification"]=function(name) getluapaths() return builtin["path specification"](name) end methods["cpath specification"]=function(name) getlibpaths() return builtin["cpath specification"](name) end methods["all in one fallback"]=function(name) return builtin["all in one fallback"](name) end methods["not loaded"]=function(name) if helpers.trace then helpers.report("unable to locate '%s'",name or "?") end return nil end local level=0 local used={} helpers.traceused=false function helpers.loaded(name) local sequence=helpers.sequence level=level+1 for i=1,#sequence do local method=sequence[i] if helpers.trace then helpers.report("%s, level '%s', method '%s', name '%s'","locating",level,method,name) end local result,rest=methods[method](name) if type(result)=="function" then if helpers.trace then helpers.report("%s, level '%s', method '%s', name '%s'","found",level,method,name) end if helpers.traceused then used[#used+1]={ level=level,name=name } end level=level-1 return result,rest end end level=level-1 return nil end function helpers.showused() local n=#used if n>0 then helpers.report("%s libraries loaded:",n) helpers.report() for i=1,n do local u=used[i] helpers.report("%i %a",u.level,u.name) end helpers.report() end end function helpers.unload(name) if helpers.trace then if package.loaded[name] then helpers.report("unloading, name '%s', %s",name,"done") else helpers.report("unloading, name '%s', %s",name,"not loaded") end end package.loaded[name]=nil end table.insert(searchers,1,helpers.loaded) if context then package.path="" end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-lpeg']={ 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" } lpeg=require("lpeg") local lpeg=lpeg if not lpeg.print then function lpeg.print(...) print(lpeg.pcode(...)) end end local type,next,tostring=type,next,tostring local byte,char,gmatch,format=string.byte,string.char,string.gmatch,string.format local floor=math.floor local P,R,S,V,Ct,C,Cs,Cc,Cp,Cmt=lpeg.P,lpeg.R,lpeg.S,lpeg.V,lpeg.Ct,lpeg.C,lpeg.Cs,lpeg.Cc,lpeg.Cp,lpeg.Cmt local lpegtype,lpegmatch,lpegprint=lpeg.type,lpeg.match,lpeg.print if setinspector then setinspector("lpeg",function(v) if lpegtype(v) then lpegprint(v) return true end end) end lpeg.patterns=lpeg.patterns or {} local patterns=lpeg.patterns local anything=P(1) local endofstring=P(-1) local alwaysmatched=P(true) patterns.anything=anything patterns.endofstring=endofstring patterns.beginofstring=alwaysmatched patterns.alwaysmatched=alwaysmatched local sign=S('+-') local zero=P('0') local digit=R('09') local digits=digit^1 local octdigit=R("07") local octdigits=octdigit^1 local lowercase=R("az") local uppercase=R("AZ") local underscore=P("_") local hexdigit=digit+lowercase+uppercase local hexdigits=hexdigit^1 local cr,lf,crlf=P("\r"),P("\n"),P("\r\n") local newline=P("\r")*(P("\n")+P(true))+P("\n") local escaped=P("\\")*anything local squote=P("'") local dquote=P('"') local space=P(" ") local period=P(".") local comma=P(",") local utfbom_32_be=P('\000\000\254\255') local utfbom_32_le=P('\255\254\000\000') local utfbom_16_be=P('\254\255') local utfbom_16_le=P('\255\254') local utfbom_8=P('\239\187\191') local utfbom=utfbom_32_be+utfbom_32_le+utfbom_16_be+utfbom_16_le+utfbom_8 local utftype=utfbom_32_be*Cc("utf-32-be")+utfbom_32_le*Cc("utf-32-le")+utfbom_16_be*Cc("utf-16-be")+utfbom_16_le*Cc("utf-16-le")+utfbom_8*Cc("utf-8")+alwaysmatched*Cc("utf-8") local utfstricttype=utfbom_32_be*Cc("utf-32-be")+utfbom_32_le*Cc("utf-32-le")+utfbom_16_be*Cc("utf-16-be")+utfbom_16_le*Cc("utf-16-le")+utfbom_8*Cc("utf-8") local utfoffset=utfbom_32_be*Cc(4)+utfbom_32_le*Cc(4)+utfbom_16_be*Cc(2)+utfbom_16_le*Cc(2)+utfbom_8*Cc(3)+Cc(0) local utf8next=R("\128\191") patterns.utfbom_32_be=utfbom_32_be patterns.utfbom_32_le=utfbom_32_le patterns.utfbom_16_be=utfbom_16_be patterns.utfbom_16_le=utfbom_16_le patterns.utfbom_8=utfbom_8 patterns.utf_16_be_nl=P("\000\r\000\n")+P("\000\r")+P("\000\n") patterns.utf_16_le_nl=P("\r\000\n\000")+P("\r\000")+P("\n\000") patterns.utf_32_be_nl=P("\000\000\000\r\000\000\000\n")+P("\000\000\000\r")+P("\000\000\000\n") patterns.utf_32_le_nl=P("\r\000\000\000\n\000\000\000")+P("\r\000\000\000")+P("\n\000\000\000") patterns.utf8one=R("\000\127") patterns.utf8two=R("\194\223")*utf8next patterns.utf8three=R("\224\239")*utf8next*utf8next patterns.utf8four=R("\240\244")*utf8next*utf8next*utf8next patterns.utfbom=utfbom patterns.utftype=utftype patterns.utfstricttype=utfstricttype patterns.utfoffset=utfoffset local utf8char=patterns.utf8one+patterns.utf8two+patterns.utf8three+patterns.utf8four local validutf8char=utf8char^0*endofstring*Cc(true)+Cc(false) local utf8character=P(1)*R("\128\191")^0 patterns.utf8=utf8char patterns.utf8char=utf8char patterns.utf8character=utf8character patterns.validutf8=validutf8char patterns.validutf8char=validutf8char local eol=S("\n\r") local spacer=S(" \t\f\v") local whitespace=eol+spacer local nonspacer=1-spacer local nonwhitespace=1-whitespace patterns.eol=eol patterns.spacer=spacer patterns.whitespace=whitespace patterns.nonspacer=nonspacer patterns.nonwhitespace=nonwhitespace local stripper=spacer^0*C((spacer^0*nonspacer^1)^0) local fullstripper=whitespace^0*C((whitespace^0*nonwhitespace^1)^0) local collapser=Cs(spacer^0/""*nonspacer^0*((spacer^0/" "*nonspacer^1)^0)) local nospacer=Cs((whitespace^1/""+nonwhitespace^1)^0) local b_collapser=Cs(whitespace^0/""*(nonwhitespace^1+whitespace^1/" ")^0) local e_collapser=Cs((whitespace^1*endofstring/""+nonwhitespace^1+whitespace^1/" ")^0) local m_collapser=Cs((nonwhitespace^1+whitespace^1/" ")^0) local b_stripper=Cs(spacer^0/""*(nonspacer^1+spacer^1/" ")^0) local e_stripper=Cs((spacer^1*endofstring/""+nonspacer^1+spacer^1/" ")^0) local m_stripper=Cs((nonspacer^1+spacer^1/" ")^0) patterns.stripper=stripper patterns.fullstripper=fullstripper patterns.collapser=collapser patterns.nospacer=nospacer patterns.b_collapser=b_collapser patterns.m_collapser=m_collapser patterns.e_collapser=e_collapser patterns.b_stripper=b_stripper patterns.m_stripper=m_stripper patterns.e_stripper=e_stripper patterns.lowercase=lowercase patterns.uppercase=uppercase patterns.letter=patterns.lowercase+patterns.uppercase patterns.space=space patterns.tab=P("\t") patterns.spaceortab=patterns.space+patterns.tab patterns.newline=newline patterns.emptyline=newline^1 patterns.equal=P("=") patterns.comma=comma patterns.commaspacer=comma*spacer^0 patterns.period=period patterns.colon=P(":") patterns.semicolon=P(";") patterns.underscore=underscore patterns.escaped=escaped patterns.squote=squote patterns.dquote=dquote patterns.nosquote=(escaped+(1-squote))^0 patterns.nodquote=(escaped+(1-dquote))^0 patterns.unsingle=(squote/"")*patterns.nosquote*(squote/"") patterns.undouble=(dquote/"")*patterns.nodquote*(dquote/"") patterns.unquoted=patterns.undouble+patterns.unsingle patterns.unspacer=((patterns.spacer^1)/"")^0 patterns.singlequoted=squote*patterns.nosquote*squote patterns.doublequoted=dquote*patterns.nodquote*dquote patterns.quoted=patterns.doublequoted+patterns.singlequoted patterns.digit=digit patterns.digits=digits patterns.octdigit=octdigit patterns.octdigits=octdigits patterns.hexdigit=hexdigit patterns.hexdigits=hexdigits patterns.sign=sign patterns.cardinal=digits patterns.integer=sign^-1*digits patterns.unsigned=digit^0*period*digits patterns.float=sign^-1*patterns.unsigned patterns.cunsigned=digit^0*comma*digits patterns.cpunsigned=digit^0*(period+comma)*digits patterns.cfloat=sign^-1*patterns.cunsigned patterns.cpfloat=sign^-1*patterns.cpunsigned patterns.number=patterns.float+patterns.integer patterns.cnumber=patterns.cfloat+patterns.integer patterns.cpnumber=patterns.cpfloat+patterns.integer patterns.oct=zero*octdigits patterns.octal=patterns.oct patterns.HEX=zero*P("X")*(digit+uppercase)^1 patterns.hex=zero*P("x")*(digit+lowercase)^1 patterns.hexadecimal=zero*S("xX")*hexdigits patterns.hexafloat=sign^-1*zero*S("xX")*(hexdigit^0*period*hexdigits+hexdigits*period*hexdigit^0+hexdigits)*(S("pP")*sign^-1*hexdigits)^-1 patterns.decafloat=sign^-1*(digit^0*period*digits+digits*period*digit^0+digits)*S("eE")*sign^-1*digits patterns.propername=(uppercase+lowercase+underscore)*(uppercase+lowercase+underscore+digit)^0*endofstring patterns.somecontent=(anything-newline-space)^1 patterns.beginline=#(1-newline) patterns.longtostring=Cs(whitespace^0/""*((patterns.quoted+nonwhitespace^1+whitespace^1/""*(endofstring+Cc(" ")))^0)) function anywhere(pattern) return (1-P(pattern))^0*P(pattern) end lpeg.anywhere=anywhere function lpeg.instringchecker(p) p=anywhere(p) return function(str) return lpegmatch(p,str) and true or false end end function lpeg.splitter(pattern,action) if action then return (((1-P(pattern))^1)/action+1)^0 else return (Cs((1-P(pattern))^1)+1)^0 end end function lpeg.tsplitter(pattern,action) if action then return Ct((((1-P(pattern))^1)/action+1)^0) else return Ct((Cs((1-P(pattern))^1)+1)^0) end end local splitters_s,splitters_m,splitters_t={},{},{} local function splitat(separator,single) local splitter=(single and splitters_s[separator]) or splitters_m[separator] if not splitter then separator=P(separator) local other=C((1-separator)^0) if single then local any=anything splitter=other*(separator*C(any^0)+"") splitters_s[separator]=splitter else splitter=other*(separator*other)^0 splitters_m[separator]=splitter end end return splitter end local function tsplitat(separator) local splitter=splitters_t[separator] if not splitter then splitter=Ct(splitat(separator)) splitters_t[separator]=splitter end return splitter end lpeg.splitat=splitat lpeg.tsplitat=tsplitat function string.splitup(str,separator) if not separator then separator="," end return lpegmatch(splitters_m[separator] or splitat(separator),str) end local cache={} function lpeg.split(separator,str) local c=cache[separator] if not c then c=tsplitat(separator) cache[separator]=c end return lpegmatch(c,str) end function string.split(str,separator) if separator then local c=cache[separator] if not c then c=tsplitat(separator) cache[separator]=c end return lpegmatch(c,str) else return { str } end end local spacing=patterns.spacer^0*newline local empty=spacing*Cc("") local nonempty=Cs((1-spacing)^1)*spacing^-1 local content=(empty+nonempty)^1 patterns.textline=content local linesplitter=tsplitat(newline) patterns.linesplitter=linesplitter function string.splitlines(str) return lpegmatch(linesplitter,str) end local cache={} function lpeg.checkedsplit(separator,str) local c=cache[separator] if not c then separator=P(separator) local other=C((1-separator)^1) c=Ct(separator^0*other*(separator^1*other)^0) cache[separator]=c end return lpegmatch(c,str) end function string.checkedsplit(str,separator) local c=cache[separator] if not c then separator=P(separator) local other=C((1-separator)^1) c=Ct(separator^0*other*(separator^1*other)^0) cache[separator]=c end return lpegmatch(c,str) end local function f2(s) local c1,c2=byte(s,1,2) return c1*64+c2-12416 end local function f3(s) local c1,c2,c3=byte(s,1,3) return (c1*64+c2)*64+c3-925824 end local function f4(s) local c1,c2,c3,c4=byte(s,1,4) return ((c1*64+c2)*64+c3)*64+c4-63447168 end local utf8byte=patterns.utf8one/byte+patterns.utf8two/f2+patterns.utf8three/f3+patterns.utf8four/f4 patterns.utf8byte=utf8byte local cache={} function lpeg.stripper(str) if type(str)=="string" then local s=cache[str] if not s then s=Cs(((S(str)^1)/""+1)^0) cache[str]=s end return s else return Cs(((str^1)/""+1)^0) end end local cache={} function lpeg.keeper(str) if type(str)=="string" then local s=cache[str] if not s then s=Cs((((1-S(str))^1)/""+1)^0) cache[str]=s end return s else return Cs((((1-str)^1)/""+1)^0) end end function lpeg.frontstripper(str) return (P(str)+P(true))*Cs(anything^0) end function lpeg.endstripper(str) return Cs((1-P(str)*endofstring)^0) end function lpeg.replacer(one,two,makefunction,isutf) local pattern local u=isutf and utf8char or 1 if type(one)=="table" then local no=#one local p=P(false) if no==0 then for k,v in next,one do p=p+P(k)/v end pattern=Cs((p+u)^0) elseif no==1 then local o=one[1] one,two=P(o[1]),o[2] pattern=Cs((one/two+u)^0) else for i=1,no do local o=one[i] p=p+P(o[1])/o[2] end pattern=Cs((p+u)^0) end else pattern=Cs((P(one)/(two or "")+u)^0) end if makefunction then return function(str) return lpegmatch(pattern,str) end else return pattern end end function lpeg.finder(lst,makefunction,isutf) local pattern if type(lst)=="table" then pattern=P(false) if #lst==0 then for k,v in next,lst do pattern=pattern+P(k) end else for i=1,#lst do pattern=pattern+P(lst[i]) end end else pattern=P(lst) end if isutf then pattern=((utf8char or 1)-pattern)^0*pattern else pattern=(1-pattern)^0*pattern end if makefunction then return function(str) return lpegmatch(pattern,str) end else return pattern end end local splitters_f,splitters_s={},{} function lpeg.firstofsplit(separator) local splitter=splitters_f[separator] if not splitter then local pattern=P(separator) splitter=C((1-pattern)^0) splitters_f[separator]=splitter end return splitter end function lpeg.secondofsplit(separator) local splitter=splitters_s[separator] if not splitter then local pattern=P(separator) splitter=(1-pattern)^0*pattern*C(anything^0) splitters_s[separator]=splitter end return splitter end local splitters_s,splitters_p={},{} function lpeg.beforesuffix(separator) local splitter=splitters_s[separator] if not splitter then local pattern=P(separator) splitter=C((1-pattern)^0)*pattern*endofstring splitters_s[separator]=splitter end return splitter end function lpeg.afterprefix(separator) local splitter=splitters_p[separator] if not splitter then local pattern=P(separator) splitter=pattern*C(anything^0) splitters_p[separator]=splitter end return splitter end function lpeg.balancer(left,right) left,right=P(left),P(right) return P { left*((1-left-right)+V(1))^0*right } end function lpeg.counter(pattern,action) local n=0 local pattern=(P(pattern)/function() n=n+1 end+anything)^0 if action then return function(str) n=0;lpegmatch(pattern,str);action(n) end else return function(str) n=0;lpegmatch(pattern,str);return n end end end function lpeg.is_lpeg(p) return p and lpegtype(p)=="pattern" end function lpeg.oneof(list,...) if type(list)~="table" then list={ list,... } end local p=P(list[1]) for l=2,#list do p=p+P(list[l]) end return p end local sort=table.sort local function copyindexed(old) local new={} for i=1,#old do new[i]=old end return new end local function sortedkeys(tab) local keys,s={},0 for key,_ in next,tab do s=s+1 keys[s]=key end sort(keys) return keys end function lpeg.append(list,pp,delayed,checked) local p=pp if #list>0 then local keys=copyindexed(list) sort(keys) for i=#keys,1,-1 do local k=keys[i] if p then p=P(k)+p else p=P(k) end end elseif delayed then local keys=sortedkeys(list) if p then for i=1,#keys,1 do local k=keys[i] local v=list[k] p=P(k)/list+p end else for i=1,#keys do local k=keys[i] local v=list[k] if p then p=P(k)+p else p=P(k) end end if p then p=p/list end end elseif checked then local keys=sortedkeys(list) for i=1,#keys do local k=keys[i] local v=list[k] if p then if k==v then p=P(k)+p else p=P(k)/v+p end else if k==v then p=P(k) else p=P(k)/v end end end else local keys=sortedkeys(list) for i=1,#keys do local k=keys[i] local v=list[k] if p then p=P(k)/v+p else p=P(k)/v end end end return p end local p_false=P(false) local p_true=P(true) local lower=utf and utf.lower or string.lower local upper=utf and utf.upper or string.upper function lpeg.setutfcasers(l,u) lower=l or lower upper=u or upper end local function make1(t,rest) local p=p_false local keys=sortedkeys(t) for i=1,#keys do local k=keys[i] if k~="" then local v=t[k] if v==true then p=p+P(k)*p_true elseif v==false then else p=p+P(k)*make1(v,v[""]) end end end if rest then p=p+p_true end return p end local function make2(t,rest) local p=p_false local keys=sortedkeys(t) for i=1,#keys do local k=keys[i] if k~="" then local v=t[k] if v==true then p=p+(P(lower(k))+P(upper(k)))*p_true elseif v==false then else p=p+(P(lower(k))+P(upper(k)))*make2(v,v[""]) end end end if rest then p=p+p_true end return p end local function utfchartabletopattern(list,insensitive) local tree={} local n=#list if n==0 then for s in next,list do local t=tree local p,pk for c in gmatch(s,".") do if t==true then t={ [c]=true,[""]=true } p[pk]=t p=t t=false elseif t==false then t={ [c]=false } p[pk]=t p=t t=false else local tc=t[c] if not tc then tc=false t[c]=false end p=t t=tc end pk=c end if t==false then p[pk]=true elseif t==true then else t[""]=true end end else for i=1,n do local s=list[i] local t=tree local p,pk for c in gmatch(s,".") do if t==true then t={ [c]=true,[""]=true } p[pk]=t p=t t=false elseif t==false then t={ [c]=false } p[pk]=t p=t t=false else local tc=t[c] if not tc then tc=false t[c]=false end p=t t=tc end pk=c end if t==false then p[pk]=true elseif t==true then else t[""]=true end end end return (insensitive and make2 or make1)(tree) end lpeg.utfchartabletopattern=utfchartabletopattern function lpeg.utfreplacer(list,insensitive) local pattern=Cs((utfchartabletopattern(list,insensitive)/list+utf8character)^0) return function(str) return lpegmatch(pattern,str) or str end end patterns.containseol=lpeg.finder(eol) local function nextstep(n,step,result) local m=n%step local d=floor(n/step) if d>0 then local v=V(tostring(step)) local s=result.start for i=1,d do if s then s=v*s else s=v end end result.start=s end if step>1 and result.start then local v=V(tostring(step/2)) result[tostring(step)]=v*v end if step>0 then return nextstep(m,step/2,result) else return result end end function lpeg.times(pattern,n) return P(nextstep(n,2^16,{ "start",["1"]=pattern })) end do local trailingzeros=zero^0*-digit local stripper=Cs(( digits*( period*trailingzeros/""+period*(digit-trailingzeros)^1*(trailingzeros/"") )+1 )^0) lpeg.patterns.stripzeros=stripper local nonzero=digit-zero local trailingzeros=zero^1*endofstring local stripper=Cs((1-period)^0*( period*trailingzeros/""+period*(nonzero^1+(trailingzeros/"")+zero^1)^0+endofstring )) lpeg.patterns.stripzero=stripper end local byte_to_HEX={} local byte_to_hex={} local byte_to_dec={} local hex_to_byte={} for i=0,255 do local H=format("%02X",i) local h=format("%02x",i) local d=format("%03i",i) local c=char(i) byte_to_HEX[c]=H byte_to_hex[c]=h byte_to_dec[c]=d hex_to_byte[h]=c hex_to_byte[H]=c end local hextobyte=P(2)/hex_to_byte local bytetoHEX=P(1)/byte_to_HEX local bytetohex=P(1)/byte_to_hex local bytetodec=P(1)/byte_to_dec local hextobytes=Cs(hextobyte^0) local bytestoHEX=Cs(bytetoHEX^0) local bytestohex=Cs(bytetohex^0) local bytestodec=Cs(bytetodec^0) patterns.hextobyte=hextobyte patterns.bytetoHEX=bytetoHEX patterns.bytetohex=bytetohex patterns.bytetodec=bytetodec patterns.hextobytes=hextobytes patterns.bytestoHEX=bytestoHEX patterns.bytestohex=bytestohex patterns.bytestodec=bytestodec function string.toHEX(s) if not s or s=="" then return s else return lpegmatch(bytestoHEX,s) end end function string.tohex(s) if not s or s=="" then return s else return lpegmatch(bytestohex,s) end end function string.todec(s) if not s or s=="" then return s else return lpegmatch(bytestodec,s) end end function string.tobytes(s) if not s or s=="" then return s else return lpegmatch(hextobytes,s) end end local patterns={} local function containsws(what) local p=patterns[what] if not p then local p1=P(what)*(whitespace+endofstring)*Cc(true) local p2=whitespace*P(p1) p=P(p1)+P(1-p2)^0*p2+Cc(false) patterns[what]=p end return p end lpeg.containsws=containsws function string.containsws(str,what) return lpegmatch(patterns[what] or containsws(what),str) end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-functions']={ 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" } functions=functions or {} function functions.dummy() end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-string']={ 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 string=string local sub,gmatch,format,char,byte,rep,lower=string.sub,string.gmatch,string.format,string.char,string.byte,string.rep,string.lower local lpegmatch,patterns=lpeg.match,lpeg.patterns local P,S,C,Ct,Cc,Cs=lpeg.P,lpeg.S,lpeg.C,lpeg.Ct,lpeg.Cc,lpeg.Cs local unquoted=patterns.squote*C(patterns.nosquote)*patterns.squote+patterns.dquote*C(patterns.nodquote)*patterns.dquote function string.unquoted(str) return lpegmatch(unquoted,str) or str end function string.quoted(str) return format("%q",str) end function string.count(str,pattern) local n=0 for _ in gmatch(str,pattern) do n=n+1 end return n end function string.limit(str,n,sentinel) if #str>n then sentinel=sentinel or "..." return sub(str,1,(n-#sentinel))..sentinel else return str end end local stripper=patterns.stripper local fullstripper=patterns.fullstripper local collapser=patterns.collapser local nospacer=patterns.nospacer local longtostring=patterns.longtostring function string.strip(str) return str and lpegmatch(stripper,str) or "" end function string.fullstrip(str) return str and lpegmatch(fullstripper,str) or "" end function string.collapsespaces(str) return str and lpegmatch(collapser,str) or "" end function string.nospaces(str) return str and lpegmatch(nospacer,str) or "" end function string.longtostring(str) return str and lpegmatch(longtostring,str) or "" end local pattern=P(" ")^0*P(-1) function string.is_empty(str) if not str or str=="" then return true else return lpegmatch(pattern,str) and true or false end end local anything=patterns.anything local allescapes=Cc("%")*S(".-+%?()[]*") local someescapes=Cc("%")*S(".-+%()[]") local matchescapes=Cc(".")*S("*?") local pattern_a=Cs ((allescapes+anything )^0 ) local pattern_b=Cs ((someescapes+matchescapes+anything )^0 ) local pattern_c=Cs (Cc("^")*(someescapes+matchescapes+anything )^0*Cc("$") ) function string.escapedpattern(str,simple) return lpegmatch(simple and pattern_b or pattern_a,str) end function string.topattern(str,lowercase,strict) if str=="" or type(str)~="string" then return ".*" elseif strict then str=lpegmatch(pattern_c,str) else str=lpegmatch(pattern_b,str) end if lowercase then return lower(str) else return str end end function string.valid(str,default) return (type(str)=="string" and str~="" and str) or default or nil end string.itself=function(s) return s end local pattern_c=Ct(C(1)^0) local pattern_b=Ct((C(1)/byte)^0) function string.totable(str,bytes) return lpegmatch(bytes and pattern_b or pattern_c,str) end local replacer=lpeg.replacer("@","%%") function string.tformat(fmt,...) return format(lpegmatch(replacer,fmt),...) end string.quote=string.quoted string.unquote=string.unquoted if not string.bytetable then local limit=5000 function string.bytetable(str) local n=#str if n>limit then local t={ byte(str,1,limit) } for i=limit+1,n do t[i]=byte(str,i) end return t else return { byte(str,1,n) } end end end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-table']={ 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,select=type,next,tostring,tonumber,select local table,string=table,string local concat,sort,insert,remove=table.concat,table.sort,table.insert,table.remove local format,lower,dump=string.format,string.lower,string.dump local getmetatable,setmetatable=getmetatable,setmetatable local lpegmatch,patterns=lpeg.match,lpeg.patterns local floor=math.floor local stripper=patterns.stripper function table.getn(t) return t and #t end function table.strip(tab) local lst,l={},0 for i=1,#tab do local s=lpegmatch(stripper,tab[i]) or "" if s=="" then else l=l+1 lst[l]=s end end return lst end function table.keys(t) if t then local keys,k={},0 for key in next,t do k=k+1 keys[k]=key end return keys else return {} end end local function compare(a,b) local ta=type(a) if ta=="number" then local tb=type(b) if ta==tb then return a1 then sort(srt) end return srt else return {} end end local function sortedindexonly(tab) if tab then local srt,s={},0 for key in next,tab do if type(key)=="number" then s=s+1 srt[s]=key end end if s>1 then sort(srt) end return srt else return {} end end local function sortedhashkeys(tab,cmp) if tab then local srt,s={},0 for key in next,tab do if key then s=s+1 srt[s]=key end end if s>1 then sort(srt,cmp) end return srt else return {} end end function table.allkeys(t) local keys={} for k,v in next,t do for k in next,v do keys[k]=true end end return sortedkeys(keys) end table.sortedkeys=sortedkeys table.sortedhashonly=sortedhashonly table.sortedindexonly=sortedindexonly table.sortedhashkeys=sortedhashkeys local function nothing() end local function sortedhash(t,cmp) if t then local s if cmp then s=sortedhashkeys(t,function(a,b) return cmp(t,a,b) end) else s=sortedkeys(t) end local m=#s if m==1 then return next,t elseif m>0 then local n=0 return function() if n0 then local n=0 for _,v in next,t do n=n+1 if type(v)=="table" then return nil end end local haszero=rawget(t,0) if n==nt then local tt={} for i=1,nt do local v=t[i] local tv=type(v) if tv=="number" then if hexify then tt[i]=format("0x%X",v) else tt[i]=v end elseif tv=="string" then tt[i]=format("%q",v) elseif tv=="boolean" then tt[i]=v and "true" or "false" else return nil end end return tt elseif haszero and (n==nt+1) then local tt={} for i=0,nt do local v=t[i] local tv=type(v) if tv=="number" then if hexify then tt[i+1]=format("0x%X",v) else tt[i+1]=v end elseif tv=="string" then tt[i+1]=format("%q",v) elseif tv=="boolean" then tt[i+1]=v and "true" or "false" else return nil end end tt[1]="[0] = "..tt[1] return tt end end return nil end table.is_simple_table=is_simple_table local propername=patterns.propername local function dummy() end local function do_serialize(root,name,depth,level,indexed) if level>0 then depth=depth.." " if indexed then handle(format("%s{",depth)) else local tn=type(name) if tn=="number" then if hexify then handle(format("%s[0x%X]={",depth,name)) else handle(format("%s[%s]={",depth,name)) end elseif tn=="string" then if noquotes and not reserved[name] and lpegmatch(propername,name) then handle(format("%s%s={",depth,name)) else handle(format("%s[%q]={",depth,name)) end elseif tn=="boolean" then handle(format("%s[%s]={",depth,name and "true" or "false")) else handle(format("%s{",depth)) end end end if root and next(root)~=nil then local first,last=nil,0 if compact then last=#root 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 compact and first and tk=="number" and k>=first and k<=last then if tv=="number" then if hexify then handle(format("%s 0x%X,",depth,v)) else handle(format("%s %s,",depth,v)) end elseif tv=="string" then handle(format("%s %q,",depth,v)) elseif tv=="table" then if next(v)==nil then handle(format("%s {},",depth)) elseif inline then local st=is_simple_table(v,hexify) if st then handle(format("%s { %s },",depth,concat(st,", "))) else do_serialize(v,k,depth,level+1,true) end else do_serialize(v,k,depth,level+1,true) end elseif tv=="boolean" then handle(format("%s %s,",depth,v and "true" or "false")) elseif tv=="function" then if functions then handle(format('%s load(%q),',depth,dump(v))) else handle(format('%s "function",',depth)) end else handle(format("%s %q,",depth,tostring(v))) end elseif k=="__p__" then if false then handle(format("%s __p__=nil,",depth)) end elseif tv=="number" then if tk=="number" then if hexify then handle(format("%s [0x%X]=0x%X,",depth,k,v)) else handle(format("%s [%s]=%s,",depth,k,v)) end elseif tk=="boolean" then if hexify then handle(format("%s [%s]=0x%X,",depth,k and "true" or "false",v)) else handle(format("%s [%s]=%s,",depth,k and "true" or "false",v)) end elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then if hexify then handle(format("%s %s=0x%X,",depth,k,v)) else handle(format("%s %s=%s,",depth,k,v)) end else if hexify then handle(format("%s [%q]=0x%X,",depth,k,v)) else handle(format("%s [%q]=%s,",depth,k,v)) end end elseif tv=="string" then if tk=="number" then if hexify then handle(format("%s [0x%X]=%q,",depth,k,v)) else handle(format("%s [%s]=%q,",depth,k,v)) end elseif tk=="boolean" then handle(format("%s [%s]=%q,",depth,k and "true" or "false",v)) elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then handle(format("%s %s=%q,",depth,k,v)) else handle(format("%s [%q]=%q,",depth,k,v)) end elseif tv=="table" then if next(v)==nil then if tk=="number" then if hexify then handle(format("%s [0x%X]={},",depth,k)) else handle(format("%s [%s]={},",depth,k)) end elseif tk=="boolean" then handle(format("%s [%s]={},",depth,k and "true" or "false")) elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then handle(format("%s %s={},",depth,k)) else handle(format("%s [%q]={},",depth,k)) end elseif inline then local st=is_simple_table(v,hexify) if st then if tk=="number" then if hexify then handle(format("%s [0x%X]={ %s },",depth,k,concat(st,", "))) else handle(format("%s [%s]={ %s },",depth,k,concat(st,", "))) end elseif tk=="boolean" then handle(format("%s [%s]={ %s },",depth,k and "true" or "false",concat(st,", "))) elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then handle(format("%s %s={ %s },",depth,k,concat(st,", "))) else handle(format("%s [%q]={ %s },",depth,k,concat(st,", "))) end else do_serialize(v,k,depth,level+1) end else do_serialize(v,k,depth,level+1) end elseif tv=="boolean" then if tk=="number" then if hexify then handle(format("%s [0x%X]=%s,",depth,k,v and "true" or "false")) else handle(format("%s [%s]=%s,",depth,k,v and "true" or "false")) end elseif tk=="boolean" then handle(format("%s [%s]=%s,",depth,tostring(k),v and "true" or "false")) elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then handle(format("%s %s=%s,",depth,k,v and "true" or "false")) else handle(format("%s [%q]=%s,",depth,k,v and "true" or "false")) end elseif tv=="function" then if functions then local getinfo=debug and debug.getinfo if getinfo then local f=getinfo(v).what=="C" and dump(dummy) or dump(v) if tk=="number" then if hexify then handle(format("%s [0x%X]=load(%q),",depth,k,f)) else handle(format("%s [%s]=load(%q),",depth,k,f)) end elseif tk=="boolean" then handle(format("%s [%s]=load(%q),",depth,k and "true" or "false",f)) elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then handle(format("%s %s=load(%q),",depth,k,f)) else handle(format("%s [%q]=load(%q),",depth,k,f)) end end end else if tk=="number" then if hexify then handle(format("%s [0x%X]=%q,",depth,k,tostring(v))) else handle(format("%s [%s]=%q,",depth,k,tostring(v))) end elseif tk=="boolean" then handle(format("%s [%s]=%q,",depth,k and "true" or "false",tostring(v))) elseif tk~="string" then elseif noquotes and not reserved[k] and lpegmatch(propername,k) then handle(format("%s %s=%q,",depth,k,tostring(v))) else handle(format("%s [%q]=%q,",depth,k,tostring(v))) end end end end if level>0 then handle(format("%s},",depth)) end end local function serialize(_handle,root,name,specification) local tname=type(name) if type(specification)=="table" then noquotes=specification.noquotes hexify=specification.hexify handle=_handle or specification.handle or print functions=specification.functions compact=specification.compact inline=specification.inline and compact metacheck=specification.metacheck if functions==nil then functions=true end if compact==nil then compact=true end if inline==nil then inline=compact end if metacheck==nil then metacheck=true end else noquotes=false hexify=false handle=_handle or print compact=true inline=true functions=true metacheck=true end if tname=="string" then if name=="return" then handle("return {") else handle(name.."={") end elseif tname=="number" then if hexify then handle(format("[0x%X]={",name)) else handle("["..name.."]={") end elseif tname=="boolean" then if name then handle("return {") else handle("{") end else handle("t={") end if root then if metacheck and 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 do_serialize(root,name,"",0) end end handle("}") end function table.serialize(root,name,specification) local t,n={},0 local function flush(s) n=n+1 t[n]=s end serialize(flush,root,name,specification) return concat(t,"\n") end table.tohandle=serialize local maxtab=2*1024 function table.tofile(filename,root,name,specification) local f=io.open(filename,'w') if f then if maxtab>1 then local t,n={},0 local function flush(s) n=n+1 t[n]=s if n>maxtab then f:write(concat(t,"\n"),"\n") t,n={},0 end end serialize(flush,root,name,specification) f:write(concat(t,"\n"),"\n") else local function flush(s) f:write(s,"\n") end serialize(flush,root,name,specification) end f:close() io.flush() end end local function flattened(t,f,depth) if f==nil then f={} depth=0xFFFF elseif tonumber(f) then depth=f f={} elseif not depth then depth=0xFFFF end for k,v in next,t do if type(k)~="number" then if depth>0 and type(v)=="table" then flattened(v,f,depth-1) else f[#f+1]=v end end end for k=1,#t do local v=t[k] if depth>0 and type(v)=="table" then flattened(v,f,depth-1) else f[#f+1]=v end end return f end table.flattened=flattened local function collapsed(t,f,h) if f==nil then f={} h={} end for k=1,#t do local v=t[k] if type(v)=="table" then collapsed(v,f,h) elseif not h[v] then f[#f+1]=v h[v]=true end end return f end local function collapsedhash(t,h) if h==nil then h={} end for k=1,#t do local v=t[k] if type(v)=="table" then collapsedhash(v,h) else h[v]=true end end return h end table.collapsed=collapsed table.collapsedhash=collapsedhash local function unnest(t,f) if not f then f={} end for i=1,#t do local v=t[i] if type(v)=="table" then if type(v[1])=="table" then unnest(v,f) else f[#f+1]=v end else f[#f+1]=v end end return f end function table.unnest(t) return unnest(t) end local function are_equal(a,b,n,m) if a==b then return true elseif a and b and #a==#b then n=n or 1 m=m or #a for i=n,m do local ai,bi=a[i],b[i] if ai==bi then elseif type(ai)=="table" and type(bi)=="table" then if not are_equal(ai,bi) then return false end else return false end end return true else return false end end local function identical(a,b) if a~=b then for ka,va in next,a do local vb=b[ka] if va==vb then elseif type(va)=="table" and type(vb)=="table" then if not identical(va,vb) then return false end else return false end end end return true end table.identical=identical table.are_equal=are_equal local function sparse(old,nest,keeptables) local new={} for k,v in next,old do if not (v=="" or v==false) then if nest and type(v)=="table" then v=sparse(v,nest) if keeptables or next(v)~=nil then new[k]=v end else new[k]=v end end end return new end table.sparse=sparse function table.compact(t) return sparse(t,true,true) end function table.contains(t,v) if t then for i=1,#t do if t[i]==v then return i end end end return false end function table.count(t) local n=0 for k,v in next,t do n=n+1 end return n end function table.swapped(t,s) local n={} if s then for k,v in next,s do n[k]=v end end for k,v in next,t do n[v]=k end return n end function table.hashed(t) for i=1,#t do t[t[i]]=i end return t end function table.mirrored(t) local n={} for k,v in next,t do n[v]=k n[k]=v end return n end function table.reversed(t) if t then local tt,tn={},#t if tn>0 then local ttn=0 for i=tn,1,-1 do ttn=ttn+1 tt[ttn]=t[i] end end return tt end end function table.reverse(t) if t then local n=#t local m=n+1 for i=1,floor(n/2) do local j=m-i t[i],t[j]=t[j],t[i] end return t end end local function sequenced(t,sep,simple) if not t then return "" elseif type(t)=="string" then return t end local n=#t local s={} if n>0 then for i=1,n do local v=t[i] if type(v)=="table" then s[i]="{"..sequenced(v,sep,simple).."}" else s[i]=tostring(t[i]) end end else n=0 for k,v in sortedhash(t) do if simple then if v==true then n=n+1 s[n]=k elseif v and v~="" then n=n+1 if type(v)=="table" then s[n]=k.."={"..sequenced(v,sep,simple).."}" else s[n]=k.."="..tostring(v) end end else n=n+1 if type(v)=="table" then s[n]=k.."={"..sequenced(v,sep,simple).."}" else s[n]=k.."="..tostring(v) end end end end return concat(s,sep or " | ") end table.sequenced=sequenced function table.print(t,...) if type(t)~="table" then print(tostring(t)) else serialize(print,t,...) end end if setinspector then setinspector("table",function(v) if type(v)=="table" then serialize(print,v,"table") return true end end) end function table.sub(t,i,j) return { unpack(t,i,j) } end function table.is_empty(t) return not t or next(t)==nil end function table.has_one_entry(t) return t and next(t,next(t))==nil end function table.loweredkeys(t) local l={} for k,v in next,t do l[lower(k)]=v end return l end function table.unique(old) local hash={} local new={} local n=0 for i=1,#old do local oi=old[i] if not hash[oi] then n=n+1 new[n]=oi hash[oi]=true end end return new end function table.sorted(t,...) sort(t,...) return t end function table.values(t,s) if t then local values,keys,v={},{},0 for key,value in next,t do if not keys[value] then v=v+1 values[v]=value keys[k]=key end end if s then sort(values) end return values else return {} end end function table.filtered(t,pattern,sort,cmp) if t and type(pattern)=="string" then if sort then local s if cmp then s=sortedhashkeys(t,function(a,b) return cmp(t,a,b) end) else s=sortedkeys(t) end local n=0 local m=#s local function kv(s) while n0 then return true else return str=="yes" or str=="on" or str=="t" end end string.toboolean=toboolean function string.booleanstring(str) if str=="0" then return false elseif str=="1" then return true elseif str=="" then return false elseif str=="false" then return false elseif str=="true" then return true elseif (tonumber(str) or 0)>0 then return true else return str=="yes" or str=="on" or str=="t" end end function string.is_boolean(str,default,strict) if type(str)=="string" then if str=="true" or str=="yes" or str=="on" or str=="t" or (not strict and str=="1") then return true elseif str=="false" or str=="no" or str=="off" or str=="f" or (not strict and str=="0") then return false end end return default end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-number']={ 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 tostring,tonumber=tostring,tonumber local format,floor,match,rep=string.format,math.floor,string.match,string.rep local concat,insert=table.concat,table.insert local lpegmatch=lpeg.match local floor=math.floor number=number or {} local number=number if bit32 then local bextract=bit32.extract local t={ "0","0","0","0","0","0","0","0", "0","0","0","0","0","0","0","0", "0","0","0","0","0","0","0","0", "0","0","0","0","0","0","0","0", } function number.tobitstring(b,m,w) if not w then w=32 end local n=w for i=0,w-1 do local v=bextract(b,i) local k=w-i if v==1 then n=k t[k]="1" else t[k]="0" end end if w then return concat(t,"",1,w) elseif m then m=33-m*8 if m<1 then m=1 end return concat(t,"",1,m) elseif n<8 then return concat(t) elseif n<16 then return concat(t,"",9) elseif n<24 then return concat(t,"",17) else return concat(t,"",25) end end else function number.tobitstring(n,m) if n>0 then local t={} while n>0 do insert(t,1,n%2>0 and 1 or 0) n=floor(n/2) end local nn=8-#t%8 if nn>0 and nn<8 then for i=1,nn do insert(t,1,0) end end if m then m=m*8-#t if m>0 then insert(t,1,rep("0",m)) end end return concat(t) elseif m then rep("00000000",m) else return "00000000" end end end function number.valid(str,default) return tonumber(str) or default or nil end function number.toevenhex(n) local s=format("%X",n) if #s%2==0 then return s else return "0"..s end end function number.bytetodecimal(b) local d=floor(b*100/255+0.5) if d>100 then return 100 elseif d<-100 then return -100 else return d end end function number.decimaltobyte(d) local b=floor(d*255/100+0.5) if b>255 then return 255 elseif b<-255 then return -255 else return b end end function number.idiv(i,d) return floor(i/d) end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-math']={ 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" } if not math.ceiling then math.ceiling=math.ceil end if not math.round then local floor=math.floor function math.round(x) return floor(x+0.5) end end if not math.div then local floor=math.floor function math.div(n,m) return floor(n/m) end end if not math.mod then function math.mod(n,m) return n%m end end if not math.sind then local sin,cos,tan=math.sin,math.cos,math.tan local pipi=2*math.pi/360 function math.sind(d) return sin(d*pipi) end function math.cosd(d) return cos(d*pipi) end function math.tand(d) return tan(d*pipi) end end if not math.odd then function math.odd (n) return n%2~=0 end function math.even(n) return n%2==0 end end if not math.cosh then local exp=math.exp function math.cosh(x) local xx=exp(x) return (xx+1/xx)/2 end function math.sinh(x) local xx=exp(x) return (xx-1/xx)/2 end function math.tanh(x) local xx=exp(x) return (xx-1/xx)/(xx+1/xx) end end if not math.pow then function math.pow(x,y) return x^y end end if not math.atan2 then math.atan2=math.atan end if not math.ldexp then function math.ldexp(x,e) return x*2.0^e end end if not math.log10 then local log=math.log function math.log10(x) return log(x,10) end end if not math.type then function math.type() return "float" end end if not math.tointeger then math.mininteger=-0x4FFFFFFFFFFF math.maxinteger=0x4FFFFFFFFFFF local floor=math.floor function math.tointeger(n) local f=floor(n) return f==n and f or nil end end if not math.ult then local floor=math.floor function math.tointeger(m,n) return floor(m)0 then f:seek("set",0) return f:read(size) else return "" end end io.readall=readall function io.loaddata(filename,textmode) local f=open(filename,(textmode and 'r') or 'rb') if f then local size=f:seek("end") local data=nil if size>0 then f:seek("set",0) data=f:read(size) end f:close() return data end end function io.copydata(source,target,action) local f=open(source,"rb") if f then local g=open(target,"wb") if g then local size=f:seek("end") if size>0 then f:seek("set",0) local data=f:read(size) if action then data=action(data) end if data then g:write(data) end end g:close() end f:close() flush() end end function io.savedata(filename,data,joiner) local f=open(filename,"wb") if f then if type(data)=="table" then f:write(concat(data,joiner or "")) elseif type(data)=="function" then data(f) else f:write(data or "") end f:close() flush() return true else return false end end if fio and fio.readline then local readline=fio.readline function io.loadlines(filename,n) local f=open(filename,'r') if not f then elseif n then local lines={} for i=1,n do local line=readline(f) if line then lines[i]=line else break end end f:close() lines=concat(lines,"\n") if #lines>0 then return lines end else local line=readline(f) f:close() if line and #line>0 then return line end end end else function io.loadlines(filename,n) local f=open(filename,'r') if not f then elseif n then local lines={} for i=1,n do local line=f:read("*lines") if line then lines[i]=line else break end end f:close() lines=concat(lines,"\n") if #lines>0 then return lines end else local line=f:read("*line") or "" f:close() if #line>0 then return line end end end end function io.loadchunk(filename,n) local f=open(filename,'rb') if f then local data=f:read(n or 1024) f:close() if #data>0 then return data end end end function io.exists(filename) local f=open(filename) if f==nil then return false else f:close() return true end end function io.size(filename) local f=open(filename) if f==nil then return 0 else local s=f:seek("end") f:close() return s end end local function noflines(f) if type(f)=="string" then local f=open(filename) if f then local n=f and noflines(f) or 0 f:close() return n else return 0 end else local n=0 for _ in f:lines() do n=n+1 end f:seek('set',0) return n end end io.noflines=noflines local nextchar={ [ 4]=function(f) return f:read(1,1,1,1) end, [ 2]=function(f) return f:read(1,1) end, [ 1]=function(f) return f:read(1) end, [-2]=function(f) local a,b=f:read(1,1) return b,a end, [-4]=function(f) local a,b,c,d=f:read(1,1,1,1) return d,c,b,a end } function io.characters(f,n) if f then return nextchar[n or 1],f end end local nextbyte={ [4]=function(f) local a,b,c,d=f:read(1,1,1,1) if d then return byte(a),byte(b),byte(c),byte(d) end end, [3]=function(f) local a,b,c=f:read(1,1,1) if b then return byte(a),byte(b),byte(c) end end, [2]=function(f) local a,b=f:read(1,1) if b then return byte(a),byte(b) end end, [1]=function (f) local a=f:read(1) if a then return byte(a) end end, [-2]=function (f) local a,b=f:read(1,1) if b then return byte(b),byte(a) end end, [-3]=function(f) local a,b,c=f:read(1,1,1) if b then return byte(c),byte(b),byte(a) end end, [-4]=function(f) local a,b,c,d=f:read(1,1,1,1) if d then return byte(d),byte(c),byte(b),byte(a) end end } function io.bytes(f,n) if f then return nextbyte[n or 1],f else return nil,nil end end function io.ask(question,default,options) while true do write(question) if options then write(format(" [%s]",concat(options,"|"))) end if default then write(format(" [%s]",default)) end write(format(" ")) flush() local answer=read() answer=gsub(answer,"^%s*(.*)%s*$","%1") if answer=="" and default then return default elseif not options then return answer else for k=1,#options do if options[k]==answer then return answer end end local pattern="^"..answer for k=1,#options do local v=options[k] if find(v,pattern) then return v end end end end end local function readnumber(f,n,m) if m then f:seek("set",n) n=m end if n==1 then return byte(f:read(1)) elseif n==2 then local a,b=byte(f:read(2),1,2) return 0x100*a+b elseif n==3 then local a,b,c=byte(f:read(3),1,3) return 0x10000*a+0x100*b+c elseif n==4 then local a,b,c,d=byte(f:read(4),1,4) return 0x1000000*a+0x10000*b+0x100*c+d elseif n==8 then local a,b=readnumber(f,4),readnumber(f,4) return 0x100*a+b elseif n==12 then local a,b,c=readnumber(f,4),readnumber(f,4),readnumber(f,4) return 0x10000*a+0x100*b+c elseif n==-2 then local b,a=byte(f:read(2),1,2) return 0x100*a+b elseif n==-3 then local c,b,a=byte(f:read(3),1,3) return 0x10000*a+0x100*b+c elseif n==-4 then local d,c,b,a=byte(f:read(4),1,4) return 0x1000000*a+0x10000*b+0x100*c+d elseif n==-8 then local h,g,f,e,d,c,b,a=byte(f:read(8),1,8) return 0x100000000000000*a+0x1000000000000*b+0x10000000000*c+0x100000000*d+0x1000000*e+0x10000*f+0x100*g+h else return 0 end end io.readnumber=readnumber function io.readstring(f,n,m) if m then f:seek("set",n) n=m end local str=gsub(f:read(n),"\000","") return str end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-os']={ 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 os=os local date,time=os.date,os.time local find,format,gsub,upper,gmatch=string.find,string.format,string.gsub,string.upper,string.gmatch local concat=table.concat local random,ceil,randomseed=math.random,math.ceil,math.randomseed local rawget,rawset,type,getmetatable,setmetatable,tonumber,tostring=rawget,rawset,type,getmetatable,setmetatable,tonumber,tostring do local selfdir=os.selfdir if selfdir=="" then selfdir=nil end if not selfdir then if arg then for i=1,#arg do local a=arg[i] if find(a,"^%-%-[c:]*texmfbinpath=") then selfdir=gsub(a,"^.-=","") break end end end if not selfdir then selfdir=os.selfbin or "luatex" if find(selfdir,"[/\\]") then selfdir=gsub(selfdir,"[/\\][^/\\]*$","") elseif os.getenv then local path=os.getenv("PATH") local name=gsub(selfdir,"^.*[/\\][^/\\]","") local patt="[^:]+" if os.type=="windows" then patt="[^;]+" name=name..".exe" end local isfile if lfs then local attributes=lfs.attributes isfile=function(name) local a=attributes(name,"mode") return a=="file" or a=="link" or nil end else local open=io.open isfile=function(name) local f=open(name) if f then f:close() return true end end end for p in gmatch(path,patt) do if isfile(p.."/"..name) then selfdir=p break end end end end os.selfdir=selfdir or "." end end math.initialseed=tonumber(string.sub(string.reverse(tostring(ceil(socket and socket.gettime()*10000 or time()))),1,6)) randomseed(math.initialseed) if not os.__getenv__ then os.__getenv__=os.getenv os.__setenv__=os.setenv if os.env then local osgetenv=os.getenv local ossetenv=os.setenv local osenv=os.env local _=osenv.PATH function os.setenv(k,v) if v==nil then v="" end local K=upper(k) osenv[K]=v if type(v)=="table" then v=concat(v,";") end ossetenv(K,v) end function os.getenv(k) local K=upper(k) local v=osenv[K] or osenv[k] or osgetenv(K) or osgetenv(k) if v=="" then return nil else return v end end else local ossetenv=os.setenv local osgetenv=os.getenv local osenv={} function os.setenv(k,v) if v==nil then v="" end local K=upper(k) osenv[K]=v end function os.getenv(k) local K=upper(k) local v=osenv[K] or osgetenv(K) or osgetenv(k) if v=="" then return nil else return v end end local function __index(t,k) return os.getenv(k) end local function __newindex(t,k,v) os.setenv(k,v) end os.env={} setmetatable(os.env,{ __index=__index,__newindex=__newindex } ) end end local execute=os.execute local iopopen=io.popen local function resultof(command) local handle=iopopen(command,"r") if handle then local result=handle:read("*all") or "" handle:close() return result else return "" end end os.resultof=resultof function os.pipeto(command) return iopopen(command,"w") end if not io.fileseparator then if find(os.getenv("PATH"),";",1,true) then io.fileseparator,io.pathseparator,os.type="\\",";",os.type or "windows" else io.fileseparator,io.pathseparator,os.type="/",":",os.type or "unix" end end os.type=os.type or (io.pathseparator==";" and "windows") or "unix" os.name=os.name or (os.type=="windows" and "mswin" ) or "linux" if os.type=="windows" then os.libsuffix,os.binsuffix,os.binsuffixes='dll','exe',{ 'exe','cmd','bat' } else os.libsuffix,os.binsuffix,os.binsuffixes='so','',{ '' } end local launchers={ windows="start %s", macosx="open %s", unix="xdg-open %s &> /dev/null &", } function os.launch(str) execute(format(launchers[os.name] or launchers.unix,str)) end if not os.times then function os.times() return { utime=os.gettimeofday(), stime=0, cutime=0, cstime=0, } end end local gettimeofday=os.gettimeofday or os.clock os.gettimeofday=gettimeofday local startuptime=gettimeofday() function os.runtime() return gettimeofday()-startuptime end local resolvers=os.resolvers or {} os.resolvers=resolvers setmetatable(os,{ __index=function(t,k) local r=resolvers[k] return r and r(t,k) or nil end }) local name,platform=os.name or "linux",os.getenv("MTX_PLATFORM") or "" if platform~="" then os.platform=platform elseif os.type=="windows" then function resolvers.platform(t,k) local architecture=os.getenv("PROCESSOR_ARCHITECTURE") or "" local platform="" if find(architecture,"AMD64",1,true) then platform="win64" else platform="mswin" end os.setenv("MTX_PLATFORM",platform) os.platform=platform return platform end elseif name=="linux" then function resolvers.platform(t,k) local architecture=os.getenv("HOSTTYPE") or resultof("uname -m") or "" local platform=os.getenv("MTX_PLATFORM") or "" local musl=find(os.selfdir or "","linuxmusl") if platform~="" then elseif find(architecture,"x86_64",1,true) then platform=musl and "linuxmusl" or "linux-64" elseif find(architecture,"ppc",1,true) then platform="linux-ppc" else platform=musl and "linuxmusl" or "linux" end os.setenv("MTX_PLATFORM",platform) os.platform=platform return platform end elseif name=="macosx" then function resolvers.platform(t,k) local architecture=resultof("echo $HOSTTYPE") or "" local platform="" if architecture=="" then platform="osx-intel" elseif find(architecture,"i386",1,true) then platform="osx-intel" elseif find(architecture,"x86_64",1,true) then platform="osx-64" else platform="osx-ppc" end os.setenv("MTX_PLATFORM",platform) os.platform=platform return platform end elseif name=="sunos" then function resolvers.platform(t,k) local architecture=resultof("uname -m") or "" local platform="" if find(architecture,"sparc",1,true) then platform="solaris-sparc" else platform="solaris-intel" end os.setenv("MTX_PLATFORM",platform) os.platform=platform return platform end elseif name=="freebsd" then function resolvers.platform(t,k) local architecture=resultof("uname -m") or "" local platform="" if find(architecture,"amd64",1,true) then platform="freebsd-amd64" else platform="freebsd" end os.setenv("MTX_PLATFORM",platform) os.platform=platform return platform end elseif name=="kfreebsd" then function resolvers.platform(t,k) local architecture=os.getenv("HOSTTYPE") or resultof("uname -m") or "" local platform="" if find(architecture,"x86_64",1,true) then platform="kfreebsd-amd64" else platform="kfreebsd-i386" end os.setenv("MTX_PLATFORM",platform) os.platform=platform return platform end else function resolvers.platform(t,k) local platform="linux" os.setenv("MTX_PLATFORM",platform) os.platform=platform return platform end end os.newline=name=="windows" and "\013\010" or "\010" function resolvers.bits(t,k) local bits=find(os.platform,"64",1,true) and 64 or 32 os.bits=bits return bits end local t={ 8,9,"a","b" } function os.uuid() return format("%04x%04x-4%03x-%s%03x-%04x-%04x%04x%04x", random(0xFFFF),random(0xFFFF), random(0x0FFF), t[ceil(random(4))] or 8,random(0x0FFF), random(0xFFFF), random(0xFFFF),random(0xFFFF),random(0xFFFF) ) end local d function os.timezone(delta) d=d or tonumber(tonumber(date("%H")-date("!%H"))) if delta then if d>0 then return format("+%02i:00",d) else return format("-%02i:00",-d) end else return 1 end end local timeformat=format("%%s%s",os.timezone(true)) local dateformat="!%Y-%m-%d %H:%M:%S" local lasttime=nil local lastdate=nil function os.fulltime(t,default) t=t and tonumber(t) or 0 if t>0 then elseif default then return default else t=time() end if t~=lasttime then lasttime=t lastdate=format(timeformat,date(dateformat)) end return lastdate end local dateformat="%Y-%m-%d %H:%M:%S" local lasttime=nil local lastdate=nil function os.localtime(t,default) t=t and tonumber(t) or 0 if t>0 then elseif default then return default else t=time() end if t~=lasttime then lasttime=t lastdate=date(dateformat,t) end return lastdate end function os.converttime(t,default) local t=tonumber(t) if t and t>0 then return date(dateformat,t) else return default or "-" end end local memory={} local function which(filename) local fullname=memory[filename] if fullname==nil then local suffix=file.suffix(filename) local suffixes=suffix=="" and os.binsuffixes or { suffix } for directory in gmatch(os.getenv("PATH"),"[^"..io.pathseparator.."]+") do local df=file.join(directory,filename) for i=1,#suffixes do local dfs=file.addsuffix(df,suffixes[i]) if io.exists(dfs) then fullname=dfs break end end end if not fullname then fullname=false end memory[filename]=fullname end return fullname end os.which=which os.where=which function os.today() return date("!*t") end function os.now() return date("!%Y-%m-%d %H:%M:%S") end if not os.sleep then local socket=socket function os.sleep(n) if not socket then socket=require("socket") end socket.sleep(n) end end local function isleapyear(year) return (year%4==0) and (year%100~=0 or year%400==0) end os.isleapyear=isleapyear local days={ 31,28,31,30,31,30,31,31,30,31,30,31 } local function nofdays(year,month) if not month then return isleapyear(year) and 365 or 364 else return month==2 and isleapyear(year) and 29 or days[month] end end os.nofdays=nofdays function os.weekday(day,month,year) return date("%w",time { year=year,month=month,day=day })+1 end function os.validdate(year,month,day) if month<1 then month=1 elseif month>12 then month=12 end if day<1 then day=1 else local max=nofdays(year,month) if day>max then day=max end end return year,month,day end local osexit=os.exit local exitcode=nil function os.setexitcode(code) exitcode=code end function os.exit(c) if exitcode~=nil then return osexit(exitcode) end if c~=nil then return osexit(c) end return osexit() end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-file']={ 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" } file=file or {} local file=file if not lfs then lfs=optionalrequire("lfs") end local insert,concat=table.insert,table.concat local match,find,gmatch=string.match,string.find,string.gmatch local lpegmatch=lpeg.match local getcurrentdir,attributes=lfs.currentdir,lfs.attributes local checkedsplit=string.checkedsplit local P,R,S,C,Cs,Cp,Cc,Ct=lpeg.P,lpeg.R,lpeg.S,lpeg.C,lpeg.Cs,lpeg.Cp,lpeg.Cc,lpeg.Ct local attributes=lfs.attributes function lfs.isdir(name) return attributes(name,"mode")=="directory" end function lfs.isfile(name) local a=attributes(name,"mode") return a=="file" or a=="link" or nil end function lfs.isfound(name) local a=attributes(name,"mode") return (a=="file" or a=="link") and name or nil end if sandbox then sandbox.redefine(lfs.isfile,"lfs.isfile") sandbox.redefine(lfs.isdir,"lfs.isdir") sandbox.redefine(lfs.isfound,"lfs.isfound") end local colon=P(":") local period=P(".") local periods=P("..") local fwslash=P("/") local bwslash=P("\\") local slashes=S("\\/") local noperiod=1-period local noslashes=1-slashes local name=noperiod^1 local suffix=period/""*(1-period-slashes)^1*-1 local pattern=C((1-(slashes^1*noslashes^1*-1))^1)*P(1) local function pathpart(name,default) return name and lpegmatch(pattern,name) or default or "" end local pattern=(noslashes^0*slashes)^1*C(noslashes^1)*-1 local function basename(name) return name and lpegmatch(pattern,name) or name end local pattern=(noslashes^0*slashes^1)^0*Cs((1-suffix)^1)*suffix^0 local function nameonly(name) return name and lpegmatch(pattern,name) or name end local pattern=(noslashes^0*slashes)^0*(noperiod^1*period)^1*C(noperiod^1)*-1 local function suffixonly(name) return name and lpegmatch(pattern,name) or "" end local pattern=(noslashes^0*slashes)^0*noperiod^1*((period*C(noperiod^1))^1)*-1+Cc("") local function suffixesonly(name) if name then return lpegmatch(pattern,name) else return "" end end file.pathpart=pathpart file.basename=basename file.nameonly=nameonly file.suffixonly=suffixonly file.suffix=suffixonly file.suffixesonly=suffixesonly file.suffixes=suffixesonly file.dirname=pathpart file.extname=suffixonly local drive=C(R("az","AZ"))*colon local path=C((noslashes^0*slashes)^0) local suffix=period*C(P(1-period)^0*P(-1)) local base=C((1-suffix)^0) local rest=C(P(1)^0) drive=drive+Cc("") path=path+Cc("") base=base+Cc("") suffix=suffix+Cc("") local pattern_a=drive*path*base*suffix local pattern_b=path*base*suffix local pattern_c=C(drive*path)*C(base*suffix) local pattern_d=path*rest function file.splitname(str,splitdrive) if not str then elseif splitdrive then return lpegmatch(pattern_a,str) else return lpegmatch(pattern_b,str) end end function file.splitbase(str) if str then return lpegmatch(pattern_d,str) else return "",str end end function file.nametotable(str,splitdrive) if str then local path,drive,subpath,name,base,suffix=lpegmatch(pattern_c,str) if splitdrive then return { path=path, drive=drive, subpath=subpath, name=name, base=base, suffix=suffix, } else return { path=path, name=name, base=base, suffix=suffix, } end end end local pattern=Cs(((period*(1-period-slashes)^1*-1)/""+1)^1) function file.removesuffix(name) return name and lpegmatch(pattern,name) end local suffix=period/""*(1-period-slashes)^1*-1 local pattern=Cs((noslashes^0*slashes^1)^0*((1-suffix)^1))*Cs(suffix) function file.addsuffix(filename,suffix,criterium) if not filename or not suffix or suffix=="" then return filename elseif criterium==true then return filename.."."..suffix elseif not criterium then local n,s=lpegmatch(pattern,filename) if not s or s=="" then return filename.."."..suffix else return filename end else local n,s=lpegmatch(pattern,filename) if s and s~="" then local t=type(criterium) if t=="table" then for i=1,#criterium do if s==criterium[i] then return filename end end elseif t=="string" then if s==criterium then return filename end end end return (n or filename).."."..suffix end end local suffix=period*(1-period-slashes)^1*-1 local pattern=Cs((1-suffix)^0) function file.replacesuffix(name,suffix) if name and suffix and suffix~="" then return lpegmatch(pattern,name).."."..suffix else return name end end local reslasher=lpeg.replacer(P("\\"),"/") function file.reslash(str) return str and lpegmatch(reslasher,str) end function file.is_writable(name) if not name then elseif lfs.isdir(name) then name=name.."/m_t_x_t_e_s_t.tmp" local f=io.open(name,"wb") if f then f:close() os.remove(name) return true end elseif lfs.isfile(name) then local f=io.open(name,"ab") if f then f:close() return true end else local f=io.open(name,"ab") if f then f:close() os.remove(name) return true end end return false end local readable=P("r")*Cc(true) function file.is_readable(name) if name then local a=attributes(name) return a and lpegmatch(readable,a.permissions) or false else return false end end file.isreadable=file.is_readable file.iswritable=file.is_writable function file.size(name) if name then local a=attributes(name) return a and a.size or 0 else return 0 end end function file.splitpath(str,separator) return str and checkedsplit(lpegmatch(reslasher,str),separator or io.pathseparator) end function file.joinpath(tab,separator) return tab and concat(tab,separator or io.pathseparator) end local someslash=S("\\/") local stripper=Cs(P(fwslash)^0/""*reslasher) local isnetwork=someslash*someslash*(1-someslash)+(1-fwslash-colon)^1*colon local isroot=fwslash^1*-1 local hasroot=fwslash^1 local reslasher=lpeg.replacer(S("\\/"),"/") local deslasher=lpeg.replacer(S("\\/")^1,"/") function file.join(one,two,three,...) if not two then return one=="" and one or lpegmatch(reslasher,one) end if one=="" then return lpegmatch(stripper,three and concat({ two,three,... },"/") or two) end if lpegmatch(isnetwork,one) then local one=lpegmatch(reslasher,one) local two=lpegmatch(deslasher,three and concat({ two,three,... },"/") or two) if lpegmatch(hasroot,two) then return one..two else return one.."/"..two end elseif lpegmatch(isroot,one) then local two=lpegmatch(deslasher,three and concat({ two,three,... },"/") or two) if lpegmatch(hasroot,two) then return two else return "/"..two end else return lpegmatch(deslasher,concat({ one,two,three,... },"/")) end end local drivespec=R("az","AZ")^1*colon local anchors=fwslash+drivespec local untouched=periods+(1-period)^1*P(-1) local mswindrive=Cs(drivespec*(bwslash/"/"+fwslash)^0) local mswinuncpath=(bwslash+fwslash)*(bwslash+fwslash)*Cc("//") local splitstarter=(mswindrive+mswinuncpath+Cc(false))*Ct(lpeg.splitat(S("/\\")^1)) local absolute=fwslash function file.collapsepath(str,anchor) if not str then return end if anchor==true and not lpegmatch(anchors,str) then str=getcurrentdir().."/"..str end if str=="" or str=="." then return "." elseif lpegmatch(untouched,str) then return lpegmatch(reslasher,str) end local starter,oldelements=lpegmatch(splitstarter,str) local newelements={} local i=#oldelements while i>0 do local element=oldelements[i] if element=='.' then elseif element=='..' then local n=i-1 while n>0 do local element=oldelements[n] if element~='..' and element~='.' then oldelements[n]='.' break else n=n-1 end end if n<1 then insert(newelements,1,'..') end elseif element~="" then insert(newelements,1,element) end i=i-1 end if #newelements==0 then return starter or "." elseif starter then return starter..concat(newelements,'/') elseif lpegmatch(absolute,str) then return "/"..concat(newelements,'/') else newelements=concat(newelements,'/') if anchor=="." and find(str,"^%./") then return "./"..newelements else return newelements end end end local validchars=R("az","09","AZ","--","..") local pattern_a=lpeg.replacer(1-validchars) local pattern_a=Cs((validchars+P(1)/"-")^1) local whatever=P("-")^0/"" local pattern_b=Cs(whatever*(1-whatever*-1)^1) function file.robustname(str,strict) if str then str=lpegmatch(pattern_a,str) or str if strict then return lpegmatch(pattern_b,str) or str else return str end end end local loaddata=io.loaddata local savedata=io.savedata file.readdata=loaddata file.savedata=savedata function file.copy(oldname,newname) if oldname and newname then local data=loaddata(oldname) if data and data~="" then savedata(newname,data) end end end local letter=R("az","AZ")+S("_-+") local separator=P("://") local qualified=period^0*fwslash+letter*colon+letter^1*separator+letter^1*fwslash local rootbased=fwslash+letter*colon lpeg.patterns.qualified=qualified lpeg.patterns.rootbased=rootbased function file.is_qualified_path(filename) return filename and lpegmatch(qualified,filename)~=nil end function file.is_rootbased_path(filename) return filename and lpegmatch(rootbased,filename)~=nil end function file.strip(name,dir) if name then local b,a=match(name,"^(.-)"..dir.."(.*)$") return a~="" and a or name end end function lfs.mkdirs(path) local full="" for sub in gmatch(path,"(/*[^\\/]+)") do full=full..sub lfs.mkdir(full) end end function file.withinbase(path) local l=0 if not find(path,"^/") then path="/"..path end for dir in gmatch(path,"/([^/]+)") do if dir==".." then l=l-1 elseif dir~="." then l=l+1 end if l<0 then return false end end return true end local symlinkattributes=lfs.symlinkattributes function lfs.readlink(name) return symlinkattributes(name,"target") or nil end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-gzip']={ version=1.001, author="Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } if not gzip then return end local suffix,suffixes=file.suffix,file.suffixes function gzip.load(filename) local f=io.open(filename,"rb") if not f then elseif suffix(filename)=="gz" then f:close() local g=gzip.open(filename,"rb") if g then local str=g:read("*all") g:close() return str end else local str=f:read("*all") f:close() return str end end function gzip.save(filename,data) if suffix(filename)~="gz" then filename=filename..".gz" end local f=io.open(filename,"wb") if f then local s=zlib.compress(data or "",9,nil,15+16) f:write(s) f:close() return #s end end function gzip.suffix(filename) local suffix,extra=suffixes(filename) local gzipped=extra=="gz" return suffix,gzipped end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-md5']={ version=1.001, author="Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright="PRAGMA ADE / ConTeXt Development Team", license="see context related readme files" } if not md5 then md5=optionalrequire("md5") end if not md5 then md5={ sum=function(str) print("error: md5 is not loaded (sum ignored)") return str end, sumhexa=function(str) print("error: md5 is not loaded (sumhexa ignored)") return str end, } end local md5,file=md5,file local gsub=string.gsub do local patterns=lpeg and lpeg.patterns if patterns then local bytestoHEX=patterns.bytestoHEX local bytestohex=patterns.bytestohex local bytestodec=patterns.bytestodec local lpegmatch=lpeg.match local md5sum=md5.sum if not md5.HEX then function md5.HEX(str) if str then return lpegmatch(bytestoHEX,md5sum(str)) end end end if not md5.hex then function md5.hex(str) if str then return lpegmatch(bytestohex,md5sum(str)) end end end if not md5.dec then function md5.dec(str) if str then return lpegmatch(bytestodec,md5sum(str)) end end end md5.sumhexa=md5.hex md5.sumHEXA=md5.HEX end end function file.needsupdating(oldname,newname,threshold) local oldtime=lfs.attributes(oldname,"modification") if oldtime then local newtime=lfs.attributes(newname,"modification") if not newtime then return true elseif newtime>=oldtime then return false elseif oldtime-newtime<(threshold or 1) then return false else return true end else return false end end file.needs_updating=file.needsupdating function file.syncmtimes(oldname,newname) local oldtime=lfs.attributes(oldname,"modification") if oldtime and lfs.isfile(newname) then lfs.touch(newname,oldtime,oldtime) end end function file.checksum(name) if md5 then local data=io.loaddata(name) if data then return md5.HEX(data) end end return nil end function file.loadchecksum(name) if md5 then local data=io.loaddata(name..".md5") return data and (gsub(data,"%s","")) end return nil end function file.savechecksum(name,checksum) if not checksum then checksum=file.checksum(name) end if checksum then io.savedata(name..".md5",checksum) return checksum end return nil end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-dir']={ 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,select=type,select local find,gmatch,match,gsub,sub=string.find,string.gmatch,string.match,string.gsub,string.sub local concat,insert,remove,unpack=table.concat,table.insert,table.remove,table.unpack local lpegmatch=lpeg.match local P,S,R,C,Cc,Cs,Ct,Cv,V=lpeg.P,lpeg.S,lpeg.R,lpeg.C,lpeg.Cc,lpeg.Cs,lpeg.Ct,lpeg.Cv,lpeg.V dir=dir or {} local dir=dir local lfs=lfs local attributes=lfs.attributes local walkdir=lfs.dir local isdir=lfs.isdir local isfile=lfs.isfile local currentdir=lfs.currentdir local chdir=lfs.chdir local mkdir=lfs.mkdir local onwindows=os.type=="windows" or find(os.getenv("PATH"),";",1,true) if onwindows then local tricky=S("/\\")*P(-1) isdir=function(name) if lpegmatch(tricky,name) then return attributes(name,"mode")=="directory" else return attributes(name.."/.","mode")=="directory" end end isfile=function(name) return attributes(name,"mode")=="file" end lfs.isdir=isdir lfs.isfile=isfile else isdir=function(name) return attributes(name,"mode")=="directory" end isfile=function(name) return attributes(name,"mode")=="file" end lfs.isdir=isdir lfs.isfile=isfile end function dir.current() return (gsub(currentdir(),"\\","/")) end local function glob_pattern_function(path,patt,recurse,action) if isdir(path) then local usedpath if path=="/" then usedpath="/." elseif not find(path,"/$") then usedpath=path.."/." path=path.."/" else usedpath=path end local dirs local nofdirs=0 for name in walkdir(usedpath) do if name~="." and name~=".." then local full=path..name local mode=attributes(full,'mode') if mode=='file' then if not patt or find(full,patt) then action(full) end elseif recurse and mode=="directory" then if dirs then nofdirs=nofdirs+1 dirs[nofdirs]=full else nofdirs=1 dirs={ full } end end end end if dirs then for i=1,nofdirs do glob_pattern_function(dirs[i],patt,recurse,action) end end end end local function glob_pattern_table(path,patt,recurse,result) if not result then result={} end local usedpath if path=="/" then usedpath="/." elseif not find(path,"/$") then usedpath=path.."/." path=path.."/" else usedpath=path end local dirs local nofdirs=0 local noffiles=#result for name,a in walkdir(usedpath) do if name~="." and name~=".." then local full=path..name local mode=attributes(full,'mode') if mode=='file' then if not patt or find(full,patt) then noffiles=noffiles+1 result[noffiles]=full end elseif recurse and mode=="directory" then if dirs then nofdirs=nofdirs+1 dirs[nofdirs]=full else nofdirs=1 dirs={ full } end end end end if dirs then for i=1,nofdirs do glob_pattern_table(dirs[i],patt,recurse,result) end end return result end local function globpattern(path,patt,recurse,method) local kind=type(method) if patt and sub(patt,1,-3)==path then patt=false end local okay=isdir(path) if kind=="function" then return okay and glob_pattern_function(path,patt,recurse,method) or {} elseif kind=="table" then return okay and glob_pattern_table(path,patt,recurse,method) or method else return okay and glob_pattern_table(path,patt,recurse,{}) or {} end end dir.globpattern=globpattern local function collectpattern(path,patt,recurse,result) local ok,scanner result=result or {} if path=="/" then ok,scanner,first=xpcall(function() return walkdir(path..".") end,function() end) else ok,scanner,first=xpcall(function() return walkdir(path) end,function() end) end if ok and type(scanner)=="function" then if not find(path,"/$") then path=path..'/' end for name in scanner,first do if name=="." then elseif name==".." then else local full=path..name local attr=attributes(full) local mode=attr.mode if mode=='file' then if find(full,patt) then result[name]=attr end elseif recurse and mode=="directory" then attr.list=collectpattern(full,patt,recurse) result[name]=attr end end end end return result end dir.collectpattern=collectpattern local separator,pattern if onwindows then local slash=S("/\\")/"/" pattern={ [1]=(Cs(P(".")+slash^1)+Cs(R("az","AZ")*P(":")*slash^0)+Cc("./"))*V(2)*V(3), [2]=Cs(((1-S("*?/\\"))^0*slash)^0), [3]=Cs(P(1)^0) } else pattern={ [1]=(C(P(".")+P("/")^1)+Cc("./"))*V(2)*V(3), [2]=C(((1-S("*?/"))^0*P("/"))^0), [3]=C(P(1)^0) } end local filter=Cs (( P("**")/".*"+P("*")/"[^/]*"+P("?")/"[^/]"+P(".")/"%%."+P("+")/"%%+"+P("-")/"%%-"+P(1) )^0 ) local function glob(str,t) if type(t)=="function" then if type(str)=="table" then for s=1,#str do glob(str[s],t) end elseif isfile(str) then t(str) else local root,path,base=lpegmatch(pattern,str) if root and path and base then local recurse=find(base,"**",1,true) local start=root..path local result=lpegmatch(filter,start..base) globpattern(start,result,recurse,t) end end else if type(str)=="table" then local t=t or {} for s=1,#str do glob(str[s],t) end return t elseif isfile(str) then if t then t[#t+1]=str return t else return { str } end else local root,path,base=lpegmatch(pattern,str) if root and path and base then local recurse=find(base,"**",1,true) local start=root..path local result=lpegmatch(filter,start..base) return globpattern(start,result,recurse,t) else return {} end end end end dir.glob=glob local function globfiles(path,recurse,func,files) if type(func)=="string" then local s=func func=function(name) return find(name,s) end end files=files or {} local noffiles=#files for name in walkdir(path) do if find(name,"^%.") then else local mode=attributes(name,'mode') if mode=="directory" then if recurse then globfiles(path.."/"..name,recurse,func,files) end elseif mode=="file" then if not func or func(name) then noffiles=noffiles+1 files[noffiles]=path.."/"..name end end end end return files end dir.globfiles=globfiles local function globdirs(path,recurse,func,files) if type(func)=="string" then local s=func func=function(name) return find(name,s) end end files=files or {} local noffiles=#files for name in walkdir(path) do if find(name,"^%.") then else local mode=attributes(name,'mode') if mode=="directory" then if not func or func(name) then noffiles=noffiles+1 files[noffiles]=path.."/"..name if recurse then globdirs(path.."/"..name,recurse,func,files) end end end end end return files end dir.globdirs=globdirs function dir.ls(pattern) return concat(glob(pattern),"\n") end local make_indeed=true if onwindows then function dir.mkdirs(...) local n=select("#",...) local str if n==1 then str=select(1,...) if isdir(str) then return str,true end else str="" for i=1,n do local s=select(i,...) if s=="" then elseif str=="" then str=s else str=str.."/"..s end end end local pth="" local drive=false local first,middle,last=match(str,"^(//)(//*)(.*)$") if first then else first,last=match(str,"^(//)/*(.-)$") if first then middle,last=match(str,"([^/]+)/+(.-)$") if middle then pth="//"..middle else pth="//"..last last="" end else first,middle,last=match(str,"^([a-zA-Z]:)(/*)(.-)$") if first then pth,drive=first..middle,true else middle,last=match(str,"^(/*)(.-)$") if not middle then last=str end end end end for s in gmatch(last,"[^/]+") do if pth=="" then pth=s elseif drive then pth,drive=pth..s,false else pth=pth.."/"..s end if make_indeed and not isdir(pth) then mkdir(pth) end end return pth,(isdir(pth)==true) end else function dir.mkdirs(...) local n=select("#",...) local str,pth if n==1 then str=select(1,...) if isdir(str) then return str,true end else str="" for i=1,n do local s=select(i,...) if s and s~="" then if str~="" then str=str.."/"..s else str=s end end end end str=gsub(str,"/+","/") if find(str,"^/") then pth="/" for s in gmatch(str,"[^/]+") do local first=(pth=="/") if first then pth=pth..s else pth=pth.."/"..s end if make_indeed and not first and not isdir(pth) then mkdir(pth) end end else pth="." for s in gmatch(str,"[^/]+") do pth=pth.."/"..s if make_indeed and not isdir(pth) then mkdir(pth) end end end return pth,(isdir(pth)==true) end end dir.makedirs=dir.mkdirs do local chdir=sandbox and sandbox.original(chdir) or chdir if onwindows then local xcurrentdir=dir.current function dir.expandname(str) local first,nothing,last=match(str,"^(//)(//*)(.*)$") if first then first=xcurrentdir().."/" end if not first then first,last=match(str,"^(//)/*(.*)$") end if not first then first,last=match(str,"^([a-zA-Z]:)(.*)$") if first and not find(last,"^/") then local d=currentdir() if chdir(first) then first=xcurrentdir() end chdir(d) end end if not first then first,last=xcurrentdir(),str end last=gsub(last,"//","/") last=gsub(last,"/%./","/") last=gsub(last,"^/*","") first=gsub(first,"/*$","") if last=="" or last=="." then return first else return first.."/"..last end end else function dir.expandname(str) if not find(str,"^/") then str=currentdir().."/"..str end str=gsub(str,"//","/") str=gsub(str,"/%./","/") str=gsub(str,"(.)/%.$","%1") return str end end end file.expandname=dir.expandname local stack={} function dir.push(newdir) local curdir=currentdir() insert(stack,curdir) if newdir and newdir~="" then chdir(newdir) return newdir else return curdir end end function dir.pop() local d=remove(stack) if d then chdir(d) end return d end local function found(...) for i=1,select("#",...) do local path=select(i,...) local kind=type(path) if kind=="string" then if isdir(path) then return path end elseif kind=="table" then local path=found(unpack(path)) if path then return path end end end end dir.found=found end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-unicode']={ 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" } utf=utf or {} if not string.utfcharacters then local gmatch=string.gmatch function string.characters(str) return gmatch(str,".[\128-\191]*") end end utf.characters=string.utfcharacters local type=type local char,byte,format,sub,gmatch=string.char,string.byte,string.format,string.sub,string.gmatch local concat=table.concat local P,C,R,Cs,Ct,Cmt,Cc,Carg,Cp=lpeg.P,lpeg.C,lpeg.R,lpeg.Cs,lpeg.Ct,lpeg.Cmt,lpeg.Cc,lpeg.Carg,lpeg.Cp local lpegmatch=lpeg.match local patterns=lpeg.patterns local tabletopattern=lpeg.utfchartabletopattern local bytepairs=string.bytepairs local finder=lpeg.finder local replacer=lpeg.replacer local p_utftype=patterns.utftype local p_utfstricttype=patterns.utfstricttype local p_utfoffset=patterns.utfoffset local p_utf8character=patterns.utf8character local p_utf8char=patterns.utf8char local p_utf8byte=patterns.utf8byte local p_utfbom=patterns.utfbom local p_newline=patterns.newline local p_whitespace=patterns.whitespace if not utf.char then utf.char=string.utfcharacter or (utf8 and utf8.char) if not utf.char then local char=string.char if bit32 then local rshift=bit32.rshift function utf.char(n) if n<0x80 then return char(n) elseif n<0x800 then return char( 0xC0+rshift(n,6), 0x80+(n%0x40) ) elseif n<0x10000 then return char( 0xE0+rshift(n,12), 0x80+(rshift(n,6)%0x40), 0x80+(n%0x40) ) elseif n<0x200000 then return char( 0xF0+rshift(n,18), 0x80+(rshift(n,12)%0x40), 0x80+(rshift(n,6)%0x40), 0x80+(n%0x40) ) else return "" end end else local floor=math.floor function utf.char(n) if n<0x80 then return char(n) elseif n<0x800 then return char( 0xC0+floor(n/0x40), 0x80+(n%0x40) ) elseif n<0x10000 then return char( 0xE0+floor(n/0x1000), 0x80+(floor(n/0x40)%0x40), 0x80+(n%0x40) ) elseif n<0x200000 then return char( 0xF0+floor(n/0x40000), 0x80+(floor(n/0x1000)%0x40), 0x80+(floor(n/0x40)%0x40), 0x80+(n%0x40) ) else return "" end end end end end if not utf.byte then utf.byte=string.utfvalue or (utf8 and utf8.codepoint) if not utf.byte then function utf.byte(c) return lpegmatch(p_utf8byte,c) end end end local utfchar,utfbyte=utf.char,utf.byte function utf.filetype(data) return data and lpegmatch(p_utftype,data) or "unknown" end local toentities=Cs ( ( patterns.utf8one+( patterns.utf8two+patterns.utf8three+patterns.utf8four )/function(s) local b=utfbyte(s) if b<127 then return s else return format("&#%X;",b) end end )^0 ) patterns.toentities=toentities function utf.toentities(str) return lpegmatch(toentities,str) end local one=P(1) local two=C(1)*C(1) local four=C(R(utfchar(0xD8),utfchar(0xFF)))*C(1)*C(1)*C(1) local pattern=P("\254\255")*Cs(( four/function(a,b,c,d) local ab=0xFF*byte(a)+byte(b) local cd=0xFF*byte(c)+byte(d) return utfchar((ab-0xD800)*0x400+(cd-0xDC00)+0x10000) end+two/function(a,b) return utfchar(byte(a)*256+byte(b)) end+one )^1 )+P("\255\254")*Cs(( four/function(b,a,d,c) local ab=0xFF*byte(a)+byte(b) local cd=0xFF*byte(c)+byte(d) return utfchar((ab-0xD800)*0x400+(cd-0xDC00)+0x10000) end+two/function(b,a) return utfchar(byte(a)*256+byte(b)) end+one )^1 ) function string.toutf(s) return lpegmatch(pattern,s) or s end local validatedutf=Cs ( ( patterns.utf8one+patterns.utf8two+patterns.utf8three+patterns.utf8four+P(1)/"�" )^0 ) patterns.validatedutf=validatedutf function utf.is_valid(str) return type(str)=="string" and lpegmatch(validatedutf,str) or false end if not utf.len then utf.len=string.utflength or (utf8 and utf8.len) if not utf.len then local n,f=0,1 local utfcharcounter=patterns.utfbom^-1*Cmt ( Cc(1)*patterns.utf8one^1+Cc(2)*patterns.utf8two^1+Cc(3)*patterns.utf8three^1+Cc(4)*patterns.utf8four^1, function(_,t,d) n=n+(t-f)/d f=t return true end )^0 function utf.len(str) n,f=0,1 lpegmatch(utfcharcounter,str or "") return n end end end utf.length=utf.len if not utf.sub then local utflength=utf.length local b,e,n,first,last=0,0,0,0,0 local function slide_zero(s,p) n=n+1 if n>=last then e=p-1 else return p end end local function slide_one(s,p) n=n+1 if n==first then b=p end if n>=last then e=p-1 else return p end end local function slide_two(s,p) n=n+1 if n==first then b=p else return true end end local pattern_zero=Cmt(p_utf8character,slide_zero)^0 local pattern_one=Cmt(p_utf8character,slide_one )^0 local pattern_two=Cmt(p_utf8character,slide_two )^0 local pattern_first=C(p_utf8character) function utf.sub(str,start,stop) if not start then return str end if start==0 then start=1 end if not stop then if start<0 then local l=utflength(str) start=l+start else start=start-1 end b,n,first=0,0,start lpegmatch(pattern_two,str) if n>=first then return sub(str,b) else return "" end end if start<0 or stop<0 then local l=utf.length(str) if start<0 then start=l+start if start<=0 then start=1 else start=start+1 end end if stop<0 then stop=l+stop if stop==0 then stop=1 else stop=stop+1 end end end if start==1 and stop==1 then return lpegmatch(pattern_first,str) or "" elseif start>stop then return "" elseif start>1 then b,e,n,first,last=0,0,0,start-1,stop lpegmatch(pattern_one,str) if n>=first and e==0 then e=#str end return sub(str,b,e) else b,e,n,last=1,0,0,stop lpegmatch(pattern_zero,str) if e==0 then e=#str end return sub(str,b,e) end end end function utf.remapper(mapping,option,action) local variant=type(mapping) if variant=="table" then action=action or mapping if option=="dynamic" then local pattern=false table.setmetatablenewindex(mapping,function(t,k,v) rawset(t,k,v) pattern=false end) return function(str) if not str or str=="" then return "" else if not pattern then pattern=Cs((tabletopattern(mapping)/action+p_utf8character)^0) end return lpegmatch(pattern,str) end end elseif option=="pattern" then return Cs((tabletopattern(mapping)/action+p_utf8character)^0) else local pattern=Cs((tabletopattern(mapping)/action+p_utf8character)^0) return function(str) if not str or str=="" then return "" else return lpegmatch(pattern,str) end end,pattern end elseif variant=="function" then if option=="pattern" then return Cs((p_utf8character/mapping+p_utf8character)^0) else local pattern=Cs((p_utf8character/mapping+p_utf8character)^0) return function(str) if not str or str=="" then return "" else return lpegmatch(pattern,str) end end,pattern end else return function(str) return str or "" end end end function utf.replacer(t) local r=replacer(t,false,false,true) return function(str) return lpegmatch(r,str) end end function utf.subtituter(t) local f=finder (t) local r=replacer(t,false,false,true) return function(str) local i=lpegmatch(f,str) if not i then return str elseif i>#str then return str else return lpegmatch(r,str) end end end local utflinesplitter=p_utfbom^-1*lpeg.tsplitat(p_newline) local utfcharsplitter_ows=p_utfbom^-1*Ct(C(p_utf8character)^0) local utfcharsplitter_iws=p_utfbom^-1*Ct((p_whitespace^1+C(p_utf8character))^0) local utfcharsplitter_raw=Ct(C(p_utf8character)^0) patterns.utflinesplitter=utflinesplitter function utf.splitlines(str) return lpegmatch(utflinesplitter,str or "") end function utf.split(str,ignorewhitespace) if ignorewhitespace then return lpegmatch(utfcharsplitter_iws,str or "") else return lpegmatch(utfcharsplitter_ows,str or "") end end function utf.totable(str) return lpegmatch(utfcharsplitter_raw,str) end function utf.magic(f) local str=f:read(4) or "" local off=lpegmatch(p_utfoffset,str) if off<4 then f:seek('set',off) end return lpegmatch(p_utftype,str) end local utf16_to_utf8_be,utf16_to_utf8_le local utf32_to_utf8_be,utf32_to_utf8_le local utf_16_be_getbom=patterns.utfbom_16_be^-1 local utf_16_le_getbom=patterns.utfbom_16_le^-1 local utf_32_be_getbom=patterns.utfbom_32_be^-1 local utf_32_le_getbom=patterns.utfbom_32_le^-1 local utf_16_be_linesplitter=utf_16_be_getbom*lpeg.tsplitat(patterns.utf_16_be_nl) local utf_16_le_linesplitter=utf_16_le_getbom*lpeg.tsplitat(patterns.utf_16_le_nl) local utf_32_be_linesplitter=utf_32_be_getbom*lpeg.tsplitat(patterns.utf_32_be_nl) local utf_32_le_linesplitter=utf_32_le_getbom*lpeg.tsplitat(patterns.utf_32_le_nl) local more=0 local p_utf16_to_utf8_be=C(1)*C(1)/function(left,right) local now=256*byte(left)+byte(right) if more>0 then now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 more=0 return utfchar(now) elseif now>=0xD800 and now<=0xDBFF then more=now return "" else return utfchar(now) end end local p_utf16_to_utf8_le=C(1)*C(1)/function(right,left) local now=256*byte(left)+byte(right) if more>0 then now=(more-0xD800)*0x400+(now-0xDC00)+0x10000 more=0 return utfchar(now) elseif now>=0xD800 and now<=0xDBFF then more=now return "" else return utfchar(now) end end local p_utf32_to_utf8_be=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) return utfchar(256*256*256*byte(a)+256*256*byte(b)+256*byte(c)+byte(d)) end local p_utf32_to_utf8_le=C(1)*C(1)*C(1)*C(1)/function(a,b,c,d) return utfchar(256*256*256*byte(d)+256*256*byte(c)+256*byte(b)+byte(a)) end p_utf16_to_utf8_be=P(true)/function() more=0 end*utf_16_be_getbom*Cs(p_utf16_to_utf8_be^0) p_utf16_to_utf8_le=P(true)/function() more=0 end*utf_16_le_getbom*Cs(p_utf16_to_utf8_le^0) p_utf32_to_utf8_be=P(true)/function() more=0 end*utf_32_be_getbom*Cs(p_utf32_to_utf8_be^0) p_utf32_to_utf8_le=P(true)/function() more=0 end*utf_32_le_getbom*Cs(p_utf32_to_utf8_le^0) patterns.utf16_to_utf8_be=p_utf16_to_utf8_be patterns.utf16_to_utf8_le=p_utf16_to_utf8_le patterns.utf32_to_utf8_be=p_utf32_to_utf8_be patterns.utf32_to_utf8_le=p_utf32_to_utf8_le utf16_to_utf8_be=function(s) if s and s~="" then return lpegmatch(p_utf16_to_utf8_be,s) else return s end end local utf16_to_utf8_be_t=function(t) if not t then return nil elseif type(t)=="string" then t=lpegmatch(utf_16_be_linesplitter,t) end for i=1,#t do local s=t[i] if s~="" then t[i]=lpegmatch(p_utf16_to_utf8_be,s) end end return t end utf16_to_utf8_le=function(s) if s and s~="" then return lpegmatch(p_utf16_to_utf8_le,s) else return s end end local utf16_to_utf8_le_t=function(t) if not t then return nil elseif type(t)=="string" then t=lpegmatch(utf_16_le_linesplitter,t) end for i=1,#t do local s=t[i] if s~="" then t[i]=lpegmatch(p_utf16_to_utf8_le,s) end end return t end utf32_to_utf8_be=function(s) if s and s~="" then return lpegmatch(p_utf32_to_utf8_be,s) else return s end end local utf32_to_utf8_be_t=function(t) if not t then return nil elseif type(t)=="string" then t=lpegmatch(utf_32_be_linesplitter,t) end for i=1,#t do local s=t[i] if s~="" then t[i]=lpegmatch(p_utf32_to_utf8_be,s) end end return t end utf32_to_utf8_le=function(s) if s and s~="" then return lpegmatch(p_utf32_to_utf8_le,s) else return s end end local utf32_to_utf8_le_t=function(t) if not t then return nil elseif type(t)=="string" then t=lpegmatch(utf_32_le_linesplitter,t) end for i=1,#t do local s=t[i] if s~="" then t[i]=lpegmatch(p_utf32_to_utf8_le,s) end end return t end utf.utf16_to_utf8_le_t=utf16_to_utf8_le_t utf.utf16_to_utf8_be_t=utf16_to_utf8_be_t utf.utf32_to_utf8_le_t=utf32_to_utf8_le_t utf.utf32_to_utf8_be_t=utf32_to_utf8_be_t utf.utf16_to_utf8_le=utf16_to_utf8_le utf.utf16_to_utf8_be=utf16_to_utf8_be utf.utf32_to_utf8_le=utf32_to_utf8_le utf.utf32_to_utf8_be=utf32_to_utf8_be function utf.utf8_to_utf8_t(t) return type(t)=="string" and lpegmatch(utflinesplitter,t) or t end function utf.utf16_to_utf8_t(t,endian) return endian and utf16_to_utf8_be_t(t) or utf16_to_utf8_le_t(t) or t end function utf.utf32_to_utf8_t(t,endian) return endian and utf32_to_utf8_be_t(t) or utf32_to_utf8_le_t(t) or t end local function little(b) if b<0x10000 then return char(b%256,rshift(b,8)) else b=b-0x10000 local b1=rshift(b,10)+0xD800 local b2=b%1024+0xDC00 return char(b1%256,rshift(b1,8),b2%256,rshift(b2,8)) end end local function big(b) if b<0x10000 then return char(rshift(b,8),b%256) else b=b-0x10000 local b1=rshift(b,10)+0xD800 local b2=b%1024+0xDC00 return char(rshift(b1,8),b1%256,rshift(b2,8),b2%256) end end local l_remap=Cs((p_utf8byte/little+P(1)/"")^0) local b_remap=Cs((p_utf8byte/big+P(1)/"")^0) local function utf8_to_utf16_be(str,nobom) if nobom then return lpegmatch(b_remap,str) else return char(254,255)..lpegmatch(b_remap,str) end end local function utf8_to_utf16_le(str,nobom) if nobom then return lpegmatch(l_remap,str) else return char(255,254)..lpegmatch(l_remap,str) end end utf.utf8_to_utf16_be=utf8_to_utf16_be utf.utf8_to_utf16_le=utf8_to_utf16_le function utf.utf8_to_utf16(str,littleendian,nobom) if littleendian then return utf8_to_utf16_le(str,nobom) else return utf8_to_utf16_be(str,nobom) end end local pattern=Cs ( (p_utf8byte/function(unicode ) return format("0x%04X",unicode) end)*(p_utf8byte*Carg(1)/function(unicode,separator) return format("%s0x%04X",separator,unicode) end)^0 ) function utf.tocodes(str,separator) return lpegmatch(pattern,str,1,separator or " ") end function utf.ustring(s) return format("U+%05X",type(s)=="number" and s or utfbyte(s)) end function utf.xstring(s) return format("0x%05X",type(s)=="number" and s or utfbyte(s)) end function utf.toeight(str) if not str or str=="" then return nil end local utftype=lpegmatch(p_utfstricttype,str) if utftype=="utf-8" then return sub(str,4) elseif utftype=="utf-16-be" then return utf16_to_utf8_be(str) elseif utftype=="utf-16-le" then return utf16_to_utf8_le(str) else return str end end do local p_nany=p_utf8character/"" local cache={} function utf.count(str,what) if type(what)=="string" then local p=cache[what] if not p then p=Cs((P(what)/" "+p_nany)^0) cache[p]=p end return #lpegmatch(p,str) else return #lpegmatch(Cs((P(what)/" "+p_nany)^0),str) end end end if not string.utfvalues then local find=string.find local dummy=function() end function string.utfvalues(str) local n=#str if n==0 then return dummy elseif n==1 then return function() return utfbyte(str) end else local p=1 return function() local b,e=find(str,".[\128-\191]*",p) if b then p=e+1 return utfbyte(sub(str,b,e)) end end end end end utf.values=string.utfvalues function utf.chrlen(u) return (u<0x80 and 1) or (u<0xE0 and 2) or (u<0xF0 and 3) or (u<0xF8 and 4) or (u<0xFC and 5) or (u<0xFE and 6) or 0 end if bit32 then local extract=bit32.extract local char=string.char function utf.toutf32string(n) if n<=0xFF then return char(n).."\000\000\000" elseif n<=0xFFFF then return char(extract(n,0,8))..char(extract(n,8,8)).."\000\000" elseif n<=0xFFFFFF then return char(extract(n,0,8))..char(extract(n,8,8))..char(extract(n,16,8)).."\000" else return char(extract(n,0,8))..char(extract(n,8,8))..char(extract(n,16,8))..char(extract(n,24,8)) end end end local len=utf.len local rep=rep function string.utfpadd(s,n) if n and n~=0 then local l=len(s) if n>0 then local d=n-l if d>0 then return rep(c or " ",d)..s end else local d=- n-l if d>0 then return s..rep(c or " ",d) end end end return s end do local utfcharacters=utf.characters or string.utfcharacters local utfchar=utf.char or string.utfcharacter lpeg.UP=P if utfcharacters then function lpeg.US(str) local p=P(false) for uc in utfcharacters(str) do p=p+P(uc) end return p end else function lpeg.US(str) local p=P(false) local f=function(uc) p=p+P(uc) end lpegmatch((p_utf8char/f)^0,str) return p end end local range=p_utf8byte*p_utf8byte+Cc(false) function lpeg.UR(str,more) local first,last if type(str)=="number" then first=str last=more or first else first,last=lpegmatch(range,str) if not last then return P(str) end end if first==last then return P(str) end if not utfchar then utfchar=utf.char end if utfchar and (last-first<8) then local p=P(false) for i=first,last do p=p+P(utfchar(i)) end return p else local f=function(b) return b>=first and b<=last end return p_utf8byte/f end end end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-url']={ 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 char,format,byte=string.char,string.format,string.byte local concat=table.concat local tonumber,type,next=tonumber,type,next local P,C,R,S,Cs,Cc,Ct,Cf,Cg,V=lpeg.P,lpeg.C,lpeg.R,lpeg.S,lpeg.Cs,lpeg.Cc,lpeg.Ct,lpeg.Cf,lpeg.Cg,lpeg.V local lpegmatch,lpegpatterns,replacer=lpeg.match,lpeg.patterns,lpeg.replacer local sortedhash=table.sortedhash url=url or {} local url=url local unescapes={} local escapes={} setmetatable(unescapes,{ __index=function(t,k) local v=char(tonumber(k,16)) t[k]=v return v end }) setmetatable(escapes,{ __index=function(t,k) local v=format("%%%02X",byte(k)) t[k]=v return v end }) local colon=P(":") local qmark=P("?") local hash=P("#") local slash=P("/") local atsign=P("@") local percent=P("%") local endofstring=P(-1) local hexdigit=R("09","AF","af") local plus=P("+") local nothing=Cc("") local okay=R("09","AZ","az")+S("-_.,:=+*~!'()@&$") local escapedchar=(percent*C(hexdigit*hexdigit))/unescapes local unescapedchar=P(1)/escapes local escaped=(plus/" ")+escapedchar local noslash=P("/")/"" local plustospace=P("+")/" " local decoder=Cs(( plustospace+escapedchar+P("\r\n")/"\n"+P(1) )^0 ) local encoder=Cs(( R("09","AZ","az")^1+S("-./_")^1+P(" ")/"+"+P("\n")/"\r\n"+unescapedchar )^0 ) lpegpatterns.urldecoder=decoder lpegpatterns.urlencoder=encoder function url.decode (str) return str and lpegmatch(decoder,str) or str end function url.encode (str) return str and lpegmatch(encoder,str) or str end function url.unescape(str) return str and lpegmatch(unescaper,str) or str end local schemestr=Cs((escaped+(1-colon-slash-qmark-hash))^2) local authoritystr=Cs((escaped+(1- slash-qmark-hash))^0) local pathstr=Cs((escaped+(1- qmark-hash))^0) local querystr=Cs(((1- hash))^0) local fragmentstr=Cs((escaped+(1- endofstring))^0) local scheme=schemestr*colon+nothing local authority=slash*slash*authoritystr+nothing local path=slash*pathstr+nothing local query=qmark*querystr+nothing local fragment=hash*fragmentstr+nothing local validurl=scheme*authority*path*query*fragment local parser=Ct(validurl) lpegpatterns.url=validurl lpegpatterns.urlsplitter=parser local escaper=Cs((R("09","AZ","az")^1+P(" ")/"%%20"+S("-./_:")^1+P(1)/escapes)^0) local unescaper=Cs((escapedchar+1)^0) local getcleaner=Cs((P("+++")/"%%2B"+P("+")/"%%20"+P(1))^1) lpegpatterns.urlunescaped=escapedchar lpegpatterns.urlescaper=escaper lpegpatterns.urlunescaper=unescaper lpegpatterns.urlgetcleaner=getcleaner function url.unescapeget(str) return lpegmatch(getcleaner,str) end local function split(str) return (type(str)=="string" and lpegmatch(parser,str)) or str end local isscheme=schemestr*colon*slash*slash local function hasscheme(str) if str then local scheme=lpegmatch(isscheme,str) return scheme~="" and scheme or false else return false end end local rootletter=R("az","AZ")+S("_-+") local separator=P("://") local qualified=P(".")^0*P("/")+rootletter*P(":")+rootletter^1*separator+rootletter^1*P("/") local rootbased=P("/")+rootletter*P(":") local barswapper=replacer("|",":") local backslashswapper=replacer("\\","/") local equal=P("=") local amp=P("&") local key=Cs(((plustospace+escapedchar+1)-equal )^0) local value=Cs(((plustospace+escapedchar+1)-amp-endofstring)^0) local splitquery=Cf (Ct("")*P { "sequence", sequence=V("pair")*(amp*V("pair"))^0, pair=Cg(key*equal*value), },rawset) local userpart=(1-atsign-colon)^1 local serverpart=(1-colon)^1 local splitauthority=((Cs(userpart)*colon*Cs(userpart)+Cs(userpart)*Cc(nil))*atsign+Cc(nil)*Cc(nil))*Cs(serverpart)*(colon*(serverpart/tonumber)+Cc(nil)) local function hashed(str) if not str or str=="" then return { scheme="invalid", original=str, } end local detailed=split(str) local rawscheme="" local rawquery="" local somescheme=false local somequery=false if detailed then rawscheme=detailed[1] rawquery=detailed[4] somescheme=rawscheme~="" somequery=rawquery~="" end if not somescheme and not somequery then return { scheme="file", authority="", path=str, query="", fragment="", original=str, noscheme=true, filename=str, } end local authority=detailed[2] local path=detailed[3] local filename local username local password local host local port if authority~="" then username,password,host,port=lpegmatch(splitauthority,authority) end if authority=="" then filename=path elseif path=="" then filename="" else filename=authority.."/"..path end return { scheme=rawscheme, authority=authority, path=path, query=lpegmatch(unescaper,rawquery), queries=lpegmatch(splitquery,rawquery), fragment=detailed[5], original=str, noscheme=false, filename=filename, host=host, port=port, } end url.split=split url.hasscheme=hasscheme url.hashed=hashed function url.addscheme(str,scheme) if hasscheme(str) then return str elseif not scheme then return "file:///"..str else return scheme..":///"..str end end function url.construct(hash) local result,r={},0 local scheme=hash.scheme local authority=hash.authority local path=hash.path local queries=hash.queries local fragment=hash.fragment if scheme and scheme~="" then r=r+1;result[r]=lpegmatch(escaper,scheme) r=r+1;result[r]="://" end if authority and authority~="" then r=r+1;result[r]=lpegmatch(escaper,authority) end if path and path~="" then r=r+1;result[r]="/" r=r+1;result[r]=lpegmatch(escaper,path) end if queries then local done=false for k,v in sortedhash(queries) do r=r+1;result[r]=done and "&" or "?" r=r+1;result[r]=lpegmatch(escaper,k) r=r+1;result[r]="=" r=r+1;result[r]=lpegmatch(escaper,v) done=true end end if fragment and fragment~="" then r=r+1;result[r]="#" r=r+1;result[r]=lpegmatch(escaper,fragment) end return concat(result) end local pattern=Cs(slash^-1/""*R("az","AZ")*((S(":|")/":")+P(":"))*slash*P(1)^0) function url.filename(filename) local spec=hashed(filename) local path=spec.path return (spec.scheme=="file" and path and lpegmatch(pattern,path)) or filename end local function escapestring(str) return lpegmatch(escaper,str) end url.escape=escapestring function url.query(str) if type(str)=="string" then return lpegmatch(splitquery,str) or "" else return str end end function url.toquery(data) local td=type(data) if td=="string" then return #str and escape(data) or nil elseif td=="table" then if next(data) then local t={} for k,v in next,data do t[#t+1]=format("%s=%s",k,escapestring(v)) end return concat(t,"&") end else end end local pattern=Cs(noslash^0*(1-noslash*P(-1))^0) function url.barepath(path) if not path or path=="" then return "" else return lpegmatch(pattern,path) end end end -- closure do -- begin closure to overcome local limits and interference if not modules then modules={} end modules ['l-set']={ 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" } set=set or {} local nums={} local tabs={} local concat=table.concat local next,type=next,type set.create=table.tohash function set.tonumber(t) if next(t) then local s="" for k,v in next,t do if v then s=s.." "..k end end local n=nums[s] if not n then n=#tabs+1 tabs[n]=t nums[s]=n end return n else return 0 end end function set.totable(n) if n==0 then return {} else return tabs[n] or {} end end function set.tolist(n) if n==0 or not tabs[n] then return "" else local t,n={},0 for k,v in next,tabs[n] do if v then n=n+1 t[n]=k end end return concat(t," ") end end function set.contains(n,s) if type(n)=="table" then return n[s] elseif n==0 then return false else local t=tabs[n] return t and t[s] end end end -- closure