~gpcf/advtrains

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
5 2

[PATCH] JIT-compile ATC commands

Details
Message ID
<20210202161304.3649-1-yw05@forksworld.de>
DKIM signature
missing
Download raw message
Patch: +367 -183
---
 advtrains/aj_unittest.lua | 155 +++++++++++++++++++++++++++++
 advtrains/atc.lua         | 192 ++----------------------------------
 advtrains/atcjit.lua      | 201 ++++++++++++++++++++++++++++++++++++++
 advtrains/init.lua        |   2 +
 4 files changed, 367 insertions(+), 183 deletions(-)
 create mode 100644 advtrains/aj_unittest.lua
 create mode 100644 advtrains/atcjit.lua

diff --git a/advtrains/aj_unittest.lua b/advtrains/aj_unittest.lua
new file mode 100644
index 0000000..9dacbc3
--- /dev/null
+++ b/advtrains/aj_unittest.lua
@@ -0,0 +1,155 @@
advtrains = {}
_G.advtrains = advtrains
function _G.attrans(...) return ... end
function advtrains.invert_train() end
function advtrains.train_ensure_init() end

local atcjit = require("atcjit")

local function assert_atc(train, warn, err, res)
	local w, e = atcjit.execute(train.id,train)
	assert.same(err, e)
	if w then assert.same(warn, w) end
	assert.same(res, train)
end

local function thisatc(desc, train, warn, err, res)
	it(desc, function() assert_atc(train, warn, err, res) end)
end

describe("simple ATC track", function()
	local t = {
		atc_arrow = true,
		atc_command = " B12WB8WBBWOLD15ORD15OCD1RS10WSM",
		door_open = 0,
		max_speed = 20,
		tarvelocity = 10,
		velocity = 0,
	}
	thisatc("should make the train slow down to 12", t, {}, nil,{
		atc_arrow = true,
		atc_brake_target = 12,
		atc_command = "B8WBBWOLD15ORD15OCD1RS10WSM",
		atc_wait_finish = true,
		door_open = 0,
		max_speed = 20,
		tarvelocity = 10,
		velocity = 0,
	})
	thisatc("should make the train brake to 8", t, {}, nil, {
		atc_arrow = true,
		atc_brake_target = 8,
		atc_command  = "BBWOLD15ORD15OCD1RS10WSM",
		atc_wait_finish = true,
		door_open = 0,
		max_speed = 20,
		tarvelocity = 8,
		velocity = 0,
	})
	thisatc("should make the train stop", t, {}, nil, {
		atc_arrow = true,
		atc_brake_target = -1,
		atc_command = "OLD15ORD15OCD1RS10WSM",
		atc_wait_finish = true,
		door_open = 0,
		max_speed = 20,
		tarvelocity = 0,
		velocity = 0,
	})
	thisatc("should make the train open its left doors", t, {}, nil, {
		atc_arrow = true,
		atc_brake_target = -1,
		atc_command = "ORD15OCD1RS10WSM",
		atc_delay = 15,
		atc_wait_finish = true,
		door_open = -1,
		max_speed = 20,
		tarvelocity = 0,
		velocity = 0,
	})
	thisatc("should make the train open its right doors", t, {}, nil,{
		atc_arrow = true,
		atc_brake_target = -1,
		atc_command = "OCD1RS10WSM",
		atc_delay = 15,
		atc_wait_finish = true,
		door_open = 1,
		max_speed = 20,
		tarvelocity = 0,
		velocity = 0,
	})
	thisatc("should make the train close its doors", t, {}, nil, {
		atc_arrow = true,
		atc_brake_target = -1,
		atc_command = "RS10WSM",
		atc_delay = 1,
		atc_wait_finish = true,
		door_open = 0,
		max_speed = 20,
		tarvelocity = 0,
		velocity = 0,
	})
	thisatc("should make the train depart and accelerate to 10", t, {}, nil, {
		atc_arrow = true,
		atc_brake_target = -1,
		atc_command = "SM",
		atc_delay = 1,
		atc_wait_finish = true,
		door_open = 0,
		max_speed = 20,
		tarvelocity = 10,
		velocity = 0,
	})
	thisatc("should make the train accelerate to 20", t, {}, nil, {
		atc_arrow = true,
		atc_brake_target = -1,
		atc_delay = 1,
		atc_wait_finish = true,
		door_open = 0,
		max_speed = 20,
		tarvelocity = 20,
		velocity = 0,
	})
end)

