Hi!

Did you ever wonder, how the hell the go program does function, or some enhancements of them (like the Inturtlpler , muTurtle, and many others)? How do they loop ? At the end of this tutorial, you will be able to make your very own turtle program, which, when launching such as follow :


execute call sl 5 10 { plU plD bk plF }

selects the 5th slot, and make a 10-blocks-long wall, by placing up and down then going backward and placing forward, 10 times.

or the functional version :

execute( "sl", 5, 10, {"plU","plD","bk","plF"} )

When I say your very own, that means your very own, so if you want it to perform infinite loops, logical operations, or anything, I will give you the tools to do it yourself, and quite simply. I saw many topics about that kind of stuff among the programs, but haven't found any in tutorial, so I made my own.


Final code to understand
Spoiler

local actions = turtle

local ans = {}

local fnil = function() end
local fconst = function(constant)
		return function()
			return constant
		end	
end 
local fans = function ()
return ans._
end


local orders = {...}

local i = 1

local keywords = {
call = function ()
		i = i + 1
	local nbarg = tonumber(orders[i])
	if nbarg then
		i = i + 1
	else
		nbarg = 1
	end
	actions[orders[i]](myunpack(orders, i + 1, i + nbarg, tonumber))
	i = i + nbarg + 1
return(ans._)
end,
}

keywords["or"] = function ()
	if ans._ then
		i = i + 1
	end
	return ans._
end

keywords["and"] = function ()
	if not ans._ then
		i = i + 1
	end
	return ans._
end

keywords["{"] = function ()
	i = i + 1
	while orders[i] ~= "}" do
		ans._ = actions[orders[i]]()
		i=i+1
	end
	return ans._
end

keywords["dowh"] = function ()
	local _i = i + 1
	ans._ = true
	while ans._ do
		i = _i
		ans._ = actions[orders[i]]()
	end
	return ans.
end


local metakw ={
__index = function ()
	error "That's not how we use execute !!"
end,
}

setmetatable(keywords, metakw)

local meta = {
__index = function (t,k)
	if tonumber(k) then
		local _i = i + 1
			for j = 1,tonumber(k) do
				i = _i
				ans._ = actions[orders[i]]()
			end
		return fans
		
	else
		return keywords[k]
	end
end,
}

setmetatable(actions, meta)

while i <= #orders do
	ans._ = actions[orders[i]]()
	i = i+1
end 


Getting started:

SpoilerWell, after typing "edit execute" in the shell, the first thing to do is to define what elementary actions we will be able to use. Why not putting them in a table?!


local actions = {
stuff1 = function (...)
--do any thing you want
end,
dance = function (...)
end,
hi = function (...)
print("hello")
end,
}

You can also base your actions on already existing tables, like APIs :


local actions = turtle


Then, execute your actions in a row :


local orders = {...}

for i,v in ipairs(orders) do
actions[v]()
end


The first line creates a tables with your inputs, indexed by numbers, the ipair loop will go through it, and call the corresponding actions.

I want to make it shorter.
Then just do it. Here is what I currently use
Spoiler

local shortCuts = {
fd = "forward",
bk = "back",
up = "up",
dn = "down",

tL = "turnLeft",
lt = "turnLeft",
tR = "turnRight",
rt = "turnRight",

sl = "select",
gIC = "getItemCount",
gIS = "getItemSpace",

--m like mine
m = "dig",
mUp = "digUp",
mDn = "digDown",

a = "attack",
aUp = "attackUp",
aDn = "attackDown",

p = "place",
pUp = "placeUp",
pDn = "placeDown",

d = "detect",
dUp = "detectUp",
dDn = "detectDown",

c = "compare",
cUp = "compareUp",
cDn = "compareDn",
cTo = "compareTo",

tTo = "transferTo",
--t like transfer/throw
t = "drop",
tUp = "dropUp",
tDn = "dropDown",

s = "suck",
sUp = "suckUp",
sDn = "suckDown",

gFL = "getFuelLevel",
ref = "refuel",

craft = "crf"
}

for k,v in pairs(shortCuts) do
	actions[k] = actions[v]
end

but you would be silly to copy and paste that, because the point of this tutorial is to make your OWN program with your very OWN shortcuts, and after using it a bit, I can tell you I could have done better.

