Added repeated instructions and action options.
This commit is contained in:
parent
821a29175d
commit
3fdc6c01f0
|
@ -22,27 +22,6 @@ jobs:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# Disabled minification for now.
|
|
||||||
# - name: Install Lua
|
|
||||||
# uses: ljmf00/setup-lua@v1.0.0
|
|
||||||
# with:
|
|
||||||
# lua-version: 5.3
|
|
||||||
# install-luarocks: true
|
|
||||||
|
|
||||||
# - name: Minify Scripts
|
|
||||||
# run: |
|
|
||||||
# script_dir=docs/src/.vuepress/public/scripts
|
|
||||||
# rm -rf $script_dir
|
|
||||||
# mkdir $script_dir
|
|
||||||
# lua minify.lua minify src/movescript.lua > $script_dir/movescript.lua
|
|
||||||
# lua minify.lua minify src/itemscript.lua > $script_dir/itemscript.lua
|
|
||||||
|
|
||||||
# - name: Clean Up Lua Artifacts
|
|
||||||
# run: |
|
|
||||||
# rm -rf .lua
|
|
||||||
# rm -rf .luarocks
|
|
||||||
# rm -rf .source
|
|
||||||
|
|
||||||
- name: Copy scripts to Docs public assets
|
- name: Copy scripts to Docs public assets
|
||||||
run: |
|
run: |
|
||||||
script_dir=docs/src/.vuepress/public/scripts
|
script_dir=docs/src/.vuepress/public/scripts
|
||||||
|
@ -50,6 +29,24 @@ jobs:
|
||||||
mkdir $script_dir
|
mkdir $script_dir
|
||||||
cp src/* $script_dir/
|
cp src/* $script_dir/
|
||||||
|
|
||||||
|
- name: Install Lua
|
||||||
|
uses: ljmf00/setup-lua@v1.0.0
|
||||||
|
with:
|
||||||
|
lua-version: 5.3
|
||||||
|
install-luarocks: true
|
||||||
|
|
||||||
|
- name: Minify Scripts
|
||||||
|
run: |
|
||||||
|
script_dir=docs/src/.vuepress/public/scripts
|
||||||
|
lua minify.lua minify src/movescript.lua > $script_dir/movescript-min.lua
|
||||||
|
lua minify.lua minify src/itemscript.lua > $script_dir/itemscript-min.lua
|
||||||
|
|
||||||
|
- name: Clean Up Lua Artifacts
|
||||||
|
run: |
|
||||||
|
rm -rf .lua
|
||||||
|
rm -rf .luarocks
|
||||||
|
rm -rf .source
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,10 @@ local ms = require("movescript")
|
||||||
ms.run("2F")
|
ms.run("2F")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `parse(script, settings)`
|
||||||
|
|
||||||
|
Parses the given `script` string and returns a table containing the parsed instructions to be executed. This is mostly useful for debugging your scripts.
|
||||||
|
|
||||||
## `run(script, settings)`
|
## `run(script, settings)`
|
||||||
|
|
||||||
Runs the given `script` string as a movescript, and optionally a `settings` table can be provided. Otherwise, [default settings](settings.md) will be used.
|
Runs the given `script` string as a movescript, and optionally a `settings` table can be provided. Otherwise, [default settings](settings.md) will be used.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Every movescript must follow the outline defined in this specification.
|
Every movescript must follow the outline defined in this specification.
|
||||||
|
|
||||||
Each script consists of zero or more **instructions**, separated by zero or more whitespace characters.
|
Each script consists of zero or more **instructions** or **repeated instructions**, separated by zero or more whitespace characters.
|
||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
|
|
||||||
|
@ -10,11 +10,23 @@ An instruction consists of an optional positive integer number, followed by a re
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
-- The regex used to parse instructions.
|
-- The regex used to parse instructions.
|
||||||
instruction = string.find(script, "%W*(%d*%u%l*)%W*")
|
instruction = string.find(script, "%s*(%d*%u%l*)%s*")
|
||||||
```
|
```
|
||||||
|
|
||||||
Each instruction can be split into two parts: the **action**, and the **count**. The action is the textual part of the instruction, and maps to a turtle behavior. The count is the optional numerical part of the instruction, and defaults to `1` if no number is provided.
|
Each instruction can be split into two parts: the **action**, and the **count**. The action is the textual part of the instruction, and maps to a turtle behavior. The count is the optional numerical part of the instruction, and defaults to `1` if no number is provided.
|
||||||
|
|
||||||
|
Here are some examples of valid instructions: `3F`, `U`, `1R`
|
||||||
|
|
||||||
|
Some instructions may allow you to specify additional options. These can be defined as key-value pairs in parentheses after the action part.
|
||||||
|
|
||||||
|
For example: `4A(delay=0.25, file=tmp.txt)`
|
||||||
|
|
||||||
|
## Repeated Instructions
|
||||||
|
|
||||||
|
A repeated instruction is a grouping of instructions that are repeated a specified number of times. It's denoted as a positive integer number, followed by a series of [instructions](#instructions) within parentheses.
|
||||||
|
|
||||||
|
For example: `22(AF)` - We execute the instructions `A` and `F` 22 times.
|
||||||
|
|
||||||
## Actions
|
## Actions
|
||||||
|
|
||||||
The following table lists all actions that are available in Movescript. Attempting to invoke an action not listed here will result in an error that will terminate your script.
|
The following table lists all actions that are available in Movescript. Attempting to invoke an action not listed here will result in an error that will terminate your script.
|
||||||
|
|
24
install.lua
24
install.lua
|
@ -1,24 +0,0 @@
|
||||||
--[[
|
|
||||||
Installation script for installing all libraries.
|
|
||||||
|
|
||||||
Run `wget run https://raw.githubusercontent.com/andrewlalis/movescript/main/install.lua`
|
|
||||||
to run the installer on your device.
|
|
||||||
]]--
|
|
||||||
|
|
||||||
BASE_URL = "https://raw.githubusercontent.com/andrewlalis/movescript/main/"
|
|
||||||
|
|
||||||
SCRIPTS = {
|
|
||||||
"movescript.lua",
|
|
||||||
"itemscript.lua"
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Create a local executable to re-install, instead of having to run this file via wget.
|
|
||||||
local f = io.open("install-movescript.lua", "w")
|
|
||||||
for _, script in pairs(SCRIPTS) do
|
|
||||||
url = BASE_URL .. script
|
|
||||||
cmd = "wget " .. url .. " " .. script
|
|
||||||
shell.run(cmd)
|
|
||||||
f:write("if fs.exists(\"" .. script .. "\") then fs.delete(\"" .. script .. "\") end")
|
|
||||||
f:write("shell.run(\"" .. cmd .. "\")")
|
|
||||||
end
|
|
||||||
f:close()
|
|
|
@ -10,6 +10,10 @@ that you don't need to get tired of typing "turtle.forward()" over and over.
|
||||||
VERSION = "0.0.1"
|
VERSION = "0.0.1"
|
||||||
|
|
||||||
local t = turtle
|
local t = turtle
|
||||||
|
-- For testing purposes, if the turtle API is not present, we inject our own.
|
||||||
|
if not t then t = {
|
||||||
|
getFuelLimit = function() return 1000000000 end
|
||||||
|
} end
|
||||||
|
|
||||||
-- The movescript module. Functions defined within this table are exported.
|
-- The movescript module. Functions defined within this table are exported.
|
||||||
local movescript = {}
|
local movescript = {}
|
||||||
|
@ -167,25 +171,121 @@ local function executeInstruction(instruction, settings)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Parses a movescript script into a series of instruction tables.
|
local INSTRUCTION_TYPES = {
|
||||||
local function parseScript(script, settings)
|
repeated = 1,
|
||||||
local instructions = {}
|
instruction = 2
|
||||||
for instruction in string.gfind(script, "%W*(%d*%u%l*)%W*") do
|
}
|
||||||
local countIdx, countIdxEnd = string.find(instruction, "%d+")
|
|
||||||
local actionIdx, actionIdxEnd = string.find(instruction, "%u%l*")
|
local function parseInstructionOptions(text, settings)
|
||||||
local count = 1
|
local idx, endIdx = string.find(text, "%b()")
|
||||||
|
if idx == nil or endIdx - idx < 4 then return nil end
|
||||||
|
local optionPairsText = string.sub(text, idx, endIdx)
|
||||||
|
debug("Parsing instruction options: " .. optionPairsText, settings)
|
||||||
|
local options = {}
|
||||||
|
local nextIdx = 1
|
||||||
|
while nextIdx < string.len(optionPairsText) do
|
||||||
|
idx, endIdx = string.find(optionPairsText, "%w+=[%w_-%.]+", nextIdx)
|
||||||
|
if idx == nil then break end
|
||||||
|
local pairText = string.sub(optionPairsText, idx, endIdx)
|
||||||
|
local keyIdx, keyEndIdx = string.find(pairText, "%w+")
|
||||||
|
local key = string.sub(pairText, keyIdx, keyEndIdx)
|
||||||
|
local valueIdx, valueEndIdx = string.find(pairText, "[%w_-%.]+", keyEndIdx + 2)
|
||||||
|
local value = string.sub(pairText, valueIdx, valueEndIdx)
|
||||||
|
options[key] = value
|
||||||
|
debug(" Found option: key = " .. key .. ", value = " .. value, settings)
|
||||||
|
nextIdx = endIdx + 2
|
||||||
|
end
|
||||||
|
return options
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parseRepeatedInstruction(match, settings)
|
||||||
|
debug("Parsing repeated instruction: " .. match, settings)
|
||||||
|
local instruction = {}
|
||||||
|
instruction.type = INSTRUCTION_TYPES.repeated
|
||||||
|
local countIdx, countEndIdx = string.find(match, "%d+")
|
||||||
|
instruction.count = tonumber(string.sub(match, countIdx, countEndIdx))
|
||||||
|
if instruction.count < 0 then
|
||||||
|
error("Repeated instruction cannot have a negative count.")
|
||||||
|
end
|
||||||
|
local innerScriptIdx, innerScriptEndIdx = string.find(match, "%b()", countEndIdx + 1)
|
||||||
|
local innerScript = string.sub(match, innerScriptIdx + 1, innerScriptEndIdx - 1)
|
||||||
|
instruction.instructions = movescript.parse(innerScript, settings)
|
||||||
|
return instruction
|
||||||
|
end
|
||||||
|
|
||||||
|
local function parseInstruction(match, settings)
|
||||||
|
debug("Parsing instruction: " .. match, settings)
|
||||||
|
local instruction = {}
|
||||||
|
instruction.type = INSTRUCTION_TYPES.instruction
|
||||||
|
local countIdx, countEndIdx = string.find(match, "%d+")
|
||||||
|
instruction.count = 1
|
||||||
if countIdx ~= nil then
|
if countIdx ~= nil then
|
||||||
count = tonumber(string.sub(instruction, countIdx, countIdxEnd))
|
instruction.count = tonumber(string.sub(match, countIdx, countEndIdx))
|
||||||
end
|
end
|
||||||
local action = string.sub(instruction, actionIdx, actionIdxEnd)
|
if instruction.count < 1 or instruction.count > t.getFuelLimit() then
|
||||||
if count < 1 or count > t.getFuelLimit() then
|
error("Instruction at index " .. actionIdx .. " has an invalid count of " .. instruction.count .. ". It should be >= 1 and <= " .. t.getFuelLimit())
|
||||||
error("Instruction at index " .. actionIdx .. " has an invalid count of " .. count .. ". It should be >= 1 and <= " .. t.getFuelLimit())
|
|
||||||
end
|
end
|
||||||
if actionMap[action] == nil then
|
local actionIdx, actionEndIdx = string.find(match, "%u%l*")
|
||||||
error("Instruction at index " .. actionIdx .. ", \"" .. action .. "\", does not refer to a valid action.")
|
instruction.action = string.sub(match, actionIdx, actionEndIdx)
|
||||||
|
if actionMap[instruction.action] == nil then
|
||||||
|
error("Instruction at index " .. actionIdx .. ", \"" .. instruction.action .. "\", does not refer to a valid action.")
|
||||||
|
end
|
||||||
|
return instruction
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Parses a movescript script into a series of instruction tables.
|
||||||
|
--[[
|
||||||
|
Movescript Grammar:
|
||||||
|
block: instruction | repeatedInstructions
|
||||||
|
|
||||||
|
repeatedInstructions: count '(' {instruction | repeatedInstructions} ')'
|
||||||
|
regex: %d+%s*%b()
|
||||||
|
instruction: [count] action [actionOptions] <- Not yet implemented.
|
||||||
|
regex: %d*%u%l*
|
||||||
|
count: %d+
|
||||||
|
|
||||||
|
action: %u%l*
|
||||||
|
|
||||||
|
actionOptions: '(' {optionPair ','} ')'
|
||||||
|
regex: %b()
|
||||||
|
|
||||||
|
optionPair: optionKey '=' optionValue
|
||||||
|
|
||||||
|
optionKey: %w+
|
||||||
|
|
||||||
|
optionValue: [%w_-]+
|
||||||
|
|
||||||
|
]]--
|
||||||
|
function movescript.parse(script, settings)
|
||||||
|
local instructions = {}
|
||||||
|
local scriptIdx = 1
|
||||||
|
while scriptIdx <= string.len(script) do
|
||||||
|
local instruction = {}
|
||||||
|
local repeatedMatchStartIdx, repeatedMatchEndIdx = string.find(script, "%d+%s*%b()", scriptIdx)
|
||||||
|
local instructionMatchStartIdx, instructionMatchEndIdx = string.find(script, "%d*%u%l*", scriptIdx)
|
||||||
|
-- Parse the first occurring matched pattern.
|
||||||
|
if repeatedMatchStartIdx ~= nil and (instructionMatchStartIdx == nil or repeatedMatchStartIdx < instructionMatchStartIdx) then
|
||||||
|
-- Parse repeated instructions.
|
||||||
|
local match = string.sub(script, repeatedMatchStartIdx, repeatedMatchEndIdx)
|
||||||
|
table.insert(instructions, parseRepeatedInstruction(match, settings))
|
||||||
|
scriptIdx = repeatedMatchEndIdx + 1
|
||||||
|
elseif instructionMatchStartIdx ~= nil and (repeatedMatchStartIdx == nil or instructionMatchStartIdx < repeatedMatchStartIdx) then
|
||||||
|
-- Parse single instruction.
|
||||||
|
local match = string.sub(script, instructionMatchStartIdx, instructionMatchEndIdx)
|
||||||
|
local instruction = parseInstruction(match, settings)
|
||||||
|
local optionsIdx, optionsEndIdx = string.find(script, "%s*%b()", instructionMatchEndIdx + 1)
|
||||||
|
if optionsIdx ~= nil then
|
||||||
|
-- Check that there's nothing but empty space between the instruction and the options text.
|
||||||
|
if not string.find(string.sub(script, instructionMatchEndIdx + 1, optionsIdx - 1), "%S+") then
|
||||||
|
local optionsText = string.sub(script, optionsIdx, optionsEndIdx)
|
||||||
|
instruction.options = parseInstructionOptions(optionsText, settings)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(instructions, instruction)
|
||||||
|
scriptIdx = instructionMatchEndIdx + 1
|
||||||
|
else
|
||||||
|
error("Invalid script characters found at index " .. scriptIdx)
|
||||||
end
|
end
|
||||||
table.insert(instructions, {action = action, count = count})
|
|
||||||
debug("Parsed instruction: " .. instruction, settings)
|
|
||||||
end
|
end
|
||||||
return instructions
|
return instructions
|
||||||
end
|
end
|
||||||
|
@ -194,7 +294,7 @@ function movescript.run(script, settings)
|
||||||
settings = settings or movescript.defaultSettings
|
settings = settings or movescript.defaultSettings
|
||||||
script = script or ""
|
script = script or ""
|
||||||
debug("Executing script: " .. script, settings)
|
debug("Executing script: " .. script, settings)
|
||||||
local instructions = parseScript(script, settings)
|
local instructions = movescript.parse(script, settings)
|
||||||
for idx, instruction in pairs(instructions) do
|
for idx, instruction in pairs(instructions) do
|
||||||
executeInstruction(instruction, settings)
|
executeInstruction(instruction, settings)
|
||||||
end
|
end
|
||||||
|
@ -208,7 +308,7 @@ function movescript.runFile(filename, settings)
|
||||||
end
|
end
|
||||||
|
|
||||||
function movescript.validate(script, settings)
|
function movescript.validate(script, settings)
|
||||||
return pcall(function () parseScript(script, settings) end)
|
return pcall(function () movescript.parse(script, settings) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
return movescript
|
return movescript
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
-- http://lua-users.org/wiki/TableSerialization
|
||||||
|
function print_r (t, name, indent)
|
||||||
|
local tableList = {}
|
||||||
|
function table_r (t, name, indent, full)
|
||||||
|
local serial=string.len(full) == 0 and name
|
||||||
|
or type(name)~="number" and '["'..tostring(name)..'"]' or '['..name..']'
|
||||||
|
io.write(indent,serial,' = ')
|
||||||
|
if type(t) == "table" then
|
||||||
|
if tableList[t] ~= nil then io.write('{}; -- ',tableList[t],' (self reference)\n')
|
||||||
|
else
|
||||||
|
tableList[t]=full..serial
|
||||||
|
if next(t) then -- Table not empty
|
||||||
|
io.write('{\n')
|
||||||
|
for key,value in pairs(t) do table_r(value,key,indent..'\t',full..serial) end
|
||||||
|
io.write(indent,'};\n')
|
||||||
|
else io.write('{};\n') end
|
||||||
|
end
|
||||||
|
else io.write(type(t)~="number" and type(t)~="boolean" and '"'..tostring(t)..'"'
|
||||||
|
or tostring(t),';\n') end
|
||||||
|
end
|
||||||
|
table_r(t,name or '__unnamed__',indent or '','')
|
||||||
|
end
|
||||||
|
|
||||||
|
local ms = require("src/movescript")
|
||||||
|
print_r(ms.parse("35(2F(safe=false)R 3(L(delay=0.25, file=file.txt)UB))", {debug=true}))
|
Loading…
Reference in New Issue