basic_robot/scripts/programming/natural language.lua
2021-06-28 08:49:33 +02:00

187 lines
5.8 KiB
Lua

-- "natural language" programming demo by rnd, 2021
-- input is lines of 'natural' language, will be translated into lua code
--[[
move forward 3
turn left
dig left
if see dirt turn left and move forward
subroutine circle commands..
--> TRANSFORMED into lua :
for i = 1,3 do move.forward(); pause(); end
turn.left(); pause();
dig.left(); pause()
if read_node.forward()=="dirt" then turn.left();pause(); move.forward(); pause() end
TODO: integrate into robots, maybe command: code.natural(..)
--]]
if not init then init = true
prog = [[
subroutine walk
if see air move forward and move down
if see dirt move up
if see wood turn right
subroutine end
walk
]]
subroutines = {}; -- [subname] = true , so we know its subroutine
nodenames = -- translation of block names into minetest
{
["dirt"] = "default:dirt",
["cobble"] = "default:cobble",
["stone"] = "default:stone",
["wood"] = "default:wood",
["water"] = "default:water_source",
}
keywords = {
["quit"] = function() return "break;" end,
["move"] = {
["forward"] = function() return "if move.forward() then pause();paused=true; end;" end,
["backward"] = function() return "if move.backward() then pause();paused=true; end;" end,
["left"] = function() return "if move.left() then pause();paused=true; end;" end,
["right"] = function() return "if move.right() then pause();paused=true; end;" end,
["up"] = function() return "if move.up() then pause();paused=true; end;" end,
["down"] = function() return "if move.down() then pause();paused=true; end;" end,
},
["turn"] = {
["left"] = function() return "turn.left(); pause();paused=true;" end,
["right"] = function() return "turn.right(); pause();paused=true;" end,
["random"] = function() return "if math.random(2)==1 then turn.right() else turn.left() end; pause(); paused=true;" end
},
["dig"] = function() return "dig.forward(); pause();" end, --TODO: remember to set robot energy to large value at start
["place"] = function(line)
local pattern1 = "place";
local i = string.find(line,pattern1)+ string.len(pattern1)+1
local nodename = string.sub(line, i) -- what are we placing?
if nodenames[nodename] then nodename = nodenames[nodename] end -- translate name
return "place.forward('" .. nodename .. "'); pause();"
end,
["if"] = {
["see"] = function(line)
local pattern1 = "see";
local pattern2 = "and" .. " "; -- important, space after 'and'
local i = string.find(line,pattern1) + string.len(pattern1) + 1
--nodename command
local j = string.find(line," ",i)
local nodename, command
nodename = string.sub(line,i,j-1)
if nodenames[nodename] then nodename = nodenames[nodename] end -- translate name
-- maybe command has several parts separated by 'and' ?
--cmd1 and cmd2 and cmd3
j = j+1; local cmds = {}; local found = false
while true do
local k = string.find(line,pattern2,j+1)
if not k then-- no more AND
if found then
cmds[#cmds+1] = string.sub(line,j + string.len(pattern2)) break
else
cmds[#cmds+1] = string.sub(line,j) break
end
end
if found then
cmds[#cmds+1] = string.sub(line,j+string.len(pattern2),k-1)
else
cmds[#cmds+1] = string.sub(line,j,k-1)
end
found = true
j=k
end
for i = 1,#cmds do
cmds[i] = parse_line(cmds[i])
end
return "if read_node.forward()=='" ..nodename .. "' then " .. table.concat(cmds," ") .. " end"
end,
},
}
parse_line = function(line)
local struct = keywords;
for word in string.gmatch(line,"%S+") do
local matched = struct[word]
local issub = subroutines[word]
if matched or issub then
--say(word .. " = " .. type(matched))
if type(matched) == "table" then
struct = matched; -- climb deeper into structure
else
local instruction;
if issub then
instruction = word.."();"
else
instruction = matched(line)
end
-- do we have need to repeat instruction?
local i = string.find(line,word) + string.len(word) + 1
local snum = tonumber(string.sub(line,i)) or 1 -- repeating?
if snum>1 and snum<10 then
return "for i = 1,"..snum .." do " .. instruction .. " end;"
end
return instruction
end
else
say("error in line: " .. line .. ", unknown command " .. word) return ""
end
end
end
parse_prog = function(code)
local out = {};
local subdef = false; -- are we defining subroutine?
local subname;
local subcmds = {}
local pattern1 = "subroutine"
for line in string.gmatch(code,"[^\n]+") do -- line by line
local i = string.find(line,pattern1)
if i then -- do we define new subroutine command?
local j = i+string.len(pattern1)+1
local sname = string.sub(line,j)
if subdef and sname == "end" then -- end of subroutine
subdef = false
if not keywords[subname] then
out[#out+1 ] = "function " .. subname .. "()\n" .. table.concat(subcmds,"\n") .. "\nend"
subroutines[subname] = true;
else
-- error, subroutine name is reserved keyword
end
subcmds = {};
else
subdef = true -- all commands will now register with subroutine
subname = sname
end
else -- normal command
if subdef then
subcmds[#subcmds+1] = parse_line(line)
else
out[#out+1] = parse_line(line)
end
end
end
return "--coroutine NaturalLanguage autogenerated\nwhile true do paused = false; "..table.concat(out,"\n") .."if not paused then pause() end end"
end
parsed_prog = parse_prog(prog)
self.label(prog .. "\n\n==>\n\n" .. parsed_prog)
code.set(parsed_prog) -- actually run code by robot
end