Note that, at this point, we can only call simple functions with no arguments. It will soon change.

Getting looped

SpoilerAs I find more logic to do things when they come, I find more logic to say "go 100 meters forward" than "go forward 100 meters", but its mainly a personal choice. We do not want to alter thoroughly our code, but we can't put a function in the actions table for each number, and that function would have to look at the table.

Now, the magic appears.. With metatables.


Metatables are actually tables that wrap other tables in order to allow a lot more functionalities, to make things both complex and simpler, like defining addition for tables, or making proxy…
Take a look at the following code to see what happens :


local actions = turtle

local orders = {...}

local i = 1

local meta = {
__index = function (t,k)
	if tonumber(k) then
		i = i + 1
			for j = 1,tonumber(k) do
				actions[orders[i]]()
			end
		return function ()
		end
	end
end,
}

setmetatable(actions, meta)

while i <= #orders do
	actions[orders[i]]()
	i = i+1
end


There still are the actions and the orders. I replaced the "for … in ipairs" loop by a while loop in order to modify i by external means. I created a metatable, and assiociated it to actions. Now, when something strange happens on actions, the metatable will be checked and will eventually tell what to do. Here, when we call action[3] for example, the computer see that 3 is not a field, and, before returning nil, it searchs the field __index in meta. If there is any, and meta.__index is a function, it will return meta.__index(actions,3) (or metatable.__index ( table_called_with_a_inexistant_field, inexistant_field ) in the general case).