describe("ATC track with whitespaces", function()
	local t = {
		atc_command = " \t\n OC \n S20 \r "
	}
	thisatc("should not cause errors", t, {}, nil, {
		door_open = 0,
		tarvelocity = 20,
	})
end)

describe("empty ATC track", function()
	local t = {atc_command = ""}
	thisatc("should not do anything", t, {}, nil, {})
end)

describe("ATC track with nested I statements", function()
	local t = {
		atc_arrow = false,
		atc_command = "I+OREI>5I<=10S16WORES12;D15;;OC",
		velocity = 10,
		door_open = 0,
	}
	thisatc("should make the train accelerate to 16", t, {}, nil,{
		atc_arrow = false,
		atc_command = "ORD15OC",
		atc_wait_finish = true,
		velocity = 10,
		door_open = 0,
		tarvelocity = 16,
	})
end)

describe("ATC track with invalid statement", function()
	local t = { atc_command = "Ifoo" }
	thisatc("should report an error", t, {}, "Invalid command or malformed I statement: Ifoo", t)
end)

describe("ATC track with invalid I condition", function()
	local t = { atc_command = "I?;" }
	thisatc("should report an error", t, {}, "Invalid I statement", t)
end)
diff --git a/advtrains/atc.lua b/advtrains/atc.lua
index 2fa3929..eff6420 100644
--- a/advtrains/atc.lua
+++ b/advtrains/atc.lua
@@ -53,15 +53,7 @@ function atc.send_command(pos, par_tid)
					atwarn("ATC rail at", pos, ": Rail not on train's path! Can't determine arrow direction. Assuming +!")
				end
				
				local command = atc.controllers[pts].command				
				command = eval_conditional(command, iconnid==1, train.velocity)
				if not command then command="" end
				command=string.match(command, "^%s*(.*)$")
				
				if command == "" then
					atprint("Sending ATC Command to", train_id, ": Not modifying, conditional evaluated empty.")
					return true
				end
				local command = atc.controllers[pts].command
				
				atc.train_set_command(train, command, iconnid==1)
				atprint("Sending ATC Command to", train_id, ":", command, "iconnid=",iconnid)
@@ -182,184 +174,18 @@ function atc.get_atc_controller_formspec(pos, meta)
	return formspec.."button_exit[0.5,4.5;7,1;save;"..attrans("Save").."]"
end

---from trainlogic.lua train step
local matchptn={
	["SM"]=function(id, train)
		train.tarvelocity=train.max_speed
		return 2
	end,
	["S([0-9]+)"]=function(id, train, match)
		train.tarvelocity=tonumber(match)
		return #match+1
	end,
	["B([0-9]+)"]=function(id, train, match)
		if train.velocity>tonumber(match) then
			train.atc_brake_target=tonumber(match)
			if not train.tarvelocity or train.tarvelocity>train.atc_brake_target then
				train.tarvelocity=train.atc_brake_target
			end
		end
		return #match+1
	end,
	["BB"]=function(id, train)
		train.atc_brake_target = -1
		train.tarvelocity = 0
		return 2
	end,
	["W"]=function(id, train)
		train.atc_wait_finish=true
		return 1
	end,
	["D([0-9]+)"]=function(id, train, match)
		train.atc_delay=tonumber(match)
		return #match+1
	end,
	["R"]=function(id, train)
		if train.velocity<=0 then
			advtrains.invert_train(id)
			advtrains.train_ensure_init(id, train)
			-- no one minds if this failed... this shouldn't even be called without train being initialized...
		else
			atwarn(sid(id), attrans("ATC Reverse command warning: didn't reverse train, train moving!"))
		end
		return 1
	end,
	["O([LRC])"]=function(id, train, match)
		local tt={L=-1, R=1, C=0}
		local arr=train.atc_arrow and 1 or -1
		train.door_open = tt[match]*arr
		return 2
	end,
	["K"] = function(id, train)
		if train.door_open == 0 then
			atwarn(sid(id), attrans("ATC Kick command warning: Doors closed"))
			return 1
		end
		if train.velocity > 0 then
			atwarn(sid(id), attrans("ATC Kick command warning: Train moving"))
			return 1
		end
		local tp = train.trainparts
		for i=1,#tp do
			local data = advtrains.wagons[tp[i]]
			local obj = advtrains.wagon_objects[tp[i]]
			if data and obj then
				local ent = obj:get_luaentity()
				if ent then
					for seatno,seat in pairs(ent.seats) do
						if data.seatp[seatno] and not ent:is_driver_stand(seat) then
							ent:get_off(seatno)
						end
					end
				end
			end
		end
		return 1
	end,
}

