Refactor ICTA to use functions instead of loadstring

This also fixes some small bugs.
This commit is contained in:
Thomas--S 2020-07-23 13:43:10 +02:00
parent 848237ea86
commit 912c8b5db0
6 changed files with 210 additions and 206 deletions

View File

@ -99,9 +99,10 @@ techage.icta_register_action("print", {
return 'print("'..data.text:sub(1,12)..'")' return 'print("'..data.text:sub(1,12)..'")'
end, end,
code = function(data, environ) code = function(data, environ)
local s1 = 'local text = string.gsub("'..(techage.icta_escape(data.text))..'", "*", env.result[#])' return function(env, output, idx)
local s2 = 'output(env.pos, text)' local text = string.gsub(data.text, "*", tostring(env.result[idx]))
return s1.."\n\t"..s2 output(env.pos, text)
end
end, end,
}) })

View File

@ -17,31 +17,18 @@ local M = minetest.get_meta
local S = techage.S local S = techage.S
local logic = techage.logic local logic = techage.logic
local function send_single_string(environ, number, topic, payload) function techage.compare(op1, op2, method)
payload = payload or "nil" if method == "is" then
local s = 'techage.send_single("%s", "%s", "%s", %s)' return op1 == op2
return string.format(s, environ.number, number, topic, payload) elseif method == "is not" then
end return op1 ~= op2
elseif method == "greater" then
local function send_multi_string(environ, numbers, topic, payload) return op1 > op2
payload = payload or "nil" elseif method == "less" then
local s = 'techage.send_multi("%s", "%s", "%s", %s)' return op1 < op2
return string.format(s, environ.number, numbers, topic, payload)
end
function techage.operand(s)
if s == "is" then
return "== "
elseif s == "is not" then
return "~= "
elseif s == "greater" then
return "> "
elseif s == "less" then
return "< "
end end
end end
function techage.fmt_number(num) function techage.fmt_number(num)
local mtch = num:match('^(%d+).*') local mtch = num:match('^(%d+).*')
if mtch and num ~= mtch then if mtch and num ~= mtch then
@ -50,13 +37,6 @@ function techage.fmt_number(num)
return num return num
end end
-- '#' is used as placeholder for rule numbers and has to be escaped
function techage.icta_escape(s)
s = tostring(s)
s = s:gsub('"', '\\"') -- to prevent code injection!!!
return s:gsub("#", '"..string.char(35).."')
end
techage.icta_register_condition("initial", { techage.icta_register_condition("initial", {
title = "initial", title = "initial",
@ -70,7 +50,13 @@ techage.icta_register_condition("initial", {
-- Return two chunks of executable Lua code for the controller, according: -- Return two chunks of executable Lua code for the controller, according:
-- return <read condition>, <expected result> -- return <read condition>, <expected result>
code = function(data, environ) code = function(data, environ)
return 'env.ticks', '== 1' local condition = function(env, idx)
return env.ticks
end
local result = function(val)
return val == 1
end
return condition, result
end, end,
button = function(data, environ) return "Initial after start" end, button = function(data, environ) return "Initial after start" end,
}) })
@ -85,7 +71,13 @@ techage.icta_register_condition("true", {
}, },
}, },
code = function(data, environ) code = function(data, environ)
return '"true"', '== "true"' local condition = function(env, idx)
return true
end
local result = function(val)
return val == true
end
return condition, result
end, end,
button = function(data, environ) return "true" end, button = function(data, environ) return "true" end,
}) })
@ -114,12 +106,14 @@ techage.icta_register_condition("condition", {
}, },
}, },
code = function(data, environ) code = function(data, environ)
local idx = data.condition:byte(-1) - 0x30 local condition = function(env, idx)
local expected_result = "== false" local index = data.condition:byte(-1) - 0x30
if data.operand == "was true" then return env.condition[index]
expected_result = "== true"
end end
return "env.condition["..idx.."]", expected_result local result = function(val)
return val == (data.operand == "was true")
end
return condition, result
end, end,
button = function(data, environ) return "cond("..data.condition:sub(-1,-1)..","..data.operand..")" end, button = function(data, environ) return "cond("..data.condition:sub(-1,-1)..","..data.operand..")" end,
}) })
@ -155,8 +149,13 @@ techage.icta_register_condition("input", {
return 'inp('..techage.fmt_number(data.number)..','..data.operand.." "..data.value..')' return 'inp('..techage.fmt_number(data.number)..','..data.operand.." "..data.value..')'
end, end,
code = function(data, environ) code = function(data, environ)
return 'env.input["'..data.number..'"]', local condition = function(env, idx)
techage.operand(data.operand)..'"'..data.value..'"' return env.input[data.number]
end
local result = function(val)
return techage.compare(val, data.value, data.operand)
end
return condition, result
end, end,
}) })
@ -193,8 +192,13 @@ techage.icta_register_condition("state", {
return 'sts('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' return 'sts('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end, end,
code = function(data, environ) code = function(data, environ)
return send_single_string(environ, data.number, "state"), local condition = function(env, idx)
techage.operand(data.operand)..'"'..data.value..'"' return techage.send_single(environ.number, data.number, "state")
end
local result = function(val)
return techage.compare(val, data.value, data.operand)
end
return condition, result
end, end,
}) })
@ -230,8 +234,13 @@ techage.icta_register_condition("fuel", {
return 'fuel('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' return 'fuel('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end, end,
code = function(data, environ) code = function(data, environ)
return send_single_string(environ, data.number, "fuel"), local condition = function(env, idx)
techage.operand(data.operand)..tonumber(data.value) return techage.send_single(environ.number, data.number, "fuel")
end
local result = function(val)
return techage.compare(val, tonumber(data.value), data.operand)
end
return condition, result
end, end,
}) })
@ -267,8 +276,13 @@ techage.icta_register_condition("load", {
return 'load('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' return 'load('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end, end,
code = function(data, environ) code = function(data, environ)
return send_single_string(environ, data.number, "load"), local condition = function(env, idx)
techage.operand(data.operand)..tonumber(data.value) return techage.send_single(environ.number, data.number, "load")
end
local result = function(val)
return techage.compare(val, tonumber(data.value), data.operand)
end
return condition, result
end, end,
}) })
@ -304,8 +318,13 @@ techage.icta_register_condition("depth", {
return 'depth('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' return 'depth('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end, end,
code = function(data, environ) code = function(data, environ)
return send_single_string(environ, data.number, "depth"), local condition = function(env, idx)
techage.operand(data.operand)..tonumber(data.value) return techage.send_single(environ.number, data.number, "depth")
end
local result = function(val)
return techage.compare(val, tonumber(data.value), data.operand)
end
return condition, result
end, end,
}) })
@ -341,8 +360,13 @@ techage.icta_register_condition("delivered", {
return 'deliv('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' return 'deliv('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end, end,
code = function(data, environ) code = function(data, environ)
return send_single_string(environ, data.number, "delivered"), local condition = function(env, idx)
techage.operand(data.operand)..tonumber(data.value) return techage.send_single(environ.number, data.number, "delivered")
end
local result = function(val)
return techage.compare(val, tonumber(data.value), data.operand)
end
return condition, result
end, end,
}) })
@ -380,8 +404,13 @@ techage.icta_register_condition("chest", {
return 'chest('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' return 'chest('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end, end,
code = function(data, environ) code = function(data, environ)
return send_single_string(environ, data.number, "state"), local condition = function(env, idx)
techage.operand(data.operand)..'"'..data.value..'"' return techage.send_single(environ.number, data.number, "state")
end
local result = function(val)
return techage.compare(val, data.value, data.operand)
end
return condition, result
end, end,
}) })
@ -416,8 +445,13 @@ techage.icta_register_condition("signaltower", {
return 'tower('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' return 'tower('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')'
end, end,
code = function(data, environ) code = function(data, environ)
return send_single_string(environ, data.number, "state"), local condition = function(env, idx)
techage.operand(data.operand)..'"'..data.value..'"' return techage.send_single(environ.number, data.number, "state")
end
local result = function(val)
return techage.compare(val, data.value, data.operand)
end
return condition, result
end, end,
}) })
@ -447,7 +481,9 @@ techage.icta_register_action("signaltower", {
return 'tower('..techage.fmt_number(data.number)..","..data.value..')' return 'tower('..techage.fmt_number(data.number)..","..data.value..')'
end, end,
code = function(data, environ) code = function(data, environ)
return send_multi_string(environ, data.number, data.value) return function(env, output, idx)
techage.send_multi(environ.number, data.number, data.value)
end
end, end,
}) })
@ -477,7 +513,9 @@ techage.icta_register_action("switch", {
return 'turn('..techage.fmt_number(data.number)..","..data.value..')' return 'turn('..techage.fmt_number(data.number)..","..data.value..')'
end, end,
code = function(data, environ) code = function(data, environ)
return send_multi_string(environ, data.number, data.value) return function(env, output, idx)
techage.send_multi(environ.number, data.number, data.value)
end
end, end,
}) })
@ -510,10 +548,13 @@ techage.icta_register_action("display", {
}, },
}, },
code = function(data, environ) code = function(data, environ)
local s1 = string.format('local text = string.gsub("%s", "*", tostring(env.result[#]))', techage.icta_escape(data.text)) return function(env, output, idx)
local s2 = string.format('local payload = {row = %s, str = text}', data.row) local text = string.gsub(data.text, "*", tostring(env.result[idx]))
local s3 = send_multi_string(environ, data.number, "set", "payload") local payload = safer_lua.Store()
return s1.."\n\t"..s2.."\n\t"..s3 payload.set("row", data.row)
payload.set("str", text)
techage.send_multi(environ.number, data.number, "set", payload)
end
end, end,
button = function(data, environ) button = function(data, environ)
return "lcd("..techage.fmt_number(data.number)..","..data.row..',"'..data.text..'")' return "lcd("..techage.fmt_number(data.number)..","..data.row..',"'..data.text..'")'
@ -531,7 +572,9 @@ techage.icta_register_action("cleardisplay", {
}, },
}, },
code = function(data, environ) code = function(data, environ)
return send_multi_string(environ, data.number, "clear") return function(env, output, idx)
techage.send_multi(environ.number, data.number, "clear")
end
end, end,
button = function(data, environ) button = function(data, environ)
return "clear lcd("..techage.fmt_number(data.number)..")" return "clear lcd("..techage.fmt_number(data.number)..")"
@ -554,7 +597,9 @@ techage.icta_register_action("chat", {
}, },
}, },
code = function(data, environ) code = function(data, environ)
return 'minetest.chat_send_player("'..environ.owner..'", "[TA4 ICTA Controller] '..techage.icta_escape(data.text)..' ")' return function(env, output, idx)
minetest.chat_send_player(environ.owner, "[TA4 ICTA Controller] "..data.text)
end
end, end,
button = function(data, environ) button = function(data, environ)
return 'chat("'..data.text:sub(1,12)..'")' return 'chat("'..data.text:sub(1,12)..'")'
@ -603,7 +648,9 @@ techage.icta_register_action("door", {
}, },
}, },
code = function(data, environ) code = function(data, environ)
return 'techage.icta_door_toggle("'..data.pos..'", "'..environ.owner..'", "'..data.door_state..'")' return function(env, output, idx)
techage.icta_door_toggle(data.pos, environ.owner, data.door_state)
end
end, end,
button = function(data, environ) button = function(data, environ)
return 'door("'..data.pos..'",'..data.door_state..")" return 'door("'..data.pos..'",'..data.door_state..")"
@ -645,7 +692,13 @@ techage.icta_register_condition("playerdetector", {
}, },
code = function(data, environ) code = function(data, environ)
return 'techage.icta_player_detect("'..environ.number..'", "'..data.number..'", "'..techage.icta_escape(data.name)..'")', "~= nil" local condition = function(env, idx)
return techage.icta_player_detect(environ.number, data.number, data.name)
end
local result = function(val)
return val ~= nil
end
return condition, result
end, end,
button = function(data, environ) button = function(data, environ)
return "detector("..techage.fmt_number(data.number)..","..data.name:sub(1,8)..")" return "detector("..techage.fmt_number(data.number)..","..data.name:sub(1,8)..")"
@ -685,7 +738,9 @@ techage.icta_register_action("set_filter", {
return 'turn('..techage.fmt_number(data.number)..","..data.color..","..data.value..')' return 'turn('..techage.fmt_number(data.number)..","..data.color..","..data.value..')'
end, end,
code = function(data, environ) code = function(data, environ)
local payload = '{slot = "'..data.color..'", val = "'..data.value..'"}' return function(env, output, idx)
return send_single_string(environ, data.number, "filter", payload) local payload = data.color.."="..data.value
techage.send_single(environ.number, data.number, "port", payload)
end
end, end,
}) })