Take a look at ( http://www.lua.org/pil/13.html ) or here to know more about metatables.


What happens next ?
As I said, it calls meta.__index(actions,k), it checks whether k is a number, and if it does, it calls the next element k times. The i = i + 1 ensures that this element will be skipped in the main loop, once the secondary is finished.

Note that because of the '()' in the 'actions[orders]()' of the main loop, we have to return a function with no argument. For both short scripting and speed computing, we can define it at the beginning :


local fnil = function() end


Well, what happen if we run "execute 3 3 forward" ? We could expect that it's equivalent to "execute 9 forward", but it actually travel only 5 blocks! In fact, it enter in the first loop, and "i=i+1" sets i to 2. then it enters in the second loop and sets i to 3. After moving thrice, the second loops stops, and we come back to the first. But now i=3, so we do not call the second loop again. Instead, it call twice the 3rd instruction, go forward.

Well, the thing is, we want to loop always on the same i, and the functions in the loop can change its value. A simple fix is to store the position for each loop :



local meta = {
__index = function (t,k)
	if tonumber(k) then
		local _i = i + 1
			for j = 1,tonumber(k) do
				i = _i
				actions[orders[i]]()
			end
		return fnil
	else
		return fnil
	end
end,
}


Going further
SpoilerAt this point, we can do almost the same things as the go function. But the go program loops when encountering an object ? No problem, set


actions.up/down/forward/back = function (...)
while turtle.up/down/forward/back() do
end


You want to raise an error if the user type something wrong ? Then just add it to the metatable!


local meta = {
__index = function (t,k)
	if tonumber(k) then
		i = i + 1
		return function ()
			for j = 1,tonumber(k) do
				actions[orders[i]]()
			end
		end
	else
		error( "NOOO! that's not how we use execute" )
	end
end,
}

Or you maybe want to go ahead anyway ?


local meta = {
__index = function (t,k)
	if tonumber(k) then
		i = i + 1
			for j = 1,tonumber(k) do
				actions[orders[i]]()
			end
		return fnil
	else
		return fnil
	end
end,
}

And the possibilities are infinite !!

How about calling instruction with arguments ?

SpoilerWait, have you tried to code it yourself ? at least for a fixed number of arguments (let's say one) ?

Spoiler

local meta = {
__index = function (t,k)
	if tonumber(k) then
		local _i = i + 1
			for j = 1,tonumber(k) do
				i = _i
				actions[orders[i]]()
			end
		return fnil
		
	elseif k == "call"
		i = i + 1
		local nbarg = tonumber(orders[i])
		if nbarg then
			i = i + 1
		else
			nbarg = 1
		end
		actions[orders[i]](myunpack(orders, i + 1, i + nbarg, tonumber))
		i = i + nbarg + 1
		return fnil
		
	else
		error( "NOOO! thats not how we use execute" )
	end
end,
}

with :
Spoiler

myunpack = function (tab, s, e, code)
	local s = s or 1
	local e = e or #tab
	code = code or function (...)
		return (...)
	end
	frec = function (_s)
		if _s <= e then
			return code(tab[_s]), frec(_s + 1)
		end
	end
	
	return frec(s)
end

WARNING : when 'code' isn't provided, all your arguments will be string. In my example, with such a simple code, they all will be numbers.

note :
SpoilerAs far as we could have put anything in actions, we could have achieve the same thing without metatables, but it would have been a lot more confusing. We assume that the functions in action, have access to none of the variables the execute program, but the ones we give them as input. We also could put the functions with arguments in keywords, we don't, for the same reasons. Doing it bye the way we do allows us to reuse the meta, which correspond to a rough language, with other actions.

It's your turn !!
SpoilerNow, if you want, you will attempt to code 'or' and 'and' such as follow :
"execute a or b c", where a, b, c are elementary instructions, execute a (anyway, a is before or, so must be executed), and if a returns true, skip b and goto c (or ends, if b were the last instruction); else, compute and return b.
Similarly, "execute a or b c", execute a and if a returns false, skip b and goto c (or ends, if b were the last instruction); else, compute and return b.

First, we will need to catch and remember the returned value of an instruction. We can add the line

local ans = 0
at the beginning of the file, but if ans is ever set to nil, and reassigned, it could become a polluting global variable, so I put

local ans = {}
at the beginning, and replace each occurrence of that :

actions[orders[i]]()
with that :

ans._ = actions[orders[i]]()
so the returned value of the very last executed task is always available in ans._

You also have to let the loop return and others, otherwise ans._ will be erased. A simple way to do that is to achieve that is to replace fnil with

local fans = function ()
return ans._
end

Well, I gave you all the tools you needed, you can code the rest.

If you really can't, here are the solution.

Spoiler

[...]
elseif k == "or"
	if ans._ then
		i = i + 1
	end
	return fans

elseif k == "or"
	if not ans._ then
		i = i + 1
	end
	return fans
else
[...]

Some harder exercises are : goto, code the brackets, in order to achieve what is proposed at the very beginning of the topic… Or anything you want, though the loop and the argument calling is already a lot.

A very personal and very last enhancement :
SpoilerI prefer put all my keywords (that means interpreted terms but not elementary instruction, like "call", "or" and "and"), in one table. Therefore, I replace the metatable with :


local keywords = {
call = function ()
		i = i + 1
	local nbarg = tonumber(orders[i])
	if nbarg then
		i = i + 1
	else
		nbarg = 1
	end
	actions[orders[i]](myunpack(orders, i + 1, i + nbarg, tonumber))
	i = i + nbarg + 1
return(ans._)
end,
}

local metakw ={
__index = function ()
	error "That's not how we use execute !!"
end,
}

setmetatable(keywords, metakw)

local meta = {
__index = function (t,k)
	if tonumber(k) then
		local _i = i + 1
			for j = 1,tonumber(k) do
				i = _i
				ans._ = actions[orders[i]]()
			end
		return fans
		
	else
		return keywords[k]
	end
end,
}

setmetatable(actions, meta)


So if I want to add keywords, I just have to add

Spoiler

keyword = function ()
	-- do stuff, dealing with i, orders and actions
	return ans._
end,


to the table of keywords.

The brackets :
Spoiler

keywords["{"] = function ()
	i = i + 1
	while orders[i] ~= "}" do
		ans._ = actions[orders[i]]()
		i=i+1
	end
	return ans._
end

Note that this coding of brackets make them incoherent with or, and and 0 (zero loop). We can either have an ugly patch, or try to consider { } as a single argument. It also require an ugly patch, unless we have a more functional point of view. That will motive the second part of this tutorial, but what does less motive motive me to do it is the lack of feedback..


Coming soon :
- making a similar but more powerful function , for function can handle several type of inputs.
- Using tables and functables, as arguments of this function.
- achieving macros saving, and "dynamic" macros saving

Well, thank you guys for reading, it's my first tuto so I'd be glad to have comment, if you have any enhancement/correction to suggest, don't hesitate.