diff options
Diffstat (limited to 'macros/luatex/generic/penlight/pl/sip.lua')
-rw-r--r-- | macros/luatex/generic/penlight/pl/sip.lua | 337 |
1 files changed, 0 insertions, 337 deletions
diff --git a/macros/luatex/generic/penlight/pl/sip.lua b/macros/luatex/generic/penlight/pl/sip.lua deleted file mode 100644 index 5d1f2aa0cc..0000000000 --- a/macros/luatex/generic/penlight/pl/sip.lua +++ /dev/null @@ -1,337 +0,0 @@ ---- Simple Input Patterns (SIP). --- SIP patterns start with '$', then a --- one-letter type, and then an optional variable in curly braces. --- --- sip.match('$v=$q','name="dolly"',res) --- ==> res=={'name','dolly'} --- sip.match('($q{first},$q{second})','("john","smith")',res) --- ==> res=={second='smith',first='john'} --- --- Type names: --- --- v identifier --- i integer --- f floating-point --- q quoted string --- ([{< match up to closing bracket --- --- See @{08-additional.md.Simple_Input_Patterns|the Guide} --- --- @module pl.sip - -local loadstring = rawget(_G,'loadstring') or load -local unpack = rawget(_G,'unpack') or rawget(table,'unpack') - -local append,concat = table.insert,table.concat -local ipairs,type = ipairs,type -local io,_G = io,_G -local print,rawget = print,rawget - -local patterns = { - FLOAT = '[%+%-%d]%d*%.?%d*[eE]?[%+%-]?%d*', - INTEGER = '[+%-%d]%d*', - IDEN = '[%a_][%w_]*', - OPTION = '[%a_][%w_%-]*', -} - -local function assert_arg(idx,val,tp) - if type(val) ~= tp then - error("argument "..idx.." must be "..tp, 2) - end -end - -local sip = {} - -local brackets = {['<'] = '>', ['('] = ')', ['{'] = '}', ['['] = ']' } -local stdclasses = {a=1,c=0,d=1,l=1,p=0,u=1,w=1,x=1,s=0} - -local function group(s) - return '('..s..')' -end - --- escape all magic characters except $, which has special meaning --- Also, un-escape any characters after $, so $( and $[ passes through as is. -local function escape (spec) - return (spec:gsub('[%-%.%+%[%]%(%)%^%%%?%*]','%%%0'):gsub('%$%%(%S)','$%1')) -end - --- Most spaces within patterns can match zero or more spaces. --- Spaces between alphanumeric characters or underscores or between --- patterns that can match these characters, however, must match at least --- one space. Otherwise '$v $v' would match 'abcd' as {'abc', 'd'}. --- This function replaces continuous spaces within a pattern with either --- '%s*' or '%s+' according to this rule. The pattern has already --- been stripped of pattern names by now. -local function compress_spaces(patt) - return (patt:gsub("()%s+()", function(i1, i2) - local before = patt:sub(i1 - 2, i1 - 1) - if before:match('%$[vifadxlu]') or before:match('^[^%$]?[%w_]$') then - local after = patt:sub(i2, i2 + 1) - if after:match('%$[vifadxlu]') or after:match('^[%w_]') then - return '%s+' - end - end - return '%s*' - end)) -end - -local pattern_map = { - v = group(patterns.IDEN), - i = group(patterns.INTEGER), - f = group(patterns.FLOAT), - o = group(patterns.OPTION), - r = '(%S.*)', - p = '([%a]?[:]?[\\/%.%w_]+)' -} - -function sip.custom_pattern(flag,patt) - pattern_map[flag] = patt -end - ---- convert a SIP pattern into the equivalent Lua string pattern. --- @param spec a SIP pattern --- @param options a table; only the <code>at_start</code> field is --- currently meaningful and ensures that the pattern is anchored --- at the start of the string. --- @return a Lua string pattern. -function sip.create_pattern (spec,options) - assert_arg(1,spec,'string') - local fieldnames,fieldtypes = {},{} - - if type(spec) == 'string' then - spec = escape(spec) - else - local res = {} - for i,s in ipairs(spec) do - res[i] = escape(s) - end - spec = concat(res,'.-') - end - - local kount = 1 - - local function addfield (name,type) - name = name or kount - append(fieldnames,name) - fieldtypes[name] = type - kount = kount + 1 - end - - local named_vars = spec:find('{%a+}') - - if options and options.at_start then - spec = '^'..spec - end - if spec:sub(-1,-1) == '$' then - spec = spec:sub(1,-2)..'$r' - if named_vars then spec = spec..'{rest}' end - end - - local names - - if named_vars then - names = {} - spec = spec:gsub('{(%a+)}',function(name) - append(names,name) - return '' - end) - end - spec = compress_spaces(spec) - - local k = 1 - local err - local r = (spec:gsub('%$%S',function(s) - local type,name - type = s:sub(2,2) - if names then name = names[k]; k=k+1 end - -- this kludge is necessary because %q generates two matches, and - -- we want to ignore the first. Not a problem for named captures. - if not names and type == 'q' then - addfield(nil,'Q') - else - addfield(name,type) - end - local res - if pattern_map[type] then - res = pattern_map[type] - elseif type == 'q' then - -- some Lua pattern matching voodoo; we want to match '...' as - -- well as "...", and can use the fact that %n will match a - -- previous capture. Adding the extra field above comes from needing - -- to accommodate the extra spurious match (which is either ' or ") - addfield(name,type) - res = '(["\'])(.-)%'..(kount-2) - else - local endbracket = brackets[type] - if endbracket then - res = '(%b'..type..endbracket..')' - elseif stdclasses[type] or stdclasses[type:lower()] then - res = '(%'..type..'+)' - else - err = "unknown format type or character class" - end - end - return res - end)) - - if err then - return nil,err - else - return r,fieldnames,fieldtypes - end -end - - -local function tnumber (s) - return s == 'd' or s == 'i' or s == 'f' -end - -function sip.create_spec_fun(spec,options) - local fieldtypes,fieldnames - local ls = {} - spec,fieldnames,fieldtypes = sip.create_pattern(spec,options) - if not spec then return spec,fieldnames end - local named_vars = type(fieldnames[1]) == 'string' - for i = 1,#fieldnames do - append(ls,'mm'..i) - end - ls[1] = ls[1] or "mm1" -- behave correctly if there are no patterns - local fun = ('return (function(s,res)\n\tlocal %s = s:match(%q)\n'):format(concat(ls,','),spec) - fun = fun..'\tif not mm1 then return false end\n' - local k=1 - for i,f in ipairs(fieldnames) do - if f ~= '_' then - local var = 'mm'..i - if tnumber(fieldtypes[f]) then - var = 'tonumber('..var..')' - elseif brackets[fieldtypes[f]] then - var = var..':sub(2,-2)' - end - if named_vars then - fun = ('%s\tres.%s = %s\n'):format(fun,f,var) - else - if fieldtypes[f] ~= 'Q' then -- we skip the string-delim capture - fun = ('%s\tres[%d] = %s\n'):format(fun,k,var) - k = k + 1 - end - end - end - end - return fun..'\treturn true\nend)\n', named_vars -end - ---- convert a SIP pattern into a matching function. --- The returned function takes two arguments, the line and an empty table. --- If the line matched the pattern, then this function returns true --- and the table is filled with field-value pairs. --- @param spec a SIP pattern --- @param options optional table; {at_start=true} ensures that the pattern --- is anchored at the start of the string. --- @return a function if successful, or nil,error -function sip.compile(spec,options) - assert_arg(1,spec,'string') - local fun,names = sip.create_spec_fun(spec,options) - if not fun then return nil,names end - if rawget(_G,'_DEBUG') then print(fun) end - local chunk,err = loadstring(fun,'tmp') - if err then return nil,err end - return chunk(),names -end - -local cache = {} - ---- match a SIP pattern against a string. --- @param spec a SIP pattern --- @param line a string --- @param res a table to receive values --- @param options (optional) option table --- @return true or false -function sip.match (spec,line,res,options) - assert_arg(1,spec,'string') - assert_arg(2,line,'string') - assert_arg(3,res,'table') - if not cache[spec] then - cache[spec] = sip.compile(spec,options) - end - return cache[spec](line,res) -end - ---- match a SIP pattern against the start of a string. --- @param spec a SIP pattern --- @param line a string --- @param res a table to receive values --- @return true or false -function sip.match_at_start (spec,line,res) - return sip.match(spec,line,res,{at_start=true}) -end - ---- given a pattern and a file object, return an iterator over the results --- @param spec a SIP pattern --- @param f a file-like object. -function sip.fields (spec,f) - assert_arg(1,spec,'string') - if not f then return nil,"no file object" end - local fun,err = sip.compile(spec) - if not fun then return nil,err end - local res = {} - return function() - while true do - local line = f:read() - if not line then return end - if fun(line,res) then - local values = res - res = {} - return unpack(values) - end - end - end -end - -local read_patterns = {} - ---- register a match which will be used in the read function. --- @string spec a SIP pattern --- @func fun a function to be called with the results of the match --- @see read -function sip.pattern (spec,fun) - assert_arg(1,spec,'string') - local pat,named = sip.compile(spec) - append(read_patterns,{pat=pat,named=named,callback=fun}) -end - ---- enter a loop which applies all registered matches to the input file. --- @param f a file-like object --- @array matches optional list of `{spec,fun}` pairs, as for `pattern` above. -function sip.read (f,matches) - local owned,err - if not f then return nil,"no file object" end - if type(f) == 'string' then - f,err = io.open(f) - if not f then return nil,err end - owned = true - end - if matches then - for _,p in ipairs(matches) do - sip.pattern(p[1],p[2]) - end - end - local res = {} - for line in f:lines() do - for _,item in ipairs(read_patterns) do - if item.pat(line,res) then - if item.callback then - if item.named then - item.callback(res) - else - item.callback(unpack(res)) - end - end - res = {} - break - end - end - end - if owned then f:close() end -end - -return sip |