eval_conditional = function(command, arrow, speed)
	--conditional statement?
	local is_cond, cond_applies, compare
	local cond, rest=string.match(command, "^I([%+%-])(.+)$")
	if cond then
		is_cond=true
		if cond=="+" then
			cond_applies=arrow
		end
		if cond=="-" then
			cond_applies=not arrow
		end
	else 
		cond, compare, rest=string.match(command, "^I([<>]=?)([0-9]+)(.+)$")
		if cond and compare then
			is_cond=true
			if cond=="<" then
				cond_applies=speed<tonumber(compare)
			end
			if cond==">" then
				cond_applies=speed>tonumber(compare)
			end
			if cond=="<=" then
				cond_applies=speed<=tonumber(compare)
			end
			if cond==">=" then
				cond_applies=speed>=tonumber(compare)
			end
		end
	end	
	if is_cond then
		atprint("Evaluating if statement: "..command)
		atprint("Cond: "..(cond or "nil"))
		atprint("Applies: "..(cond_applies and "true" or "false"))
		atprint("Rest: "..rest)
		--find end of conditional statement
		local nest, pos, elsepos=0, 1
		while nest>=0 do
			if pos>#rest then
				atwarn(sid(id), attrans("ATC command syntax error: I statement not closed: @1",command))
				return ""
			end
			local char=string.sub(rest, pos, pos)
			if char=="I" then
				nest=nest+1
			end
			if char==";" then
				nest=nest-1
			end
			if nest==0 and char=="E" then
				elsepos=pos+0
			end
			pos=pos+1
		end
		if not elsepos then elsepos=pos-1 end
		if cond_applies then
			command=string.sub(rest, 1, elsepos-1)..string.sub(rest, pos)
		else
			command=string.sub(rest, elsepos+1, pos-2)..string.sub(rest, pos)
		end
		atprint("Result: "..command)
	end
	return command
end

function atc.execute_atc_command(id, train)
	--strip whitespaces
	local command=string.match(train.atc_command, "^%s*(.*)$")
	
	
	if string.match(command, "^%s*$") then
		train.atc_command=nil
		return
	end

	train.atc_command = eval_conditional(command, train.atc_arrow, train.velocity)
	
	if not train.atc_command then return end
	command=string.match(train.atc_command, "^%s*(.*)$")
	
	if string.match(command, "^%s*$") then
		train.atc_command=nil
		return
	end
	
	for pattern, func in pairs(matchptn) do
		local match=string.match(command, "^"..pattern)
		if match then
			local patlen=func(id, train, match)
			
			atprint("Executing: "..string.sub(command, 1, patlen))
			
			train.atc_command=string.sub(command, patlen+1)
			if train.atc_delay<=0 and not train.atc_wait_finish then
				--continue (recursive, cmds shouldn't get too long, and it's a end-recursion.)
				atc.execute_atc_command(id, train)
			end
			return
	local w, e = advtrains.atcjit.execute(id, train)
	if w then
		for i = 1, #w, 1 do
			atwarn(sid(id),w[i])
		end
	end
	atwarn(sid(id), attrans("ATC command parse error: Unknown command: @1", command))
	atc.train_reset_command(train, true)
	if e then
		atwarn(sid(id),e)
		atc.train_reset_command(train, true)
	end
end


diff --git a/advtrains/atcjit.lua b/advtrains/atcjit.lua
new file mode 100644
index 0000000..8e01239
--- /dev/null
+++ b/advtrains/atcjit.lua
@@ -0,0 +1,201 @@
local aj_cache = {}

local aj_tostring

