func.lua
local type,select,setmetatable,getmetatable,rawset = type,select,setmetatable,getmetatable,rawset
local concat,append = table.concat,table.insert
local tostring = tostring
local utils = require 'pl.utils'
local pairs,ipairs,loadstring,rawget,unpack = pairs,ipairs,loadstring,rawget,utils.unpack
local tablex = require 'pl.tablex'
local map = tablex.map
local _DEBUG = rawget(_G,'_DEBUG')
local assert_arg = utils.assert_arg
local func = {}
local _PEMT = {}
local function P (t)
setmetatable(t,_PEMT)
return t
end
func.PE = P
local function isPE (obj)
return getmetatable(obj) == _PEMT
end
func.isPE = isPE
local function PH (idx)
return P {op='X',repr='_'..idx, index=idx}
end
local function CPH (idx)
return P {op='X',repr='_C'..idx, index=idx}
end
func._1,func._2,func._3,func._4,func._5 = PH(1),PH(2),PH(3),PH(4),PH(5)
func._0 = P{op='X',repr='...',index=0}
function func.Var (name)
local ls = utils.split(name,'[%s,]+')
local res = {}
for _,n in ipairs(ls) do
append(res,P{op='X',repr=n,index=0})
end
return unpack(res)
end
function func._ (value)
return P{op='X',repr=value,index='wrap'}
end
local repr
func.Nil = func.Var 'nil'
function _PEMT.__index(obj,key)
return P{op='[]',obj,key}
end
function _PEMT.__call(fun,...)
return P{op='()',fun,...}
end
function _PEMT.__tostring (e)
return repr(e)
end
function _PEMT.__unm(arg)
return P{op='-',arg}
end
function func.Not (arg)
return P{op='not',arg}
end
function func.Len (arg)
return P{op='#',arg}
end
local function binreg(context,t)
for name,op in pairs(t) do
rawset(context,name,function(x,y)
return P{op=op,x,y}
end)
end
end
local function import_name (name,fun,context)
rawset(context,name,function(...)
return P{op='()',fun,...}
end)
end
local imported_functions = {}
local function is_global_table (n)
return type(_G[n]) == 'table'
end
function func.import(tname,context)
assert_arg(1,tname,'string',is_global_table,'arg# 1: not a name of a global table')
local t = _G[tname]
context = context or _G
for name,fun in pairs(t) do
import_name(name,fun,context)
imported_functions[fun] = name
end
end
function func.register (fun,name)
assert_arg(1,fun,'function')
if name then
assert_arg(2,name,'string')
imported_functions[fun] = name
end
return function(...)
return P{op='()',fun,...}
end
end
function func.lookup_imported_name (fun)
return imported_functions[fun]
end
local function _arg(...) return ... end
function func.Args (...)
return P{op='()',_arg,...}
end
local operators = {
['or'] = 0,
['and'] = 1,
['=='] = 2, ['~='] = 2, ['<'] = 2, ['>'] = 2, ['<='] = 2, ['>='] = 2,
['..'] = 3,
['+'] = 4, ['-'] = 4,
['*'] = 5, ['/'] = 5, ['%'] = 5,
['not'] = 6, ['#'] = 6, ['-'] = 6,
['^'] = 7
}
binreg (func,{And='and',Or='or',Eq='==',Lt='<',Gt='>',Le='<=',Ge='>='})
binreg (_PEMT,{__add='+',__sub='-',__mul='*',__div='/',__mod='%',__pow='^',__concat='..'})
binreg (_PEMT,{__eq='=='})
function func.tail (ls)
assert_arg(1,ls,'table')
local res = {}
for i = 2,#ls do
append(res,ls[i])
end
return res
end
function repr (e,lastpred)
local tail = func.tail
if isPE(e) then
local pred = operators[e.op]
local ls = map(repr,e,pred)
if pred then if #ls ~= 1 then
local s = concat(ls,' '..e.op..' ')
if lastpred and lastpred > pred then
s = '('..s..')'
end
return s
else
return e.op..' '..ls[1]
end
else if e.op == '[]' then
return ls[1]..'['..ls[2]..']'
elseif e.op == '()' then
local fn
if ls[1] ~= nil then fn = ls[1]
else
fn = ''
end
return fn..'('..concat(tail(ls),',')..')'
else
return e.repr
end
end
elseif type(e) == 'string' then
return '"'..e..'"'
elseif type(e) == 'function' then
local name = func.lookup_imported_name(e)
if name then return name else return tostring(e) end
else
return tostring(e) end
end
func.repr = repr
local collect_values
function collect_values (e,vlist)
if isPE(e) then
if e.op ~= 'X' then
local m = 0
for i,subx in ipairs(e) do
local pe = isPE(subx)
if pe then
if subx.op == 'X' and subx.index == 'wrap' then
subx = subx.repr
pe = false
else
m = math.max(m,collect_values(subx,vlist))
end
end
if not pe then
append(vlist,subx)
e[i] = CPH(#vlist)
end
end
return m
else return e.index
end
else return 0
end
end
func.collect_values = collect_values
function func.instantiate (e)
local consts,values,parms = {},{},{}
local rep, err, fun
local n = func.collect_values(e,values)
for i = 1,#values do
append(consts,'_C'..i)
if _DEBUG then print(i,values[i]) end
end
for i =1,n do
append(parms,'_'..i)
end
consts = concat(consts,',')
parms = concat(parms,',')
rep = repr(e)
local fstr = ('return function(%s) return function(%s) return %s end end'):format(consts,parms,rep)
if _DEBUG then print(fstr) end
fun,err = utils.load(fstr,'fun')
if not fun then return nil,err end
fun = fun() fun = fun(unpack(values)) e.__PE_function = fun
return fun
end
function func.I(e)
if rawget(e,'__PE_function') then
return e.__PE_function
else return func.instantiate(e)
end
end
utils.add_function_factory(_PEMT,func.I)
func.bind1 = utils.bind1
func.curry = func.bind1
function func.compose (f,g)
return function(...) return f(g(...)) end
end
function func.bind(fn,...)
local args = table.pack(...)
local holders,parms,bvalues,values = {},{},{'fn'},{}
local nv,maxplace,varargs = 1,0,false
for i = 1,args.n do
local a = args[i]
if isPE(a) and a.op == 'X' then
append(holders,a.repr)
maxplace = math.max(maxplace,a.index)
if a.index == 0 then varargs = true end
else
local v = '_v'..nv
append(bvalues,v)
append(holders,v)
append(values,a)
nv = nv + 1
end
end
for np = 1,maxplace do
append(parms,'_'..np)
end
if varargs then append(parms,'...') end
bvalues = concat(bvalues,',')
parms = concat(parms,',')
holders = concat(holders,',')
local fstr = ([[
return function (%s)
return function(%s) return fn(%s) end
end
]]):format(bvalues,parms,holders)
if _DEBUG then print(fstr) end
local res,err = utils.load(fstr)
res = res()
return res(fn,unpack(values))
end
return func