Change implementation of require/reject API

Remove the dependency on load/loadstring for requireXXX/rejectXXX API functions
This commit is contained in:
Ian Sharkey 2023-03-10 21:24:23 -05:00
parent 821914795d
commit 32a2e99127
2 changed files with 114 additions and 37 deletions

View File

@ -6,3 +6,71 @@ local systemB = tiny.system()
world:addSystem(systemA)
world:addSystem(systemB)
world:setSystemIndex(systemA, 1)
--- test requireXXX/rejectXXX functions
local filt1 = tiny.requireAll("prop1", "prop2")
assert(filt1(nil, { }) == false)
assert(filt1(nil, { prop1 = 1 }) == false)
assert(filt1(nil, { prop1 = 1, prop3 = 2}) == false)
assert(filt1(nil, { prop1 = 1, prop2 = 2 }) == true)
assert(filt1(nil, { prop2 = 1 }) == false)
assert(filt1(nil, { prop2 = 1, prop1 = 1, prop3 = 2 }) == true)
local filt2 = tiny.requireAny("prop1", "prop2")
assert(filt2(nil, { }) == false)
assert(filt2(nil, { prop1 = 1 }) == true)
assert(filt2(nil, { prop1 = 1, prop3 = 2}) == true)
assert(filt2(nil, { prop1 = 1, prop2 = 2 }) == true)
assert(filt2(nil, { prop2 = 1 }) == true)
assert(filt2(nil, { prop2 = 1, prop1 = 1, prop3 = 2 }) == true)
assert(filt2(nil, { prop4 = 1, prop5 = 1, prop6 = 2 }) == false)
local filt3 = tiny.rejectAll("prop1", "prop2")
assert(filt3(nil, { }) == true)
assert(filt3(nil, { prop1 = 1 }) == true)
assert(filt3(nil, { prop1 = 1, prop3 = 2}) == true)
assert(filt3(nil, { prop1 = 1, prop2 = 2 }) == false)
assert(filt3(nil, { prop2 = 1 }) == true)
assert(filt3(nil, { prop2 = 1, prop1 = 1, prop3 = 2 }) == false)
local filt4 = tiny.rejectAny("prop1", "prop2")
assert(filt4(nil, { }) == true)
assert(filt4(nil, { prop1 = 1 }) == false)
assert(filt4(nil, { prop1 = 1, prop3 = 2}) == false)
assert(filt4(nil, { prop1 = 1, prop2 = 2 }) == false)
assert(filt4(nil, { prop2 = 1 }) == false)
assert(filt4(nil, { prop2 = 1, prop1 = 1, prop3 = 2 }) == false)
assert(filt4(nil, { prop4 = 1, prop5 = 1, prop6 = 2 }) == true)
local filt5 = tiny.requireAll("prop3", filt2)
assert(filt5(nil, {}) == false)
assert(filt5(nil, {prop1 = 1}) == false)
assert(filt5(nil, {prop1 = 1, prop2 = 1, prop3 = 1}) == true)
assert(filt5(nil, {prop1 = 1, prop2 = 1, prop3 = 1, prop4 = 1}) == true)
assert(filt5(nil, {prop1 = 1, prop2 = 1, prop4 = 1}) == false)
local filt6 = tiny.requireAny("prop3", filt2)
assert(filt6(nil, {}) == false)
assert(filt6(nil, {prop1 = 1}) == true)
assert(filt6(nil, {prop1 = 1, prop2 = 1, prop3 = 1}) == true)
assert(filt6(nil, {prop1 = 1, prop2 = 1, prop3 = 1, prop4 = 1}) == true)
assert(filt6(nil, {prop1 = 1, prop2 = 1, prop4 = 1}) == true)
assert(filt6(nil, {prop5 = 1, prop4 = 1}) == false)

View File

@ -91,40 +91,49 @@ local filterJoin
-- A helper function to filters from string
local filterBuildString
do
local loadstring = loadstring or load
local function getchr(c)
return "\\" .. c:byte()
end
local function make_safe(text)
return ("%q"):format(text):gsub('\n', 'n'):gsub("[\128-\255]", getchr)
end
local function filterJoinRaw(invert, joining_op, ...)
local _args = {...}
local function filterJoinRaw(prefix, seperator, ...)
local accum = {}
local build = {}
for i = 1, select('#', ...) do
local item = select(i, ...)
if type(item) == 'string' then
accum[#accum + 1] = ("(e[%s] ~= nil)"):format(make_safe(item))
elseif type(item) == 'function' then
build[#build + 1] = ('local subfilter_%d_ = select(%d, ...)')
:format(i, i)
accum[#accum + 1] = ('(subfilter_%d_(system, e))'):format(i)
return function(system, e)
local acc
local args = _args
if joining_op == 'or' then
acc = false
for i = 1, #args do
local v = args[i]
if type(v) == "string" then
acc = acc or (e[v] ~= nil)
elseif type(v) == "function" then
acc = acc or v(system, e)
else
error 'Filter token must be a string or a filter function.'
end
end
local source = ('%s\nreturn function(system, e) return %s(%s) end')
:format(
table.concat(build, '\n'),
prefix,
table.concat(accum, seperator))
local loader, err = loadstring(source)
if err then error(err) end
return loader(...)
else
acc = true
for i = 1, #args do
local v = args[i]
if type(v) == "string" then
acc = acc and (e[v] ~= nil)
elseif type(v) == "function" then
acc = acc and v(system, e)
else
error 'Filter token must be a string or a filter function.'
end
end
end
-- computes a simple xor
if invert then
return not acc
else
return acc
end
end
end
do
function filterJoin(...)
local state, value = pcall(filterJoinRaw, ...)
@ -169,25 +178,25 @@ end
--- Makes a Filter that selects Entities with all specified Components and
-- Filters.
function tiny.requireAll(...)
return filterJoin('', ' and ', ...)
return filterJoin(false, 'and', ...)
end
--- Makes a Filter that selects Entities with at least one of the specified
-- Components and Filters.
function tiny.requireAny(...)
return filterJoin('', ' or ', ...)
return filterJoin(false, 'or', ...)
end
--- Makes a Filter that rejects Entities with all specified Components and
-- Filters, and selects all other Entities.
function tiny.rejectAll(...)
return filterJoin('not', ' and ', ...)
return filterJoin(true, 'and', ...)
end
--- Makes a Filter that rejects Entities with at least one of the specified
-- Components and Filters, and selects all other Entities.
function tiny.rejectAny(...)
return filterJoin('not', ' or ', ...)
return filterJoin(true, 'or', ...)
end
--- Makes a Filter from a string. Syntax of `pattern` is as follows.