From 912c8b5db06d36b0a112c635f949c7fe8d12da28 Mon Sep 17 00:00:00 2001 From: Thomas--S Date: Thu, 23 Jul 2020 13:43:10 +0200 Subject: [PATCH] Refactor ICTA to use functions instead of loadstring This also fixes some small bugs. --- icta_controller/action.lua | 7 +- icta_controller/commands.lua | 215 +++++++++++++++++++++------------ icta_controller/condition.lua | 10 +- icta_controller/controller.lua | 163 ++++++++----------------- icta_controller/display.lua | 4 +- lua_controller/commands.lua | 17 +-- 6 files changed, 210 insertions(+), 206 deletions(-) diff --git a/icta_controller/action.lua b/icta_controller/action.lua index 2a44b17..e10e370 100644 --- a/icta_controller/action.lua +++ b/icta_controller/action.lua @@ -99,9 +99,10 @@ techage.icta_register_action("print", { return 'print("'..data.text:sub(1,12)..'")' end, code = function(data, environ) - local s1 = 'local text = string.gsub("'..(techage.icta_escape(data.text))..'", "*", env.result[#])' - local s2 = 'output(env.pos, text)' - return s1.."\n\t"..s2 + return function(env, output, idx) + local text = string.gsub(data.text, "*", tostring(env.result[idx])) + output(env.pos, text) + end end, }) diff --git a/icta_controller/commands.lua b/icta_controller/commands.lua index 64b1332..4aff35c 100644 --- a/icta_controller/commands.lua +++ b/icta_controller/commands.lua @@ -16,32 +16,19 @@ local M = minetest.get_meta local S = techage.S local logic = techage.logic - -local function send_single_string(environ, number, topic, payload) - payload = payload or "nil" - local s = 'techage.send_single("%s", "%s", "%s", %s)' - return string.format(s, environ.number, number, topic, payload) -end - -local function send_multi_string(environ, numbers, topic, payload) - payload = payload or "nil" - local s = 'techage.send_multi("%s", "%s", "%s", %s)' - 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 "< " +function techage.compare(op1, op2, method) + if method == "is" then + return op1 == op2 + elseif method == "is not" then + return op1 ~= op2 + elseif method == "greater" then + return op1 > op2 + elseif method == "less" then + return op1 < op2 end end - function techage.fmt_number(num) local mtch = num:match('^(%d+).*') if mtch and num ~= mtch then @@ -50,13 +37,6 @@ function techage.fmt_number(num) return num 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", { title = "initial", @@ -69,8 +49,14 @@ techage.icta_register_condition("initial", { }, -- Return two chunks of executable Lua code for the controller, according: -- return , - code = function(data, environ) - return 'env.ticks', '== 1' + code = function(data, environ) + local condition = function(env, idx) + return env.ticks + end + local result = function(val) + return val == 1 + end + return condition, result end, button = function(data, environ) return "Initial after start" end, }) @@ -84,8 +70,14 @@ techage.icta_register_condition("true", { label = "Condition is always true.", }, }, - code = function(data, environ) - return '"true"', '== "true"' + code = function(data, environ) + local condition = function(env, idx) + return true + end + local result = function(val) + return val == true + end + return condition, result end, button = function(data, environ) return "true" end, }) @@ -113,13 +105,15 @@ techage.icta_register_condition("condition", { label = "Used to execute two or more\nactions based on one condition.", }, }, - code = function(data, environ) - local idx = data.condition:byte(-1) - 0x30 - local expected_result = "== false" - if data.operand == "was true" then - expected_result = "== true" + code = function(data, environ) + local condition = function(env, idx) + local index = data.condition:byte(-1) - 0x30 + return env.condition[index] end - return "env.condition["..idx.."]", expected_result + local result = function(val) + return val == (data.operand == "was true") + end + return condition, result end, button = function(data, environ) return "cond("..data.condition:sub(-1,-1)..","..data.operand..")" end, }) @@ -154,9 +148,14 @@ techage.icta_register_condition("input", { button = function(data, environ) -- default button label return 'inp('..techage.fmt_number(data.number)..','..data.operand.." "..data.value..')' end, - code = function(data, environ) - return 'env.input["'..data.number..'"]', - techage.operand(data.operand)..'"'..data.value..'"' + code = function(data, environ) + local condition = function(env, idx) + return env.input[data.number] + end + local result = function(val) + return techage.compare(val, data.value, data.operand) + end + return condition, result end, }) @@ -192,9 +191,14 @@ techage.icta_register_condition("state", { button = function(data, environ) -- default button label return 'sts('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' end, - code = function(data, environ) - return send_single_string(environ, data.number, "state"), - techage.operand(data.operand)..'"'..data.value..'"' + code = function(data, environ) + local condition = function(env, idx) + 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, }) @@ -229,9 +233,14 @@ techage.icta_register_condition("fuel", { button = function(data, environ) return 'fuel('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' end, - code = function(data, environ) - return send_single_string(environ, data.number, "fuel"), - techage.operand(data.operand)..tonumber(data.value) + code = function(data, environ) + local condition = function(env, idx) + 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, }) @@ -266,9 +275,14 @@ techage.icta_register_condition("load", { button = function(data, environ) return 'load('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' end, - code = function(data, environ) - return send_single_string(environ, data.number, "load"), - techage.operand(data.operand)..tonumber(data.value) + code = function(data, environ) + local condition = function(env, idx) + 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, }) @@ -303,9 +317,14 @@ techage.icta_register_condition("depth", { button = function(data, environ) return 'depth('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' end, - code = function(data, environ) - return send_single_string(environ, data.number, "depth"), - techage.operand(data.operand)..tonumber(data.value) + code = function(data, environ) + local condition = function(env, idx) + 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, }) @@ -340,9 +359,14 @@ techage.icta_register_condition("delivered", { button = function(data, environ) return 'deliv('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' end, - code = function(data, environ) - return send_single_string(environ, data.number, "delivered"), - techage.operand(data.operand)..tonumber(data.value) + code = function(data, environ) + local condition = function(env, idx) + 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, }) @@ -379,9 +403,14 @@ techage.icta_register_condition("chest", { button = function(data, environ) -- default button label return 'chest('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' end, - code = function(data, environ) - return send_single_string(environ, data.number, "state"), - techage.operand(data.operand)..'"'..data.value..'"' + code = function(data, environ) + local condition = function(env, idx) + 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, }) @@ -415,9 +444,14 @@ techage.icta_register_condition("signaltower", { button = function(data, environ) -- default button label return 'tower('..techage.fmt_number(data.number)..","..data.operand..' '..data.value..')' end, - code = function(data, environ) - return send_single_string(environ, data.number, "state"), - techage.operand(data.operand)..'"'..data.value..'"' + code = function(data, environ) + local condition = function(env, idx) + 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, }) @@ -447,7 +481,9 @@ techage.icta_register_action("signaltower", { return 'tower('..techage.fmt_number(data.number)..","..data.value..')' end, 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, }) @@ -477,7 +513,9 @@ techage.icta_register_action("switch", { return 'turn('..techage.fmt_number(data.number)..","..data.value..')' end, 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, }) @@ -509,11 +547,14 @@ techage.icta_register_action("display", { label = "Use a '*' character as reference\nto any condition result", }, }, - code = function(data, environ) - local s1 = string.format('local text = string.gsub("%s", "*", tostring(env.result[#]))', techage.icta_escape(data.text)) - local s2 = string.format('local payload = {row = %s, str = text}', data.row) - local s3 = send_multi_string(environ, data.number, "set", "payload") - return s1.."\n\t"..s2.."\n\t"..s3 + code = function(data, environ) + return function(env, output, idx) + local text = string.gsub(data.text, "*", tostring(env.result[idx])) + local payload = safer_lua.Store() + payload.set("row", data.row) + payload.set("str", text) + techage.send_multi(environ.number, data.number, "set", payload) + end end, button = function(data, environ) return "lcd("..techage.fmt_number(data.number)..","..data.row..',"'..data.text..'")' @@ -530,8 +571,10 @@ techage.icta_register_action("cleardisplay", { default = "", }, }, - code = function(data, environ) - return send_multi_string(environ, data.number, "clear") + code = function(data, environ) + return function(env, output, idx) + techage.send_multi(environ.number, data.number, "clear") + end end, button = function(data, environ) return "clear lcd("..techage.fmt_number(data.number)..")" @@ -553,8 +596,10 @@ techage.icta_register_action("chat", { label = "The chat message is send to the\nController owner, only.", }, }, - code = function(data, environ) - return 'minetest.chat_send_player("'..environ.owner..'", "[TA4 ICTA Controller] '..techage.icta_escape(data.text)..' ")' + code = function(data, environ) + return function(env, output, idx) + minetest.chat_send_player(environ.owner, "[TA4 ICTA Controller] "..data.text) + end end, button = function(data, environ) return 'chat("'..data.text:sub(1,12)..'")' @@ -602,8 +647,10 @@ techage.icta_register_action("door", { "Use the Techage Info Tool to\neasily determine a door position.", }, }, - code = function(data, environ) - return 'techage.icta_door_toggle("'..data.pos..'", "'..environ.owner..'", "'..data.door_state..'")' + code = function(data, environ) + return function(env, output, idx) + techage.icta_door_toggle(data.pos, environ.owner, data.door_state) + end end, button = function(data, environ) return 'door("'..data.pos..'",'..data.door_state..")" @@ -644,8 +691,14 @@ techage.icta_register_condition("playerdetector", { }, }, - code = function(data, environ) - return 'techage.icta_player_detect("'..environ.number..'", "'..data.number..'", "'..techage.icta_escape(data.name)..'")', "~= nil" + code = function(data, environ) + 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, button = function(data, environ) 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..')' end, code = function(data, environ) - local payload = '{slot = "'..data.color..'", val = "'..data.value..'"}' - return send_single_string(environ, data.number, "filter", payload) + return function(env, output, idx) + local payload = data.color.."="..data.value + techage.send_single(environ.number, data.number, "port", payload) + end end, }) diff --git a/icta_controller/condition.lua b/icta_controller/condition.lua index 4e025d0..6d560ee 100644 --- a/icta_controller/condition.lua +++ b/icta_controller/condition.lua @@ -71,7 +71,15 @@ end techage.icta_register_condition("default", { title = "", 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, }) diff --git a/icta_controller/controller.lua b/icta_controller/controller.lua index 9b41145..0721e4a 100644 --- a/icta_controller/controller.lua +++ b/icta_controller/controller.lua @@ -57,100 +57,51 @@ local function output(pos, text, flush_buffer) meta:set_string("formspec", techage.formspecOutput(meta)) end ------------------ template ------------------------------- --- -- Rule 1 --- if env.blocked[1] == false and env.ticks % == 0 then --- env.result[1] = --- env.blocked[1] = env.result[1] --- if env.blocked[1] then --- env.timer[1] = env.ticks + --- end --- env.conditions[1] = env.blocked[1] --- else --- env.conditions[1] = false --- end --- if env.blocked[1] and env.timer[1] == env.ticks then --- --- env.blocked[1] = false --- end - --- -- Callback variant --- if env.blocked[1] == false and env.ticks % == 0 then --- env.result[1], env.blocked[1] = --- if env.blocked[1] then --- env.timer[1] = env.ticks + --- end --- env.conditions[1] = env.blocked[1] --- else --- env.conditions[1] = false --- end --- if env.blocked[1] and env.timer[1] == env.ticks then --- --- env.blocked[1] = false --- end - - -- cyclic execution (cycle, cond, result, after, actn) -local TemplCyc = [[ --- Rule # -if env.blocked[#] == false and env.ticks %% %s == 0 then - env.result[#] = %s - env.blocked[#] = env.result[#] %s - if env.blocked[#] then - env.timer[#] = env.ticks + %s +local function TemplCyc(cycle, cond, result, after, actn, idx) + return function(env, output) + if env.blocked[idx] == false and env.ticks % cycle == 0 then + env.result[idx] = cond(env, idx) + env.blocked[idx] = result(env.result[idx]) + if env.blocked[idx] then + 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 - 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 -local TemplEvt = [[ --- Rule # -if env.blocked[#] == false and env.event then - env.result[#] = %s - env.blocked[#] = env.result[#] %s - if env.blocked[#] then - env.timer[#] = env.ticks + %s +local function TemplEvt(cond, result, after, actn, idx) + return function(env, output) + if env.blocked[idx] == false and env.event then + env.result[idx] = cond(env, idx) + env.blocked[idx] = result(env.result[idx]) + if env.blocked[idx] then + 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 - 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 -if env.blocked[#] and env.timer[#] == env.ticks then - %s - env.blocked[#] = false -end -]] -- generate the Lua code from the NUM_RULES rules local function generate(pos, meta, environ) 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 env, output = ...\n"} + local tbl = {} for idx = 1,techage.NUM_RULES do local cycle = integer(fs_data[idx].cycle, 0, 1000) local cond, result = techage.code_condition(fs_data[idx].cond, environ) @@ -159,26 +110,21 @@ local function generate(pos, meta, environ) -- valid rule? if cycle and cond and after and actn then -- add rule number - local s - if cycle == 0 then -- event? - if result then - 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 + local f + if cycle == 0 then -- event + f = TemplEvt(cond, result, after, actn, idx) else -- cyclic - s = string.format(TemplCyc, cycle, cond, result, after, actn) + f = TemplCyc(cycle, cond, result, after, actn, idx) end -- add to list of rules - tbl[#tbl+1] = s:gsub("#", idx) + tbl[#tbl+1] = f elseif cond ~= nil and actn == nil then output(pos, "Error in action in rule "..idx) elseif cond == nil and actn ~= nil then output(pos, "Error in condition in rule "..idx) end end - return table.concat(tbl) + return tbl end local function runtime_environ(pos) @@ -200,21 +146,12 @@ local function compile(pos, meta, number) number = number, owner = meta:get_string("owner"), } - local text = generate(pos, meta, gen_environ) - if text then - local code, err = loadstring(text) - if code then - Cache[number] = { - code = code, - env = runtime_environ(pos), - } - return true - else - output(pos, err) - return false - end - end - return false + local functions = generate(pos, meta, gen_environ) + Cache[number] = { + code = functions, + env = runtime_environ(pos), + } + return true end local function execute(pos, number, event) @@ -226,10 +163,12 @@ local function execute(pos, number, event) env.event = false env.ticks = env.ticks + 1 end - local res, err = pcall(code, env, output) - if not res then - output(pos, err) - return false + for _,func in ipairs(code) do + local res, err = pcall(func, env, output) + if not res then + output(pos, err) + return false + end end return true end diff --git a/icta_controller/display.lua b/icta_controller/display.lua index 04640c5..8add063 100644 --- a/icta_controller/display.lua +++ b/icta_controller/display.lua @@ -211,8 +211,8 @@ local function write_row(pos, payload, cycle_time) local mem = techage.get_mem(pos) nvm.text = nvm.text or {} mem.ticks = mem.ticks or 0 - local str = tostring(payload.str) or "oops" - local row = tonumber(payload.row) or 1 + local str = tostring(payload.get("str")) or "oops" + local row = tonumber(payload.get("row")) or 1 if mem.ticks == 0 then mem.ticks = cycle_time diff --git a/lua_controller/commands.lua b/lua_controller/commands.lua index 47b36d3..03074b0 100644 --- a/lua_controller/commands.lua +++ b/lua_controller/commands.lua @@ -39,16 +39,17 @@ techage.lua_ctlr.register_function("get_input", { }) techage.lua_ctlr.register_function("read_data", { - cmnd = function(self, num, ident, add_data) + cmnd = function(self, num, cmnd, data) 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, - help = " $read_data(num, ident, add_data)\n".. - " Read any kind of data from another block.\n".. - ' "num" is the block number\n'.. - ' "ident" specifies the data to be read\n'.. - ' "add_data" is additional data (optional)\n'.. - ' example: sts = $read_data("1234", "state")' + help = " $read_data(num, cmnd, add_data)\n".. + " This function is deprecated.\n".. + " It will be removed in future releases.\n".. + " Use $send_cmnd(num, cmnd, add_data) instead." }) techage.lua_ctlr.register_function("time_as_str", {