Merge pull request #19 from Thomas--S/icta-func

Refactor ICTA to use functions instead of loadstring
This commit is contained in:
Joachim Stolberg 2020-07-24 18:17:56 +02:00 committed by GitHub
commit 2b6fcb5b9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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)..'")'
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,
})

View File

@ -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 <read condition>, <expected result>
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,
})

View File

@ -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,
})

View File

@ -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 % <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)
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

View File

@ -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

View File

@ -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", {