View File

@ -71,7 +71,15 @@ end
techage.icta_register_condition("default", { techage.icta_register_condition("default", {
title = "", title = "",
formspec = {}, formspec = {},
code = function(data, environ) return false, false end, code = function(data, environ)
local condition = function(env, idx)
return false
end
local result = function(val)
return false
end
return condition, result
end,
button = function(data, environ) return "..." end, button = function(data, environ) return "..." end,
}) })

View File

@ -57,100 +57,51 @@ local function output(pos, text, flush_buffer)
meta:set_string("formspec", techage.formspecOutput(meta)) meta:set_string("formspec", techage.formspecOutput(meta))
end end
----------------- template -------------------------------
-- -- Rule 1
-- if env.blocked[1] == false and env.ticks % <cycle> == 0 then
-- env.result[1] = <check condition>
-- env.blocked[1] = env.result[1] <expected result>
-- if env.blocked[1] then
-- env.timer[1] = env.ticks + <after>
-- end
-- env.conditions[1] = env.blocked[1]
-- else
-- env.conditions[1] = false
-- end
-- if env.blocked[1] and env.timer[1] == env.ticks then
-- <action>
-- env.blocked[1] = false
-- end
-- -- Callback variant
-- if env.blocked[1] == false and env.ticks % <cycle> == 0 then
-- env.result[1], env.blocked[1] = <callback>
-- if env.blocked[1] then
-- env.timer[1] = env.ticks + <after>
-- end
-- env.conditions[1] = env.blocked[1]
-- else
-- env.conditions[1] = false
-- end
-- if env.blocked[1] and env.timer[1] == env.ticks then
-- <action>
-- env.blocked[1] = false
-- end
-- cyclic execution (cycle, cond, result, after, actn) -- cyclic execution (cycle, cond, result, after, actn)
local TemplCyc = [[ local function TemplCyc(cycle, cond, result, after, actn, idx)
-- Rule # return function(env, output)
if env.blocked[#] == false and env.ticks %% %s == 0 then if env.blocked[idx] == false and env.ticks % cycle == 0 then
env.result[#] = %s env.result[idx] = cond(env, idx)
env.blocked[#] = env.result[#] %s env.blocked[idx] = result(env.result[idx])
if env.blocked[#] then if env.blocked[idx] then
env.timer[#] = env.ticks + %s env.timer[idx] = env.ticks + after
end
env.condition[idx] = env.blocked[idx]
else
env.condition[idx] = false
end
if env.blocked[idx] and env.timer[idx] == env.ticks then
actn(env, output, idx)
env.blocked[idx] = false
end
end end
env.condition[#] = env.blocked[#]
else
env.condition[#] = false
end end
if env.blocked[#] and env.timer[#] == env.ticks then
%s
env.blocked[#] = false
end
]]
-- event based execution -- event based execution
local TemplEvt = [[ local function TemplEvt(cond, result, after, actn, idx)
-- Rule # return function(env, output)
if env.blocked[#] == false and env.event then if env.blocked[idx] == false and env.event then
env.result[#] = %s env.result[idx] = cond(env, idx)
env.blocked[#] = env.result[#] %s env.blocked[idx] = result(env.result[idx])
if env.blocked[#] then if env.blocked[idx] then
env.timer[#] = env.ticks + %s env.timer[idx] = env.ticks + after
end
env.condition[idx] = env.blocked[idx]
else
env.condition[idx] = false
end
if env.blocked[idx] and env.timer[idx] == env.ticks then
actn(env, output, idx)
env.blocked[idx] = false
end
end end
env.condition[#] = env.blocked[#]
else
env.condition[#] = false
end
if env.blocked[#] and env.timer[#] == env.ticks then
%s
env.blocked[#] = false
end
]]
-- event based execution of callback function
local TemplEvtClbk = [[
-- Rule #
if env.blocked[#] == false and env.event then
env.result[#], env.blocked[#] = %s(env, %s)
if env.blocked[#] then
env.timer[#] = env.ticks + %s
end
env.condition[#] = env.blocked[#]
else
env.condition[#] = false
end end
if env.blocked[#] and env.timer[#] == env.ticks then
%s
env.blocked[#] = false
end
]]
-- generate the Lua code from the NUM_RULES rules -- generate the Lua code from the NUM_RULES rules
local function generate(pos, meta, environ) local function generate(pos, meta, environ)
local fs_data = minetest.deserialize(meta:get_string("fs_data")) or FS_DATA local fs_data = minetest.deserialize(meta:get_string("fs_data")) or FS_DATA
-- chunks are compiled as vararg functions. Parameters are available via: local a, b, c = ... local tbl = {}
local tbl = {"local env, output = ...\n"}
for idx = 1,techage.NUM_RULES do for idx = 1,techage.NUM_RULES do
local cycle = integer(fs_data[idx].cycle, 0, 1000) local cycle = integer(fs_data[idx].cycle, 0, 1000)
local cond, result = techage.code_condition(fs_data[idx].cond, environ) local cond, result = techage.code_condition(fs_data[idx].cond, environ)
@ -159,26 +110,21 @@ local function generate(pos, meta, environ)
-- valid rule? -- valid rule?
if cycle and cond and after and actn then if cycle and cond and after and actn then
-- add rule number -- add rule number
local s local f
if cycle == 0 then -- event? if cycle == 0 then -- event
if result then f = TemplEvt(cond, result, after, actn, idx)
s = string.format(TemplEvt, cond, result, after, actn)
else -- callback function
local data = dump(fs_data[idx].cond)
s = string.format(TemplEvtClbk, cond, data, after, actn)
end
else -- cyclic else -- cyclic
s = string.format(TemplCyc, cycle, cond, result, after, actn) f = TemplCyc(cycle, cond, result, after, actn, idx)
end end
-- add to list of rules -- add to list of rules
tbl[#tbl+1] = s:gsub("#", idx) tbl[#tbl+1] = f
elseif cond ~= nil and actn == nil then elseif cond ~= nil and actn == nil then
output(pos, "Error in action in rule "..idx) output(pos, "Error in action in rule "..idx)
elseif cond == nil and actn ~= nil then elseif cond == nil and actn ~= nil then
output(pos, "Error in condition in rule "..idx) output(pos, "Error in condition in rule "..idx)
end end
end end
return table.concat(tbl) return tbl
end end
local function runtime_environ(pos) local function runtime_environ(pos)
@ -200,21 +146,12 @@ local function compile(pos, meta, number)
number = number, number = number,
owner = meta:get_string("owner"), owner = meta:get_string("owner"),
} }
local text = generate(pos, meta, gen_environ) local functions = generate(pos, meta, gen_environ)
if text then Cache[number] = {
local code, err = loadstring(text) code = functions,
if code then env = runtime_environ(pos),
Cache[number] = { }
code = code, return true
env = runtime_environ(pos),
}
return true
else
output(pos, err)
return false
end
end
return false
end end
local function execute(pos, number, event) local function execute(pos, number, event)
@ -226,10 +163,12 @@ local function execute(pos, number, event)
env.event = false env.event = false
env.ticks = env.ticks + 1 env.ticks = env.ticks + 1
end end
local res, err = pcall(code, env, output) for _,func in ipairs(code) do
if not res then local res, err = pcall(func, env, output)
output(pos, err) if not res then
return false output(pos, err)
return false
end
end end
return true return true
end end

View File

@ -211,8 +211,8 @@ local function write_row(pos, payload, cycle_time)
local mem = techage.get_mem(pos) local mem = techage.get_mem(pos)
nvm.text = nvm.text or {} nvm.text = nvm.text or {}
mem.ticks = mem.ticks or 0 mem.ticks = mem.ticks or 0
local str = tostring(payload.str) or "oops" local str = tostring(payload.get("str")) or "oops"
local row = tonumber(payload.row) or 1 local row = tonumber(payload.get("row")) or 1
if mem.ticks == 0 then if mem.ticks == 0 then
mem.ticks = cycle_time mem.ticks = cycle_time

View File

@ -39,16 +39,17 @@ techage.lua_ctlr.register_function("get_input", {
}) })
techage.lua_ctlr.register_function("read_data", { techage.lua_ctlr.register_function("read_data", {
cmnd = function(self, num, ident, add_data) cmnd = function(self, num, cmnd, data)
num = tostring(num or "") num = tostring(num or "")
return techage.send_single(self.meta.number, num, ident, add_data) cmnd = tostring(cmnd or "")
if not_protected(self.meta.owner, num) then
return techage.send_single(self.meta.number, num, cmnd, data)
end
end, end,
help = " $read_data(num, ident, add_data)\n".. help = " $read_data(num, cmnd, add_data)\n"..
" Read any kind of data from another block.\n".. " This function is deprecated.\n"..
' "num" is the block number\n'.. " It will be removed in future releases.\n"..
' "ident" specifies the data to be read\n'.. " Use $send_cmnd(num, cmnd, add_data) instead."
' "add_data" is additional data (optional)\n'..
' example: sts = $read_data("1234", "state")'
}) })
techage.lua_ctlr.register_function("time_as_str", { techage.lua_ctlr.register_function("time_as_str", {