187 lines
5.8 KiB
Lua
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 |