local matchptn = {
	["BB"] = function()
		return [[do
			train.atc_brake_target = -1
			train.tarvelocity = 0
		end]], 2
	end,
	["B([0-9]+)"] = function(match)
		return string.format([[do
			train.atc_brake_target = %s
			if not train.tarvelocity or train.tarvelocity > train.atc_brake_target then
				train.tarvelocity = train.atc_brake_target
			end
		end]], match),#match+1
	end,
	["D([0-9]+)"] = function(match)
		return string.format("train.atc_delay = %s", match), #match+1, true
	end,
	["(%bI;)"] = function(match)
		local i = 2
		local l = #match
		local epos
		while (i<l) do
			local b, e, c = string.find(match,"([IE])",i)
			if c == "I" then
				b, e = string.find(match,"%bI;", b)
				-- This is unlikely to happen since the %b pattern is balanced
				if not (b and e) then return nil, "Malformed nested I statement" end
				i = e+1
			else
				epos = b
				break
			end
		end
		local endp = string.find(match,"[WDI]") and true
		local cond = string.match(match,"^I([+-])")
		if cond then
			local vtrue = string.sub(match, 3, epos and epos-1 or -2)
			local vfalse = epos and string.sub(match, epos+1, -2)
			local cstr = (cond == "-") and "not" or ""
			local tstr, err = aj_tostring(vtrue, 1, true)
			if not tstr then return nil, err end
			if vfalse then
				local fstr, err = aj_tostring(vfalse, 1, true)
				if not fstr then return nil, err end
				return string.format("if %s train.atc_arrow then %s else %s end",
					cstr, tstr, fstr), l, endp
			else
				return string.format("if %s train.atc_arrow then %s end", cstr, tstr), l, endp
			end
		else
			local op, ref = string.match(match,"^I([<>]=?)([0-9]+)")
			if not op then
				return _, "Invalid I statement"
			end
			local spos = 2+#op+#ref
			local vtrue = string.sub(match, spos, epos and epos-1 or -2)
			local vfalse = epos and string.sub(match, epos+1, -2)
			local cstr = string.format("train.velocity %s %s", op, ref)
			local tstr = aj_tostring(vtrue, 1, true)
			if vfalse then
				local fstr, err = aj_tostring(vfalse, 1, true)
				if not fstr then return nil, err end
				return string.format("if %s then %s else %s end", cstr, tstr, fstr), l, endp
			else
				return string.format("if %s then %s end", cstr, tstr), l, endp
			end
		end
	end,
	["K"] = function()
		return [=[do
			if train.door_open == 0 then
				_w[#_w+1] = attrans("ATC Kick command warning: Doors are closed")
			elseif train.velocity>0 then
				_w[#_w+1] = attrans("ATC Kick command warning: Train moving")
			else
				local tp = train.trainparts
				for i = 1, #tp do
					local data = advtrains.wagons[tp[i]]
					local obj = advtrains.wagon_objects[tp[i]]
					if data and obj then
						local ent = obj:get_luaentity()
						if ent then
							for seatno, seat in pairs(ent.seats) do
								if data.seatp[seatno] and not ent:is_driver_stand(seat) then
									ent:get_off(seatno)
								end
							end
						end
					end
				end
			end
		end]=], 1
	end,
	["O([LR])"] = function(match)
		local tt = {L = -1, R = 1}
		return string.format("train.door_open = %d*(train.atc_arrow and 1 or -1)",tt[match]), 2
	end,
	["OC"] = function(match)
		return "train.door_open = 0", 2
	end,
	["R"] = function()
		return [[
			if train.velocity<=0 then
				advtrains.invert_train(id)
				advtrains.train_ensure_init(id, train)
			else
				_w[#_w+1] = attrans("ATC Reverse command warning: didn't reverse train, train moving!")
			end]], 1
	end,
	["SM"] = function()
		return "train.tarvelocity=train.max_speed", 2
	end,
	["S([0-9]+)"] = function(match)
		return string.format("train.tarvelocity=%s",match), #match+1
	end,
	["W"] = function()
		return "train.atc_wait_finish=true", 1, true
	end,
}

local function aj_tostring_single(cmd, pos)
	if not pos then pos = 1 end
	for pattern, func in pairs(matchptn) do
		local match = string.match(cmd, "^"..pattern, pos)
		if match then
			return func(match)
		end
	end
	return nil
end

aj_tostring = function(cmd, pos, noreset)
	if not pos then pos = 1 end
	local t = {}
	local endp = false
	while pos <= #cmd do
		if string.match(cmd,"^%s+$", pos) then break end
		local _, e = string.find(cmd, "^%s+", pos)
		if e then pos = e+1 end
		local str, len
		str, len, endp = aj_tostring_single(cmd, pos)
		if not str then
			return nil, (len or "Invalid command or malformed I statement: "..string.sub(cmd,pos))
		end
		t[#t+1] = str
		pos = pos+len
		if endp then
			if endp then
				t[#t+1] = string.format("_c[#_c+1]=%q",string.sub(cmd, pos))
			end
			break
		end
	end
	return table.concat(t,"\n"), pos
end

local function aj_compile(cmd)
	if aj_cache[cmd] then
		local x = aj_cache[cmd]
		if type(x) == "function" then
			return x
		else
			return nil, x
		end
	end
	local str, err = aj_tostring(cmd)
	if not str then
		aj_cache[cmd] = err
		return nil, err
	end
	local str = string.format([[return function(id,train)
		local _c = {}
		local _w = {}
		%s
		if _c[1] then train.atc_command=table.concat(_c)
		else train.atc_command = nil end
		return _w, nil
	end]], str)
	local f, e = loadstring(str)
	if not f then return nil, e end
	f = f()
	aj_cache[cmd] = f
	return f
end

local function aj_execute(id,train)
	if not train.atc_command then return end
	local func, err = aj_compile(train.atc_command)
	if func then return func(id,train) end
	return nil, err
end

return {
	compile = aj_compile,
	execute = aj_execute
}
diff --git a/advtrains/init.lua b/advtrains/init.lua
index 6e622e5..2c3c7fc 100644
--- a/advtrains/init.lua
+++ b/advtrains/init.lua
@@ -195,6 +195,8 @@ advtrains.meseconrules =

advtrains.fpath=minetest.get_worldpath().."/advtrains"

advtrains.atcjit=dofile(advtrains.modpath.."/atcjit.lua")

dofile(advtrains.modpath.."/path.lua")
dofile(advtrains.modpath.."/trainlogic.lua")
dofile(advtrains.modpath.."/trainhud.lua")
-- 
2.30.0

[PATCH 2/2] Allow ATC patterns to catch multiple entries

Details
Message ID
<20210202171038.2167-1-yw05@forksworld.de>
In-Reply-To
<20210202161304.3649-1-yw05@forksworld.de> (view parent)
DKIM signature
missing
Download raw message
Patch: +3 -3
---
 advtrains/atcjit.lua | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/advtrains/atcjit.lua b/advtrains/atcjit.lua
index 8e01239..7d4af04 100644
--- a/advtrains/atcjit.lua
+++ b/advtrains/atcjit.lua
@@ -126,9 +126,9 @@ local matchptn = {
local function aj_tostring_single(cmd, pos)
	if not pos then pos = 1 end
	for pattern, func in pairs(matchptn) do
		local match = string.match(cmd, "^"..pattern, pos)
		if match then
			return func(match)
		local match = {string.match(cmd, "^"..pattern, pos)}
		if match[1] then
			return func(unpack(match))
		end
	end
	return nil
-- 
2.30.0

[PATCH 3/3] Add command to view generated lua code; add unittests for repeating code

Details
Message ID
<20210202184902.3793-1-yw05@forksworld.de>
In-Reply-To
<20210202161304.3649-1-yw05@forksworld.de> (view parent)
DKIM signature
missing
Download raw message
Patch: +60 -27
---
 advtrains/aj_unittest.lua | 15 +++++++++++
 advtrains/atc.lua         | 15 +++++++++--
 advtrains/atcjit.lua      | 57 ++++++++++++++++++++++-----------------
 3 files changed, 60 insertions(+), 27 deletions(-)

diff --git a/advtrains/aj_unittest.lua b/advtrains/aj_unittest.lua
index 9dacbc3..a660131 100644
--- a/advtrains/aj_unittest.lua
+++ b/advtrains/aj_unittest.lua
@@ -153,3 +153,18 @@ describe("ATC track with invalid I condition", function()
	local t = { atc_command = "I?;" }
	thisatc("should report an error", t, {}, "Invalid I statement", t)
end)

describe("ATC track reusing existing code", function()
	local t = { atc_command = " B12WB8WBBWOLD15ORD15OCD1RS10WSM", tarvelocity = 15 }
	thisatc("should do the same thing as in the first test", t, {}, nil, {
		atc_brake_target = 12,
		atc_command = "B8WBBWOLD15ORD15OCD1RS10WSM",
		atc_wait_finish = true,
		tarvelocity = 12
	})
end)

describe("ATC track reusing malformed code", function()
	local t = {atc_command = "I?;"}
	thisatc("Should report the invalid I statement", t, {}, "Invalid I statement", t)
end)
diff --git a/advtrains/atc.lua b/advtrains/atc.lua
index eff6420..186c506 100644
--- a/advtrains/atc.lua
+++ b/advtrains/atc.lua
@@ -174,7 +174,6 @@ function atc.get_atc_controller_formspec(pos, meta)
	return formspec.."button_exit[0.5,4.5;7,1;save;"..attrans("Save").."]"
end


function atc.execute_atc_command(id, train)
	local w, e = advtrains.atcjit.execute(id, train)
	if w then
@@ -188,7 +187,19 @@ function atc.execute_atc_command(id, train)
	end
end


minetest.register_chatcommand("at_get_lua",{
	params = "<command>",
	description = "Compile the given ATC command into Lua code and show the given code",
	privs = {train_admin = true},
	func = function(name, params)
		local f, s = advtrains.atcjit.compile(params)
		if not f then	
			return false, s or ("Unknown compilation error")
		else
			return true, s
		end
	end,
})

--move table to desired place
advtrains.atc=atc
diff --git a/advtrains/atcjit.lua b/advtrains/atcjit.lua
index 7d4af04..52a03c2 100644
--- a/advtrains/atcjit.lua
+++ b/advtrains/atcjit.lua
@@ -1,4 +1,5 @@
local aj_cache = {}
local aj_strout = {}

local aj_tostring

@@ -150,8 +151,9 @@ aj_tostring = function(cmd, pos, noreset)
		t[#t+1] = str
		pos = pos+len
		if endp then
			if endp then
				t[#t+1] = string.format("_c[#_c+1]=%q",string.sub(cmd, pos))
			local cont = string.sub(cmd, pos)
			if not string.match(cont, "^%s*$") then
				t[#t+1] = string.format("_c[#_c+1]=%q",cont)
			end
			break
		end
@@ -160,32 +162,37 @@ aj_tostring = function(cmd, pos, noreset)
end

local function aj_compile(cmd)
	if aj_cache[cmd] then
		local x = aj_cache[cmd]
		if type(x) == "function" then
			return x
	local c = aj_cache[cmd]
	if c then
		if type(c) == "function" then
			return c, aj_strout[cmd]
		else
			return nil, x
			return nil, c
		end
	else
		local str, err = aj_tostring(cmd)
		if not str then
			aj_cache[cmd] = err
			return nil, err
		end
		str = string.format([[return function(id, train)
			local _c={}
			local _w={}
			%s
			if _c[1] then train.atc_command=table.concat(_c)
			else train.atc_command=nil end
			return _w, nil
		end]], str)
		local f, e = loadstring(str)
		if not f then
			aj_cache[cmd] = e
			return nil, e
		end
		f = f()
		aj_cache[cmd] = f
		aj_strout[cmd] = str
		return f, str
	end
	local str, err = aj_tostring(cmd)
	if not str then
		aj_cache[cmd] = err
		return nil, err
	end
	local str = string.format([[return function(id,train)
		local _c = {}
		local _w = {}
		%s
		if _c[1] then train.atc_command=table.concat(_c)
		else train.atc_command = nil end
		return _w, nil
	end]], str)
	local f, e = loadstring(str)
	if not f then return nil, e end
	f = f()
	aj_cache[cmd] = f
	return f
end

local function aj_execute(id,train)
-- 
2.30.0

Re: [PATCH 3/3] Add command to view generated lua code; add unittests for repeating code

Details
Message ID
<65da448b-a0e6-fc9c-8b79-34019890db7f@gpcf.eu>
In-Reply-To
<20210202184902.3793-1-yw05@forksworld.de> (view parent)
DKIM signature
missing
Download raw message
I got an observations: How does the _c table gain more than one entry? I 
would make the compiler slightly smarter to only create _c and _w tables 
if absolutely needed by the commands, which would simplify the code a 
lot. I don't know how table.concat actually works, but it seems to me 
that it could add a fair bit of overhead for something that could be 
fixed at compile-time even if it concats a single entry, just by virtue 
of pairs() being quite inefficient.

[PATCH] Optimizations

Details
Message ID
<20210205172157.6757-1-yw05@forksworld.de>
In-Reply-To
<65da448b-a0e6-fc9c-8b79-34019890db7f@gpcf.eu> (view parent)
DKIM signature
missing
Download raw message
Patch: +58 -51
---
 advtrains/atc.lua    |   7 +--
 advtrains/atcjit.lua | 102 ++++++++++++++++++++++++-------------------
 2 files changed, 58 insertions(+), 51 deletions(-)

diff --git a/advtrains/atc.lua b/advtrains/atc.lua
index 186c506..7b1ec2f 100644
--- a/advtrains/atc.lua
+++ b/advtrains/atc.lua
@@ -175,12 +175,7 @@ function atc.get_atc_controller_formspec(pos, meta)
end

function atc.execute_atc_command(id, train)
	local w, e = advtrains.atcjit.execute(id, train)
	if w then
		for i = 1, #w, 1 do
			atwarn(sid(id),w[i])
		end
	end
	local _, e = advtrains.atcjit.execute(id, train)
	if e then
		atwarn(sid(id),e)
		atc.train_reset_command(train, true)
diff --git a/advtrains/atcjit.lua b/advtrains/atcjit.lua
index 52a03c2..e3df1d1 100644
--- a/advtrains/atcjit.lua
+++ b/advtrains/atcjit.lua
@@ -3,25 +3,39 @@ local aj_strout = {}

local aj_tostring

--[[ Notes on the pattern matching functions:
- Patterns can have multiple captures (e.g. coordinates). These captures
are passed starting from the second argument.
- The commands after the match are passed as the first argument. If the
command involves waiting, it should:
  - Set train.atc_command to the first argument passed to the function,
  - End with "do return end", and
  - Return true as the second argument
- The function should work on 
]]
local matchptn = {
	["BB"] = function()
		return [[do
			train.atc_brake_target = -1
			train.tarvelocity = 0
		end]], 2
		end]]
	end,
	["B([0-9]+)"] = function(match)
	["B([0-9]+)"] = function(_, match)
		return string.format([[do
			train.atc_brake_target = %s
			if not train.tarvelocity or train.tarvelocity > train.atc_brake_target then
				train.tarvelocity = train.atc_brake_target
			end
		end]], match),#match+1
		end]], match)
	end,
	["D([0-9]+)"] = function(match)
		return string.format("train.atc_delay = %s", match), #match+1, true
	["D([0-9]+)"] = function(cont, match)
		return string.format([[do
			train.atc_delay = %s
			train.atc_command = %q
			return
		end]], match, cont), true
	end,
	["(%bI;)"] = function(match)
	["(%bI;)"] = function(cont, match)
		local i = 2
		local l = #match
		local epos
@@ -43,15 +57,15 @@ local matchptn = {
			local vtrue = string.sub(match, 3, epos and epos-1 or -2)
			local vfalse = epos and string.sub(match, epos+1, -2)
			local cstr = (cond == "-") and "not" or ""
			local tstr, err = aj_tostring(vtrue, 1, true)
			local tstr, err = aj_tostring(vtrue, 1, cont)
			if not tstr then return nil, err end
			if vfalse then
				local fstr, err = aj_tostring(vfalse, 1, true)
				local fstr, err = aj_tostring(vfalse, 1, cont)
				if not fstr then return nil, err end
				return string.format("if %s train.atc_arrow then %s else %s end",
					cstr, tstr, fstr), l, endp
					cstr, tstr, fstr)
			else
				return string.format("if %s train.atc_arrow then %s end", cstr, tstr), l, endp
				return string.format("if %s train.atc_arrow then %s end", cstr, tstr)
			end
		else
			local op, ref = string.match(match,"^I([<>]=?)([0-9]+)")
@@ -62,22 +76,22 @@ local matchptn = {
			local vtrue = string.sub(match, spos, epos and epos-1 or -2)
			local vfalse = epos and string.sub(match, epos+1, -2)
			local cstr = string.format("train.velocity %s %s", op, ref)
			local tstr = aj_tostring(vtrue, 1, true)
			local tstr = aj_tostring(vtrue, 1, cont)
			if vfalse then
				local fstr, err = aj_tostring(vfalse, 1, true)
				local fstr, err = aj_tostring(vfalse, 1, cont)
				if not fstr then return nil, err end
				return string.format("if %s then %s else %s end", cstr, tstr, fstr), l, endp
				return string.format("if %s then %s else %s end", cstr, tstr, fstr)
			else
				return string.format("if %s then %s end", cstr, tstr), l, endp
				return string.format("if %s then %s end", cstr, tstr)
			end
		end
	end,
	["K"] = function()
		return [=[do
			if train.door_open == 0 then
				_w[#_w+1] = attrans("ATC Kick command warning: Doors are closed")
				atwarn(sid(id),attrans("ATC Kick command warning: Doors are closed"))
			elseif train.velocity>0 then
				_w[#_w+1] = attrans("ATC Kick command warning: Train moving")
				atwarn(sid(id),attrans("ATC Kick command warning: Train moving"))
			else
				local tp = train.trainparts
				for i = 1, #tp do
@@ -95,14 +109,14 @@ local matchptn = {
					end
				end
			end
		end]=], 1
		end]=]
	end,
	["O([LR])"] = function(match)
	["O([LR])"] = function(_, match)
		local tt = {L = -1, R = 1}
		return string.format("train.door_open = %d*(train.atc_arrow and 1 or -1)",tt[match]), 2
		return string.format("train.door_open = %d*(train.atc_arrow and 1 or -1)",tt[match])
	end,
	["OC"] = function(match)
		return "train.door_open = 0", 2
	["OC"] = function()
		return "train.door_open = 0"
	end,
	["R"] = function()
		return [[
@@ -110,32 +124,38 @@ local matchptn = {
				advtrains.invert_train(id)
				advtrains.train_ensure_init(id, train)
			else
				_w[#_w+1] = attrans("ATC Reverse command warning: didn't reverse train, train moving!")
			end]], 1
				atwarn(sid(id),attrans("ATC Reverse command warning: didn't reverse train, train moving!"))
			end]]
	end,
	["SM"] = function()
		return "train.tarvelocity=train.max_speed", 2
		return "train.tarvelocity=train.max_speed"
	end,
	["S([0-9]+)"] = function(match)
		return string.format("train.tarvelocity=%s",match), #match+1
	["S([0-9]+)"] = function(_, match)
		return string.format("train.tarvelocity=%s",match)
	end,
	["W"] = function()
		return "train.atc_wait_finish=true", 1, true
	["W"] = function(cont)
		return string.format([[do
			train.atc_wait_finish=true
			train.atc_command=%q
			return
		end]], cont), true
	end,
}

local function aj_tostring_single(cmd, pos)
local function aj_tostring_single(cmd, pos, cont)
	if not pos then pos = 1 end
	for pattern, func in pairs(matchptn) do
		local match = {string.match(cmd, "^"..pattern, pos)}
		local match = {string.find(cmd, "^"..pattern, pos)}
		if match[1] then
			return func(unpack(match))
			local e = match[2]
			match[2] = string.sub(cmd, e+1)..(cont or "")
			return e+1, func(unpack(match, 2))
		end
	end
	return nil
end

aj_tostring = function(cmd, pos, noreset)
aj_tostring = function(cmd, pos, cont)
	if not pos then pos = 1 end
	local t = {}
	local endp = false
@@ -143,18 +163,14 @@ aj_tostring = function(cmd, pos, noreset)
		if string.match(cmd,"^%s+$", pos) then break end
		local _, e = string.find(cmd, "^%s+", pos)
		if e then pos = e+1 end
		local str, len
		str, len, endp = aj_tostring_single(cmd, pos)
		local nxt, str
		nxt, str, endp = aj_tostring_single(cmd, pos, cont or "")
		if not str then
			return nil, (len or "Invalid command or malformed I statement: "..string.sub(cmd,pos))
			return nil, (endp or "Invalid command or malformed I statement: "..string.sub(cmd,pos))
		end
		t[#t+1] = str
		pos = pos+len
		pos = nxt
		if endp then
			local cont = string.sub(cmd, pos)
			if not string.match(cont, "^%s*$") then
				t[#t+1] = string.format("_c[#_c+1]=%q",cont)
			end
			break
		end
	end
@@ -176,12 +192,8 @@ local function aj_compile(cmd)
			return nil, err
		end
		str = string.format([[return function(id, train)
			local _c={}
			local _w={}
			%s
			if _c[1] then train.atc_command=table.concat(_c)
			else train.atc_command=nil end
			return _w, nil
			train.atc_command=nil
		end]], str)
		local f, e = loadstring(str)
		if not f then
-- 
2.30.0

[PATCH] Fix incomplete comment

Details
Message ID
<20210205172606.6898-1-yw05@forksworld.de>
In-Reply-To
<20210205172157.6757-1-yw05@forksworld.de> (view parent)
DKIM signature
missing
Download raw message
Patch: +2 -1
---
 advtrains/atcjit.lua | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/advtrains/atcjit.lua b/advtrains/atcjit.lua
index e3df1d1..973e27a 100644
--- a/advtrains/atcjit.lua
+++ b/advtrains/atcjit.lua
@@ -11,7 +11,8 @@ command involves waiting, it should:
  - Set train.atc_command to the first argument passed to the function,
  - End with "do return end", and
  - Return true as the second argument
- The function should work on 
- The function should return a string to be compiled as the first result
and should not fail.
]]
local matchptn = {
	["BB"] = function()
-- 
2.30.0
Reply to thread Export thread (mbox)