Compare commits

...

39 Commits

Author SHA1 Message Date
Andrew Lalis c326b4adad Fixed word matching for modded items. 2023-05-08 08:05:38 +02:00
Andrew Lalis a8242c610e Fixed findSlots 2023-05-08 07:31:23 +02:00
Andrew Lalis 0f3e5d9ee2 Fix bug in itemscript. 2023-05-07 21:24:41 +02:00
Andrew Lalis d27320c135 Organized slot functions. 2023-05-05 09:36:29 +02:00
Andrew Lalis e477ba3b42 Added some more documentation. 2023-05-02 10:28:43 +02:00
Andrew Lalis 812e5a8eaf Fix buildscript. 2023-04-27 13:56:47 +02:00
Andrew Lalis 38e34f9a68 Added execute hooks. 2023-04-27 13:37:45 +02:00
Andrew Lalis a7b8bcf1c8 Made placing safe. 2023-04-27 13:19:37 +02:00
Andrew Lalis febe9b8878 Fixed selection logic. 2023-04-27 12:59:32 +02:00
Andrew Lalis affd55fa81 remove version checking. 2023-04-27 12:46:13 +02:00
Andrew Lalis f545e6a930 Fix again. 2023-04-27 12:42:05 +02:00
Andrew Lalis 66ca84fd9f Fix installer. 2023-04-27 12:35:47 +02:00
Andrew Lalis f9a9398c74 Fixed bugs, improved installer. 2023-04-27 12:30:58 +02:00
Andrew Lalis d3da86c95e added installer script to readme. 2023-04-27 12:17:07 +02:00
Andrew Lalis 3560cca7ca Added ms-installer. 2023-04-27 12:15:25 +02:00
Andrew Lalis f7fd8790f7 Try relative links 2023-04-27 11:14:35 +02:00
Andrew Lalis cb0f6b592f Added scripts listing 2023-04-27 11:11:26 +02:00
Andrew Lalis 690841eca1 Fix typo 2023-04-27 11:07:17 +02:00
Andrew Lalis 0ae239ad53 Add docs workflow to triggers 2023-04-27 11:06:02 +02:00
Andrew Lalis b095535c8e Force node 16 for docs 2023-04-27 11:05:09 +02:00
Andrew Lalis f76c72019a Added proper itemscript 2023-04-27 11:02:12 +02:00
Andrew Lalis cfe8cc8a20 Improved buildscript and itemscript. Still thinking about itemscript filters... 2023-04-26 10:37:17 +02:00
Andrew Lalis a1dd7a850a Added buildscript start 2023-04-26 08:41:55 +02:00
Andrew Lalis 78d49f8abc Moved INSTRUCTION_TYPES declaration to front of file. 2022-12-31 11:09:03 +01:00
Andrew Lalis 9a00985a68 Added options and more documentation. 2022-12-23 13:19:44 +01:00
Andrew Lalis 2178004f65 Merge branch 'main' of github.com:andrewlalis/movescript into main 2022-12-23 11:37:46 +01:00
Andrew Lalis 3fdc6c01f0 Added repeated instructions and action options. 2022-12-23 11:37:40 +01:00
Andrew Lalis f0a0fd5789 Fixed refuel error with nil item. 2022-12-23 08:27:45 +01:00
Andrew Lalis a90f9fbd5b Fixed refuel bug. 2022-12-22 21:54:32 +01:00
Andrew Lalis 821a29175d Updated package lock sources. 2022-12-22 13:03:12 +01:00
Andrew Lalis a3294c3c1a Added copy-to-clipboard, and fixed bug in refuel. 2022-12-22 12:45:46 +01:00
Andrew Lalis f0681b77bd Added basic scripts page. 2022-12-22 12:33:25 +01:00
Andrew Lalis dc1450ddd9 Changed to deploy full scripts to docs pages. 2022-12-22 12:32:29 +01:00
Andrew Lalis e84fc835e7 Added shorter links to scripts. 2022-12-22 12:26:04 +01:00
Andrew Lalis 17a8ae6988 Added validate method, and docs. 2022-12-22 12:20:18 +01:00
Andrew Lalis e80a7fd61f Added permissions to workflow. 2022-12-22 12:12:47 +01:00
Andrew Lalis 11d01cb749 Added the workflow for real. 2022-12-22 12:09:52 +01:00
Andrew Lalis 15f9e1a824 Added better workflow. 2022-12-22 12:09:07 +01:00
Andrew Lalis 11bba93309
Merge pull request #5 from andrewlalis/create-pull-request/patch
Changes by create-pull-request action
2022-12-22 10:49:46 +01:00
21 changed files with 975 additions and 254 deletions

View File

@ -1,13 +1,15 @@
name: Publish Docs name: Deploy Docs to Pages
on: on:
push: push:
branches: [ "main" ] branches: [ "main" ]
paths: paths:
- src/**
- docs/** - docs/**
- .github/workflows/deploy-docs2.yml
workflow_dispatch: workflow_dispatch:
permissions: permissions:
contents: read contents: read
pages: write pages: write
@ -20,10 +22,38 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Copy scripts to Docs public assets
run: |
script_dir=docs/src/.vuepress/public/scripts
rm -rf $script_dir
mkdir $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
lua minify.lua minify src/buildscript.lua > $script_dir/buildscript-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
with:
node-version: 16
- name: Setup Pages - name: Setup Pages
uses: actions/configure-pages@v2 uses: actions/configure-pages@v2
@ -50,3 +80,4 @@ jobs:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v1 uses: actions/deploy-pages@v1

View File

@ -1,39 +0,0 @@
name: Minify Scripts
on:
push:
branches: [ "main" ]
paths:
- src/**
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Lua
uses: ljmf00/setup-lua@v1.0.0
with:
lua-version: 5.3
install-luarocks: true
- name: Minify Scripts
run: |
rm -rf min
mkdir min
lua minify.lua minify src/movescript.lua > min/movescript.lua
lua minify.lua minify src/itemscript.lua > min/itemscript.lua
- name: Clean Up Repository
run: |
rm -rf .lua
rm -rf .luarocks
rm -rf .source
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4

14
docs/package-lock.json generated
View File

@ -8,6 +8,9 @@
"name": "movescript-docs", "name": "movescript-docs",
"version": "0.0.1", "version": "0.0.1",
"license": "MIT", "license": "MIT",
"dependencies": {
"vuepress-plugin-code-copy": "^1.0.6"
},
"devDependencies": { "devDependencies": {
"vuepress": "^1.5.3" "vuepress": "^1.5.3"
} }
@ -14222,6 +14225,12 @@
"object.getownpropertydescriptors": "^2.0.3" "object.getownpropertydescriptors": "^2.0.3"
} }
}, },
"node_modules/vuepress-plugin-code-copy": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/vuepress-plugin-code-copy/-/vuepress-plugin-code-copy-1.0.6.tgz",
"integrity": "sha512-FiqwMtlb4rEsOI56O6sSkekcd3SlESxbkR2IaTIQxsMOMoalKfW5R9WlR1Pjm10v6jmU661Ex8MR11k9IzrNUg==",
"license": "GPL-3.0-or-later"
},
"node_modules/vuepress-plugin-container": { "node_modules/vuepress-plugin-container": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/vuepress-plugin-container/-/vuepress-plugin-container-2.1.5.tgz", "resolved": "https://registry.npmjs.org/vuepress-plugin-container/-/vuepress-plugin-container-2.1.5.tgz",
@ -26370,6 +26379,11 @@
} }
} }
}, },
"vuepress-plugin-code-copy": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/vuepress-plugin-code-copy/-/vuepress-plugin-code-copy-1.0.6.tgz",
"integrity": "sha512-FiqwMtlb4rEsOI56O6sSkekcd3SlESxbkR2IaTIQxsMOMoalKfW5R9WlR1Pjm10v6jmU661Ex8MR11k9IzrNUg=="
},
"vuepress-plugin-container": { "vuepress-plugin-container": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/vuepress-plugin-container/-/vuepress-plugin-container-2.1.5.tgz", "resolved": "https://registry.npmjs.org/vuepress-plugin-container/-/vuepress-plugin-container-2.1.5.tgz",

View File

@ -15,5 +15,8 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"vuepress": "^1.5.3" "vuepress": "^1.5.3"
},
"dependencies": {
"vuepress-plugin-code-copy": "^1.0.6"
} }
} }

View File

@ -81,5 +81,11 @@ module.exports = {
plugins: [ plugins: [
'@vuepress/plugin-back-to-top', '@vuepress/plugin-back-to-top',
'@vuepress/plugin-medium-zoom', '@vuepress/plugin-medium-zoom',
['vuepress-plugin-code-copy', {
backgroundTransition: false,
staticIcon: false,
color: '#de9502',
successText: 'Copied to clipboard.'
}]
] ]
} }

View File

@ -1,15 +0,0 @@
---
sidebar: auto
---
# Config
## foo
- Type: `string`
- Default: `/`
## bar
- Type: `string`
- Default: `/`

View File

@ -8,3 +8,8 @@ Movescript is a set of libraries that remove the prohibitive tedium of writing t
| [Itemscript](./itemscript/) | A collection of helper functions that simplify inventory management. | | [Itemscript](./itemscript/) | A collection of helper functions that simplify inventory management. |
These modules are intended to be used with the Turtle robots provided by the [CC:Tweaked](https://tweaked.cc/) mod for Minecraft. These modules are intended to be used with the Turtle robots provided by the [CC:Tweaked](https://tweaked.cc/) mod for Minecraft.
You can run an installer script to automatically download the latest versions of all of the movescript family of libraries:
```shell
wget run https://andrewlalis.github.io/movescript/scripts/ms-installer.lua
```

View File

@ -7,7 +7,7 @@ The Itemscript module provides a flexible, powerful interface for managing a tur
To install this module, run the following command from your turtle's console: To install this module, run the following command from your turtle's console:
```shell ```shell
wget https://raw.githubusercontent.com/andrewlalis/movescript/main/min/itemscript.lua wget https://andrewlalis.github.io/movescript/scripts/itemscript.lua
``` ```
And then use it in a script: And then use it in a script:

View File

@ -26,6 +26,27 @@ In the case of strings, a **filter expression string** is a string that can be u
The most basic form of an expression string is just an item name, like `"minecraft:dirt"`, or `"create:train_door"`. Most normal items will begin with the `minecraft:` *namespace* prefix. If you don't include such a prefix, and you're not doing a [fuzzy match](#fuzzy-match), itemscript will add `minecraft:` for you. The most basic form of an expression string is just an item name, like `"minecraft:dirt"`, or `"create:train_door"`. Most normal items will begin with the `minecraft:` *namespace* prefix. If you don't include such a prefix, and you're not doing a [fuzzy match](#fuzzy-match), itemscript will add `minecraft:` for you.
### Grammar
Filter expressions can be summarized with a BNF-style grammar description.
```
word = %a[%w%-_:]* A whole or substring of an item's name.
number = %d+
expr = word Matches item stacks whose name matches the given word.
= #word Matches item stacks whose name contains the given word.
= (expr) Grouping of a nested expression.
= !expr Matches item stacks that don't match the given expression.
= expr | expr Matches item stacks that match any of the given expressions (OR).
= expr & expr Matches item stacks that match all of the given expressions (AND).
= expr > number Matches item stacks that match the given expression, and have more than N items.
= expr >= number Matches item stacks that match the given expression, and have more than or equal to N items.
= expr < number Matches item stacks that match the given expression, and have less than N items.
= expr <= number Matches item stacks that match the given expression, and have less than or equal to N items.
= expr = number Matches item stacks that match the given expression, and have exactly N items.
= expr != number Matches item stacks that match the given expression, and do not have exactly N items.
```
For example, we can count the number of stone items in our inventory like this: For example, we can count the number of stone items in our inventory like this:
```lua ```lua

View File

@ -7,7 +7,7 @@ The Movescript module provides a simple interface for executing *movescript sour
To install this module, run the following command from your turtle's console: To install this module, run the following command from your turtle's console:
```shell ```shell
wget https://raw.githubusercontent.com/andrewlalis/movescript/main/min/movescript.lua wget https://andrewlalis.github.io/movescript/scripts/movescript.lua
``` ```
And then use it in a script: And then use it in a script:

View File

@ -7,14 +7,48 @@ local ms = require("movescript")
ms.run("2F") ms.run("2F")
``` ```
## `run(script, settings)` ## `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.
## `executeInstruction(instruction, settings, preExecuteHook, postExecuteHook)`
Executes a single instruction table using the given settings, and if pre- and post-execution hooks are defined, they will be invoked. This is mostly useful for debugging your scripts.
## `run(script, settings, preExecuteHook, postExecuteHook)`
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.
## `runFile(filename, settings)` For example:
```lua
local ms = require("movescript")
ms.run("3F2R3B2LUD", {debug=true})
```
If you provide a non-nil `preExecuteHook` or `postExecuteHook` function, that function will run before or after each instruction in the script, respectively. This could be used to update other systems as to the robot's status, or to make sure items are selected.
## `runFile(filename, settings, preExecuteHook, postExecuteHook)`
Reads content from the given filename and executes it as a script. Just like with `run`, an optional `settings` table can be provided. Reads content from the given filename and executes it as a script. Just like with `run`, an optional `settings` table can be provided.
## `validate(script, settings)`
Validates the given `script`, by parsing its instructions in a wrapped [`pcall`](https://www.lua.org/pil/8.4.html). It returns `true` if the script is valid, or `false` and an error message describing why the script is not valid.
For example:
```lua
local ms = require("movescript")
local status, message = ms.validate("not a valid script.")
```
## `mirror(script)`
Mirrors the given `script`. That is, this swaps any `R` (turn right) instructions with `L` (turn left), which effectively mirrors the robot's motion relative to its original facing direction.
Returns the mirrored script which can then be run.
## `defaultSettings` ## `defaultSettings`
A table containing the default settings for any script executed by the movescript module. A table containing the default [settings](./settings.md) for any script executed by the movescript module.

View File

@ -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,29 +10,53 @@ 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.
| Action | Description | Needs Fuel | | Action | Description | Options |
| ------ | ------------------------------------------------ | ---------- | | ------ | ------------------------------------------------ | ------------------------------------------ |
| `U` | Move up. | ✅ | | `U` | Move up. |
| `D` | Move down. | ✅ | | `D` | Move down. |
| `L` | Turn left. | ❌ | | `L` | Turn left. |
| `R` | Turn right. | ❌ | | `R` | Turn right. |
| `F` | Move forward. | ✅ | | `F` | Move forward. |
| `B` | Move backward. | ✅ | | `B` | Move backward. |
| `P` | Place the selected item in front of the turtle. | ❌ | | `P` | Place the selected item in front of the turtle. | `text: string` - Text to use if placing a sign. |
| `Pu` | Place the selected item above the turtle. | ❌ | | `Pu` | Place the selected item above the turtle. | `text: string` - Text to use if placing a sign. |
| `Pd` | Place the selected item below the turtle. | ❌ | | `Pd` | Place the selected item below the turtle. | `text: string` - Text to use if placing a sign. |
| `A` | Attack in front of the turtle. | ❌ | | `A` | Attack in front of the turtle. | `side: string` - The tool side to use (left or right). |
| `Au` | Attack above the turtle. | ❌ | | `Au` | Attack above the turtle. | `side: string` - The tool side to use (left or right). |
| `Ad` | Attack below the turtle. | ❌ | | `Ad` | Attack below the turtle. | `side: string` - The tool side to use (left or right). |
| `Dg` | Dig in front of the turtle. | `side: string` - The tool side to use (left or right). |
| `Dgu` | Dig above the turtle. | `side: string` - The tool side to use (left or right). |
| `Dgd` | Dig below the turtle. | `side: string` - The tool side to use (left or right). |
| `S` | Suck items from in front of the turtle. | `count: number` - The number of items to suck. |
| `Su` | Suck items from above the turtle. | `count: number` - The number of items to suck. |
| `Sd` | Suck items from below the turtle. | `count: number` - The number of items to suck. |
| `Eqr` | Equip the selected item to the right side. |
| `Eql` | Equip the selected item to the left side. |
| `Sel` | Selects slot 1, or the specified slot. | `slot: number` - The slot to select. |
| `Dr` | Drops the selected items in front of the turtle. | `count: number` - The number of items to drop. |
| `Dru` | Drops the selected items above the turtle. | `count: number` - The number of items to drop. |
| `Drd` | Drops the selected items below the turtle. | `count: number` - The number of items to drop. |
For example, if we want our turtle to go forward 3 times, instead of writing `turtle.forward()` 3 times, we can just do the following: For example, if we want our turtle to go forward 3 times, instead of writing `turtle.forward()` 3 times, we can just do the following:
@ -55,6 +79,6 @@ The following snippets show a few example scripts, along with a description of w
`3F2U1L` - Move forward 3 blocks, then up 2 blocks, and turn left. `3F2U1L` - Move forward 3 blocks, then up 2 blocks, and turn left.
`B2RAd` - Move back 2 blocks, then turn right twice, and then attack downward. `B2RAd` - Move back, then turn right twice, and then attack downward.
`4(U2RD)` - 4 times in a row, move up, twice right, and back down.

View File

@ -0,0 +1,10 @@
# Scripts
This directory contains all scripts published under **Movescript**.
- [movescript](./movescript.lua)
- [movescript-min](./movescript-min.lua)
- [itemscript](./itemscript.lua)
- [itemscript-min](./itemscript-min.lua)
- [buildscript](./buildscript.lua)
- [buildscript-min](./buildscript-min.lua)

View File

@ -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()

View File

@ -1 +0,0 @@
a="0.0.1"local b=turtle local c={}local function d(k,l,m)return k~=nil and((not m and k.name==l)or string.find(k.name,l))end local function e(k)return function(l)return not k(l)end end local function f(k)return function(l)for m,n in pairs(k)do if not n(l)then return false end end return true end end local function g(k)return function(l)for m,n in pairs(k)do if n(l)then return true end end return false end end local function h(k)local l,m=string.find(k,"^[!#]+")local n=false local o=false if l~=nil then for q=l,m do local r=string.sub(k,q,q)if r=="!"then o=true elseif r=="#"then n=true end end k=string.sub(k,m+1,string.len(k))end local p=string.find(k,":")if p==nil and not n then k="minecraft:"..k end return function(q)if q==nil then return false end local r=d(q,k,n)if o then r=not r end return r end end local function i(k)if type(k)=="table"and#k>0 and type(k[1])=="string"then local l={}for m,n in pairs(k)do table.insert(l,h(n))end return g(l)elseif type(k)=="string"then return h(k)elseif type(k)=="function"then return k else error("Unsupported filter type: "..type(k))end end function c.totalCount(k)local l=i(k)local m=0 for n=1,16 do local o=b.getItemDetail(n)if l(o)then m=m+o.count end end return m end function c.select(k)local l=i(k)for m=1,16 do local n=b.getItemDetail(m)if l(n)then b.select(m)return true end end return false end local function j(k,l)for m=1,16 do local n=b.getItemDetail(m)if l(n)then b.select(m)k()end end end function c.dropAll(k)j(b.drop,i(k))end function c.dropAllDown(k)j(b.dropDown,i(k))end function c.dropAllUp(k)j(b.dropUp,i(k))end function c.organize()error("Not yet implemented.")end return c

View File

@ -1 +0,0 @@
a="0.0.1"local d=turtle local e={}e.defaultSettings={debug=false,safe=true,destructive=false,fuels={"minecraft:coal","minecraft:charcoal"}}local function f(t,u)if u and u.debug then print("[MS] "..t)end end function d.digBack(t)d.turnRight()d.turnRight()d.dig(t)d.turnRight()d.turnRight()end function d.detectBack()d.turnRight()d.turnRight()local t=d.detect()d.turnRight()d.turnRight()return t end local function g(t,u,v,w)w=w or e.defaultSettings b=w.safe or e.defaultSettings.safe c=w.destructive or e.defaultSettings.destructive local x=t()if not b then return end while not x do f("Unable to move.",w)if c and v()then f("Detected a block in the way; attempting to remove it.",w)u()end x=t()end end local function h(t)f("Moving up.",t)g(d.up,d.digUp,d.detectUp,t)end local function j(t)f("Moving down.",t)g(d.down,d.digDown,d.detectDown,t)end local function k(t)f("Moving forward.",t)g(d.forward,d.dig,d.detect,t)end local function l(t)f("Moving back.",t)g(d.back,d.digBack,d.detectBack,t)end local function m(t)f("Turning right.",t)d.turnRight()end local function n(t)f("Turning left.",t)d.turnLeft()end local o={["U"]={f=h,needsFuel=true},["D"]={f=j,needsFuel=true},["L"]={f=n,needsFuel=false},["R"]={f=m,needsFuel=false},["F"]={f=k,needsFuel=true},["B"]={f=l,needsFuel=true},["P"]={f=d.place,needsFuel=false},["Pu"]={f=d.placeUp,needsFuel=false},["Pd"]={f=d.placeDown,needsFuel=false},["A"]={f=d.attack,needsFuel=false},["Au"]={f=d.attackUp,needsFuel=false},["Ad"]={f=d.attackDown,needsFuel=false}}local function p(t)f("Refueling...",t)local u=t.fuels or e.defaultSettings.fuels local v=false for w=1,16 do local x=d.getItemDetail(w)if x~=nil then for y,z in pairs(u)do if x.name==z then d.select(i)if d.refuel(x.count)then v=true end break end end end end return v end local function q(t,u)p(u)while d.getFuelLevel<t do print("[MS] Fuel level is too low. Level: "..d.getFuelLevel()..". Required: "..t..". Please add some of the following fuels:")local v=u.fuels or e.defaultSettings.fuels for x,y in pairs(v)do print(" - "..y)end local w=false while not w do os.pullEvent("turtle_inventory")w=p()end end end local function r(t,u)local v=o[t.action]if v then f("Executing action \""..t.action.."\" "..t.count.." times.",u)local w=((u.safe or true)and(v.needsFuel)and(t.count>d.getFuelLevel()))if w then local x=t.count q(x,u)end for x=1,t.count do v.f()end end end local function s(t,u)local v={}for w in string.gfind(t,"%W*(%d*%u%l*)%W*")do local x,y=string.find(w,"%d+")local z,A=string.find(w,"%u%l*")local B=1 if x~=nil then B=tonumber(string.sub(w,x,y))end local C=string.sub(w,z,A)if B<1 or B>d.getFuelLimit()then error("Instruction at index "..z.." has an invalid count of "..B..". It should be >= 1 and <= "..d.getFuelLimit())end if o[C]==nil then error("Instruction at index "..z..", \""..C.."\", does not refer to a valid action.")end table.insert(v,{action=C,count=B})f("Parsed instruction: "..w,u)end return v end function e.run(t,u)u=u or e.defaultSettings t=t or""f("Executing script: "..t,u)local v=s(t,u)for w,x in pairs(v)do r(x,u)end end function e.runFile(t,u)local v=fs.open(t,"r")local w=v.readAll()v.close()e.run(w,u)end return e

119
src/buildscript.lua Normal file
View File

@ -0,0 +1,119 @@
--[[
Buildscript - A unified set of tools that make repetitive building tasks easier
with ComputerCraft robots.
Author: Andrew Lalis <andrewlalisofficial@gmail.com>
This module depends upon both Movescript and Itemscript.
]]--
local movescript = require("movescript")
local itemscript = require("itemscript")
-- The buildscript module.
local buildscript = {}
buildscript.VERSION = "0.0.1"
-- Runs a movescript script, while ensuring that a given item is always selected.
function buildscript.runWithItem(ms_script, filterExpr, settings)
movescript.run(ms_script, settings, function() itemscript.selectOrWait(filterExpr) end)
end
-- Runs a movescript script, while selecting random items that match a filter.
function buildscript.runWithRandomItems(ms_script, filterExpr, settings)
movescript.run(ms_script, settings, function() itemscript.selectRandomOrWait(filterExpr) end)
end
-- Parses a value for an argument specification from a raw value.
local function parseArgValue(argSpec, arg)
if argSpec.required and (not arg or #arg < 1) then
return false, "Missing required value."
end
if argSpec.type == "string" then
return true, arg
elseif argSpec.type == "number" then
local num = tonumber(arg)
if not num and argSpec.required then
return false, "Invalid number."
end
return true, num
elseif argSpec.type == "bool" then
local txt = string.lower(arg)
if txt == "true" or txt == "t" or txt == "yes" or txt == "y" then
return true, true
else
return true, false
end
else
return false, "Unknown type: " .. argSpec.type
end
end
-- Parses arguments according to a specification table, for common building
-- scripts, and returns a table with key-value pairs for each arg.
-- The specification table should be formatted like so:
-- {
-- argName = { type = "string", required = true, idx = 1 },
-- namedArg = { name = "-f", required = true, type = "bool" }
-- }
-- Supported types: string, number, bool
function buildscript.parseArgs(args, spec)
for name, argSpec in pairs(spec) do
if argSpec.idx ~= nil then
if type(argSpec.idx) ~= "number" or argSpec.idx < 1 then
return false, "Invalid argument specification: " .. name .. " does not have a valid numeric index."
end
elseif argSpec.name ~= nil then
if type(argSpec.name) ~= "string" or #argSpec.name < 3 then
return false, "Invalid argument specification: " .. name .. " does not have a valid string name."
end
else
return false, "Invalid argument specification: " .. name .. " doesn't have idx or name."
end
if not argSpec.type then argSpec.type = "string" end
end
local results = {}
-- Iterate over each argument specification, and try and find a value for it.
for name, argSpec in pairs(spec) do
if argSpec.idx then
-- Parse a positional argument.
if argSpec.idx > #args and argSpec.required then
return false, "Missing required positional argument " .. name .. " at index " .. argSpec.idx
end
if argSpec.idx > #args then
results[name] = nil
else
local success, value = parseArgValue(argSpec, args[argSpec.idx])
if not success then
return false, "Failed to parse value for argument " .. name .. ": " .. value
end
results[name] = value
end
else
-- Parse a named argument by iterating over all args until we find one matching the name.
local valueFound = false
for idx, arg in pairs(args) do
if arg == argSpec.name then
if idx >= #args and argSpec.required then
return false, "Missing value for required argument " .. name
end
local success, value = parseArgValue(argSpec, args[idx + 1])
if not success then
return false, "Failed to parse value for argument " .. name .. ": " .. value
end
results[name] = value
valueFound = true
break
end
end
if argSpec.required and not valueFound then
return false, "Missing argument: " .. name
end
end
end
return true, results
end
return buildscript

View File

@ -5,164 +5,362 @@ Author: Andrew Lalis <andrewlalisofficial@gmail.com>
]]-- ]]--
VERSION = "0.0.1"
local t = turtle
-- The itemscript module. Functions defined within this table are exported. -- The itemscript module. Functions defined within this table are exported.
local itemscript = {} local itemscript = {}
itemscript.VERSION = "0.0.1"
-- Determines if an item stack matches the given name. -- Determines if an item stack matches the given name.
-- If fuzzy, then the item name will be matched against the given name. -- If fuzzy, then the item name will be matched against the given name.
local function stackMatches(itemStack, name, fuzzy) local function stackMatches(itemStack, name, fuzzy)
return itemStack ~= nil and if itemStack == nil or itemStack.name == nil then return false end
( if fuzzy then return string.find(itemStack.name, name) ~= nil end
(not fuzzy and itemStack.name == name) or return itemStack.name == name
string.find(itemStack.name, name)
)
end end
local function notFilter(filter) local function splitString(str, sep)
return function(item) if sep == nil then sep = "%s" end
return not filter(item) local result = {}
for s in string.gmatch(str, "([^"..sep.."]+)") do
table.insert(result, s)
end end
return result
end end
local function andFilter(filters) -- Parses a filter expression string and returns a table representing the syntax tree.
return function(item) -- An error is thrown if compilation fails.
for _, filter in pairs(filters) do
if not filter(item) then
return false
end
end
return true
end
end
local function orFilter(filters)
return function(item)
for _, filter in pairs(filters) do
if filter(item) then
return true
end
end
return false
end
end
-- Parses a filter expression string and returns a filter that implements it.
--[[ --[[
Item Filter Expressions: Item Filter Expressions:
A filter expression is a way to define a complex method of matching item A filter expression is a way to define a complex method of matching item
stacks. stacks.
Prepending ! will match any item stack whose name does not match. Grammar:
Prepending # will do a fuzzy match using string.find.
word = %a[%w%-_:]* A whole or substring of an item's name.
number = %d+
expr = word Matches item stacks whose name matches the given word.
= #word Matches item stacks whose name contains the given word.
= (expr)
= !expr Matches item stacks that don't match the given expression.
= expr | expr Matches item stacks that match any of the given expressions (OR).
= expr & expr Matches item stacks that match all of the given expressions (AND).
= expr > %d Matches item stacks that match the given expression, and have more than N items.
= expr >= %d Matches item stacks that match the given expression, and have more than or equal to N items.
= expr < %d Matches item stacks that match the given expression, and have less than N items.
= expr <= %d Matches item stacks that match the given expression, and have less than or equal to N items.
= expr = %d Matches item stacks that match the given expression, and have exactly N items.
= expr != %d Matches item stacks that match the given expression, and do not have exactly N items.
Examples:
"#log > 10" matches any items containing the word "log", that have more than 10 items in the stack.
"10% coal, 90% iron_ore" matches coal 10% of the time, and iron_ore 90% of the time.
]]-- ]]--
local function parseItemFilterExpression(expr) function itemscript.parseFilterExpression(str)
local prefixIdx, prefixIdxEnd = string.find(expr, "^[!#]+") str = str:gsub("^%s*(.-)%s*$", "%1") -- Trim whitespace from the beginning and end of the string.
local fuzzy = false
local negated = false -- Parse group constructs
if prefixIdx ~= nil then local ignoreRange = nil
for i = prefixIdx, prefixIdxEnd do if string.sub(str, 1, 1) == "(" then
local char = string.sub(expr, i, i) local idx1, idx2 = string.find(str, "%b()")
if char == "!" then if idx1 == nil then
negated = true error("Invalid group construct: \"" .. str .. "\".")
elseif char == "#" then end
fuzzy = true -- If the group is the whole expression, parse it. Otherwise, defer parsing to later.
if idx2 == #str then
return itemscript.parseFilterExpression(string.sub(str, idx1 + 1, idx2 - 1))
else
ignoreRange = {idx1, idx2}
end
end
-- Parse logical group operators (OR and AND)
local logicalGroupOperators = {
{ name = "OR", token = "|" },
{ name = "AND", token = "&" }
}
for _, operator in pairs(logicalGroupOperators) do
local idx = string.find(str, operator.token)
if idx ~= nil and (ignoreRange == nil or idx < ignoreRange[1] or idx > ignoreRange[2]) then
return {
type = operator.name,
children = {
itemscript.parseFilterExpression(string.sub(str, 1, idx - 1)),
itemscript.parseFilterExpression(string.sub(str, idx + 1, -1))
}
}
end
end
-- Parse item count arithmetic operators
local arithmeticOperators = {
["LESS_THAN"] = "<",
["LESS_THAN_OR_EQUAL_TO"] = "<=",
["GREATER_THAN"] = ">",
["GREATER_THAN_OR_EQUAL_TO"] = ">=",
["EQUALS"] = "=",
["NOT_EQUALS"] = "!="
}
for typeName, token in pairs(arithmeticOperators) do
local idx = string.find(str, token)
if idx ~= nil and (ignoreRange == nil or idx < ignoreRange[1] or idx > ignoreRange[2]) then
local subExpr = itemscript.parseFilterExpression(string.sub(str, 1, idx - 1))
local numberExprIdx1, numberExprIdx2 = string.find(str, "%d+", idx + 1)
if numberExprIdx1 == nil then
error("Could not find number expression (%d+) in string: \"" .. string.sub(str, idx + 1, -1) .. "\".")
end end
local numberValue = tonumber(string.sub(str, numberExprIdx1, numberExprIdx2))
if numberValue == nil then
error("Could not parse number from string: \"" .. string.sub(str, numberExprIdx1, numberExprIdx2) .. "\".")
end
return {
type = typeName,
expr = subExpr,
value = numberValue
}
end end
expr = string.sub(expr, prefixIdxEnd + 1, string.len(expr))
end end
local namespaceSeparatorIdx = string.find(expr, ":")
if namespaceSeparatorIdx == nil and not fuzzy then -- Parse NOT operator.
expr = "minecraft:" .. expr if string.sub(str, 1, 1) == "!" then
return {
type = "NOT",
expr = itemscript.parseFilterExpression(string.sub(str, 2, -1))
}
end end
return function(item)
if item == nil then return false end -- Parse fuzzy and plain words.
local matches = stackMatches(item, expr, fuzzy) local fuzzy = false
if negated then if string.sub(str, 1, 1) == "#" then
matches = not matches fuzzy = true
str = string.sub(str, 2, -1)
end
local wordIdx1, wordIdx2 = string.find(str, "%a[%w%-_:]*")
if wordIdx1 ~= nil then
local value = string.sub(str, wordIdx1, wordIdx2)
if not fuzzy and string.find(value, ":") == nil then
value = "minecraft:" .. value
end end
return matches return {
type = "WORD",
value = value,
fuzzy = fuzzy
}
end
error("Invalid filter expression syntax: " .. str)
end
-- Compiles a filter function from a filter expression syntax tree.
function itemscript.compileFilter(expr)
if expr.type == "WORD" then
return function(item)
return stackMatches(item, expr.value, expr.fuzzy)
end
end
if expr.type == "NOT" then
local subFilter = itemscript.compileFilter(expr.expr)
return function (item)
return not subFilter(item)
end
end
if expr.type == "LESS_THAN" then
local subFilter = itemscript.compileFilter(expr.expr)
return function (item)
return subFilter(item) and item.count < expr.value
end
end
if expr.type == "GREATER_THAN" then
local subFilter = itemscript.compileFilter(expr.expr)
return function (item)
return subFilter(item) and item.count > expr.value
end
end
if expr.type == "LESS_THAN_OR_EQUAL_TO" then
local subFilter = itemscript.compileFilter(expr.expr)
return function (item)
return subFilter(item) and item.count <= expr.value
end
end
if expr.type == "GREATER_THAN_OR_EQUAL_TO" then
local subFilter = itemscript.compileFilter(expr.expr)
return function (item)
return subFilter(item) and item.count >= expr.value
end
end
if expr.type == "EQUALS" then
local subFilter = itemscript.compileFilter(expr.expr)
return function (item)
return subFilter(item) and item.count == expr.value
end
end
if expr.type == "NOT_EQUALS" then
local subFilter = itemscript.compileFilter(expr.expr)
return function (item)
return subFilter(item) and item.count ~= expr.value
end
end
if expr.type == "AND" then
local subFilters = {}
for _, childExpr in pairs(expr.children) do
table.insert(subFilters, itemscript.compileFilter(childExpr))
end
return function (item)
for _, subFilter in pairs(subFilters) do
if not subFilter(item) then return false end
end
return true
end
end
if expr.type == "OR" then
local subFilters = {}
for _, childExpr in pairs(expr.children) do
table.insert(subFilters, itemscript.compileFilter(childExpr))
end
return function (item)
for _, subFilter in pairs(subFilters) do
if subFilter(item) then return true end
end
return false
end
end
error("Invalid filter expression syntax tree item: " .. expr.type)
end
--[[
Converts an arbitrary value to a filter function that can be applied to item
stacks for filtering operations. The following types are supported:
- strings are parsed and compiled to filter functions.
- functions are assumed to be filter functions that take an item stack as
a single parameter, and return true for a match, and false otherwise.
- tables are assumed to be pre-parsed filter expression syntax trees.
]]--
function itemscript.filterize(value)
if type(value) == "string" then
return itemscript.compileFilter(itemscript.parseFilterExpression(value))
elseif type(value) == "table" then
return itemscript.compileFilter(value)
elseif type(value) == "function" then
return value
else
error("Invalid filterizable value. Expected filter expression string, syntax tree table, or filter function.")
end end
end end
-- Converts an arbitrary variable into a filter; useful for any function that's public, so users can supply any filter. -- Finds the first matching slot for the given filter expression.
-- It converts the following: function itemscript.findSlot(filterExpr)
-- filter function tables directly. local filter = itemscript.filterize(filterExpr)
-- strings and lists of strings are translated into an item names filter. for i = 1, 16 do
-- Functions are added with default fuzzy and whitelist parameters. local item = turtle.getItemDetail(i)
local function convertToFilter(var) if filter(item) then return i end
if type(var) == "table" and #var > 0 and type(var[1]) == "string" then
local filters = {}
for _, expr in pairs(var) do
table.insert(filters, parseItemFilterExpression(expr))
end
return orFilter(filters)
elseif type(var) == "string" then
return parseItemFilterExpression(var)
elseif type(var) == "function" then
return var
else
error("Unsupported filter type: " .. type(var))
end end
return nil
end
-- Gets a list of all inventory slots that match the given filter expression.
function itemscript.findSlots(filterExpr)
local filter = itemscript.filterize(filterExpr)
local slots = {}
for i = 1, 16 do
local item = turtle.getItemDetail(i)
if filter(item) then
table.insert(slots, i)
end
end
return slots
end end
-- Gets the total number of items in the turtle's inventory that match the given expression. -- Gets the total number of items in the turtle's inventory that match the given expression.
function itemscript.totalCount(filterExpr) function itemscript.totalCount(filterExpr)
local filter = convertToFilter(filterExpr)
local count = 0 local count = 0
for i = 1, 16 do for _, slot in pairs(itemscript.findSlots(filterExpr)) do
local item = t.getItemDetail(i) local item = turtle.getItemDetail(slot)
if filter(item) then count = count + item.count
count = count + item.count
end
end end
return count return count
end end
-- Selects a slot containing at least one of the given item type. -- Select the first slot containing a matching item stack for a filter.
-- Returns a boolean indicating whether we could find and select the item. -- Returns a boolean indicating whether we could find and select the item.
function itemscript.select(filterExpr) function itemscript.select(filterExpr)
local filter = convertToFilter(filterExpr) local slot = itemscript.findSlot(filterExpr)
if slot ~= nil then
turtle.select(slot)
return true
end
return false
end
-- Selects a random slot containing a matching item stack.
function itemscript.selectRandom(filterExpr)
local eligibleSlots = itemscript.findSlots(filterExpr)
if #eligibleSlots == 0 then return false end
local slot = eligibleSlots[math.random(1, #eligibleSlots)]
turtle.select(slot)
return true
end
-- Selects a slot containing at least 1 of an item type matching
-- the given filter expression.
function itemscript.selectOrWait(filterExpr)
local filter = itemscript.filterize(filterExpr)
while not itemscript.select(filter) do
print("Couldn't find at least 1 item matching the filter expression: \"" .. filterExpr .. "\". Please add it.")
os.pullEvent("turtle_inventory")
end
end
function itemscript.selectRandomOrWait(filterExpr)
local filter = itemscript.filterize(filterExpr)
while not itemscript.select(filter) do
print("Couldn't find at least 1 item matching the filter expression: \"" .. filterExpr .. "\". Please add it.")
os.pullEvent("turtle_inventory")
end
end
-- Selects the first empty slot, if there is one. Returns true if an empty slot could be selected.
function itemscript.selectEmpty()
for i = 1, 16 do for i = 1, 16 do
local item = t.getItemDetail(i) local item = turtle.getItemDetail(i)
if filter(item) then if item == nil then
t.select(i) turtle.select(i)
return true return true
end end
end end
return false return false
end end
-- Selects the first empty slot, or prompts the user to remove items so that an empty slot can be selected.
function itemscript.selectEmptyOrWait()
while not itemscript.selectEmpty() do
print("Couldn't find an empty slot. Please remove some items.")
os.pullEvent("turtle_inventory")
end
end
-- Helper function to drop items in a flexible way, using a drop function and filtering function. -- Helper function to drop items in a flexible way, using a drop function and filtering function.
local function dropFiltered(dropFunction, filter) local function dropFiltered(dropFunction, filterExpr)
local filter = itemscript.filterize(filterExpr)
for i = 1, 16 do for i = 1, 16 do
local item = t.getItemDetail(i) local item = turtle.getItemDetail(i)
if filter(item) then if filter(item) then
t.select(i) turtle.select(i)
dropFunction() dropFunction()
end end
end end
end end
function itemscript.dropAll(filterExpr) function itemscript.dropAll(filterExpr)
dropFiltered(t.drop, convertToFilter(filterExpr)) dropFiltered(turtle.drop, filterExpr)
end end
function itemscript.dropAllDown(filterExpr) function itemscript.dropAllDown(filterExpr)
dropFiltered(t.dropDown, convertToFilter(filterExpr)) dropFiltered(turtle.dropDown, filterExpr)
end end
function itemscript.dropAllUp(filterExpr) function itemscript.dropAllUp(filterExpr)
dropFiltered(t.dropUp, convertToFilter(filterExpr)) dropFiltered(turtle.dropUp, filterExpr)
end
-- Cleans up the turtle's inventory by compacting all stacks of items.
function itemscript.organize()
error("Not yet implemented.")
end end
return itemscript return itemscript

View File

@ -7,12 +7,15 @@ Movescript provides a simpler, conciser way to program "turtles" (robots), so
that you don't need to get tired of typing "turtle.forward()" over and over. that you don't need to get tired of typing "turtle.forward()" over and over.
]]-- ]]--
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 = {}
movescript.VERSION = "0.0.1"
movescript.defaultSettings = { movescript.defaultSettings = {
debug = false, debug = false,
@ -21,6 +24,11 @@ movescript.defaultSettings = {
fuels = {"minecraft:coal", "minecraft:charcoal"} fuels = {"minecraft:coal", "minecraft:charcoal"}
} }
local INSTRUCTION_TYPES = {
repeated = 1,
instruction = 2
}
local function debug(msg, settings) local function debug(msg, settings)
if settings and settings.debug then if settings and settings.debug then
print("[MS] " .. msg) print("[MS] " .. msg)
@ -62,36 +70,160 @@ local function goDirection(dirFunction, digFunction, detectFunction, settings)
end end
end end
local function goUp(settings) local function goUp(options, settings)
debug("Moving up.", settings) debug("Moving up.", settings)
goDirection(t.up, t.digUp, t.detectUp, settings) goDirection(t.up, t.digUp, t.detectUp, settings)
end end
local function goDown(settings) local function goDown(options, settings)
debug("Moving down.", settings) debug("Moving down.", settings)
goDirection(t.down, t.digDown, t.detectDown, settings) goDirection(t.down, t.digDown, t.detectDown, settings)
end end
local function goForward(settings) local function goForward(options, settings)
debug("Moving forward.", settings) debug("Moving forward.", settings)
goDirection(t.forward, t.dig, t.detect, settings) goDirection(t.forward, t.dig, t.detect, settings)
end end
local function goBack(settings) local function goBack(options, settings)
debug("Moving back.", settings) debug("Moving back.", settings)
goDirection(t.back, t.digBack, t.detectBack, settings) goDirection(t.back, t.digBack, t.detectBack, settings)
end end
local function goRight(settings) local function goRight(options, settings)
debug("Turning right.", settings) debug("Turning right.", settings)
t.turnRight() t.turnRight()
end end
local function goLeft(settings) local function goLeft(options, settings)
debug("Turning left.", settings) debug("Turning left.", settings)
t.turnLeft() t.turnLeft()
end end
local function placeFunction(func, digFunction, detectFunction, settings, options)
settings = settings or movescript.defaultSettings
safe = settings.safe or movescript.defaultSettings.safe
destructive = settings.destructive or movescript.defaultSettings.destructive
local success = func(options.text)
if not safe then return end
while not success do
debug("Unable to place.", settings)
if destructive and detectFunction() then
debug("Detected a block in the way; attempting to remove it.", settings)
digFunction()
end
success = func(options.text)
end
end
local function place(options, settings)
debug("Placing.", settings)
placeFunction(t.place, t.dig, t.detect, settings, options)
end
local function placeUp(options, settings)
debug("Placing up.", settings)
placeFunction(t.placeUp, t.digUp, t.detectUp, settings, options)
end
local function placeDown(options, settings)
debug("Placing down.", settings)
placeFunction(t.placeDown, t.digDown, t.detectDown, settings, options)
end
local function attack(options, settings)
debug("Attacking.", settings)
t.attack(options.side)
end
local function attackUp(options, settings)
debug("Attacking up.", settings)
t.attackUp(options.side)
end
local function attackDown(options, settings)
debug("Attacking down.", settings)
t.attackDown(options.side)
end
local function dig(options, settings)
debug("Digging.", settings)
t.dig(options.side)
end
local function digUp(options, settings)
debug("Digging up.", settings)
t.digUp(options.side)
end
local function digDown(options, settings)
debug("Digging down.", settings)
t.digDown(options.side)
end
local function suck(options, settings)
debug("Sucking.", settings)
local count = nil
if options.count ~= nil then
count = tonumber(options.count)
end
t.suck(count)
end
local function suckUp(options, settings)
debug("Sucking up.", settings)
local count = nil
if options.count ~= nil then
count = tonumber(options.count)
end
t.suckUp(count)
end
local function suckDown(options, settings)
debug("Sucking down.", settings)
local count = nil
if options.count ~= nil then
count = tonumber(options.count)
end
t.suckDown(count)
end
local function selectSlot(options, settings)
local slot = 1
if options.slot ~= nil then
slot = tonumber(options.slot)
end
debug("Selecting slot " .. slot .. ".", settings)
t.select(slot)
end
local function drop(options, settings)
debug("Dropping.", settings)
local count = nil
if options.count ~= nil then
count = tonumber(options.count)
end
t.drop(count)
end
local function dropUp(options, settings)
debug("Dropping up.", settings)
local count = nil
if options.count ~= nil then
count = tonumber(options.count)
end
t.dropUp(count)
end
local function dropDown(options, settings)
debug("Dropping down.", settings)
local count = nil
if options.count ~= nil then
count = tonumber(options.count)
end
t.dropDown(count)
end
local actionMap = { local actionMap = {
["U"] = {f = goUp, needsFuel = true}, ["U"] = {f = goUp, needsFuel = true},
["D"] = {f = goDown, needsFuel = true}, ["D"] = {f = goDown, needsFuel = true},
@ -99,12 +231,24 @@ local actionMap = {
["R"] = {f = goRight, needsFuel = false}, ["R"] = {f = goRight, needsFuel = false},
["F"] = {f = goForward, needsFuel = true}, ["F"] = {f = goForward, needsFuel = true},
["B"] = {f = goBack, needsFuel = true}, ["B"] = {f = goBack, needsFuel = true},
["P"] = {f = t.place, needsFuel = false}, ["P"] = {f = place, needsFuel = false},
["Pu"] = {f = t.placeUp, needsFuel = false}, ["Pu"] = {f = placeUp, needsFuel = false},
["Pd"] = {f = t.placeDown, needsFuel = false}, ["Pd"] = {f = placeDown, needsFuel = false},
["A"] = {f = t.attack, needsFuel = false}, ["A"] = {f = attack, needsFuel = false},
["Au"] = {f = t.attackUp, needsFuel = false}, ["Au"] = {f = attackUp, needsFuel = false},
["Ad"] = {f = t.attackDown, needsFuel = false} ["Ad"] = {f = attackDown, needsFuel = false},
["Dg"] = {f = dig, needsFuel = false},
["Dgu"] = {f = digUp, needsFuel = false},
["Dgd"] = {f = digDown, needsFuel = false},
["S"] = {f = suck, needsFuel = false},
["Su"] = {f = suckUp, needsFuel = false},
["Sd"] = {f = suckDown, needsFuel = false},
["Eqr"] = {f = t.equipRight, needsFuel = false},
["Eql"] = {f = t.equipLeft, needsFuel = false},
["Sel"] = {f = selectSlot, needsFuel = false},
["Dr"] = {f = drop, needsFuel = false},
["Dru"] = {f = dropUp, needsFuel = false},
["Drd"] = {f = dropDown, needsFuel = false}
} }
-- Tries to refuel the turtle from all slots that contain a valid fuel. -- Tries to refuel the turtle from all slots that contain a valid fuel.
@ -118,7 +262,7 @@ local function refuelAll(settings)
if item ~= nil then if item ~= nil then
for _, fuelName in pairs(fuels) do for _, fuelName in pairs(fuels) do
if item.name == fuelName then if item.name == fuelName then
t.select(i) t.select(slot)
if t.refuel(item.count) then refueled = true end if t.refuel(item.count) then refueled = true end
break break
end end
@ -131,7 +275,7 @@ end
-- Blocks until the turtle's fuel level is at least at the required level. -- Blocks until the turtle's fuel level is at least at the required level.
local function refuelToAtLeast(requiredLevel, settings) local function refuelToAtLeast(requiredLevel, settings)
refuelAll(settings) refuelAll(settings)
while t.getFuelLevel < requiredLevel do while t.getFuelLevel() < requiredLevel do
print( print(
"[MS] Fuel level is too low. Level: " .. t.getFuelLevel() .. ". Required: " .. requiredLevel .. "[MS] Fuel level is too low. Level: " .. t.getFuelLevel() .. ". Required: " .. requiredLevel ..
". Please add some of the following fuels:" ". Please add some of the following fuels:"
@ -143,68 +287,187 @@ local function refuelToAtLeast(requiredLevel, settings)
local fuelUpdated = false local fuelUpdated = false
while not fuelUpdated do while not fuelUpdated do
os.pullEvent("turtle_inventory") os.pullEvent("turtle_inventory")
fuelUpdated = refuelAll() fuelUpdated = refuelAll(settings)
end end
end end
end end
-- Executes a single instruction. An instruction is a table with an "action" -- Executes a single instruction. An instruction is a table with an "action"
-- and some attributes, such as if it needs fuel or not. -- and some attributes, such as if it needs fuel or not.
local function executeInstruction(instruction, settings) function movescript.executeInstruction(instruction, settings, preExecuteHook, postExecuteHook)
local action = actionMap[instruction.action] if settings == nil then settings = movescript.defaultSettings end
if action then if instruction.type == INSTRUCTION_TYPES.repeated then
debug("Executing action \"" .. instruction.action .. "\" " .. instruction.count .. " times.", settings) debug("Executing repeated instruction " .. instruction.count .. " times.", settings)
local shouldRefuel = ( for i = 1, instruction.count do
(settings.safe or true) and for _, nestedInstruction in pairs(instruction.instructions) do
(action.needsFuel) and movescript.executeInstruction(nestedInstruction, settings, preExecuteHook, postExecuteHook)
(instruction.count > t.getFuelLevel()) end
) end
if shouldRefuel then elseif instruction.type == INSTRUCTION_TYPES.instruction then
local fuelRequired = instruction.count local action = actionMap[instruction.action]
refuelToAtLeast(fuelRequired, settings) if action then
debug("Executing action \"" .. instruction.action .. "\" " .. instruction.count .. " times.", settings)
local shouldRefuel = (
((settings ~= nil and settings.safe) or true) and
(action.needsFuel) and
(instruction.count > t.getFuelLevel())
)
if shouldRefuel then
local fuelRequired = instruction.count
refuelToAtLeast(fuelRequired, settings)
end
for i = 1, instruction.count do
if preExecuteHook ~= nil then preExecuteHook() end
action.f(instruction.options, settings)
if postExecuteHook ~= nil then postExecuteHook() end
end
end end
for i = 1, instruction.count do action.f() end
end end
end end
local function parseInstructionOptions(text, settings)
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
instruction.count = tonumber(string.sub(match, countIdx, countEndIdx))
end
if instruction.count < 1 or instruction.count > t.getFuelLimit() then
error("Instruction at index " .. actionIdx .. " has an invalid count of " .. instruction.count .. ". It should be >= 1 and <= " .. t.getFuelLimit())
end
local actionIdx, actionEndIdx = string.find(match, "%u%l*")
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. -- Parses a movescript script into a series of instruction tables.
local function parseScript(script, settings) --[[
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 instructions = {}
for instruction in string.gfind(script, "%W*(%d*%u%l*)%W*") do local scriptIdx = 1
local countIdx, countIdxEnd = string.find(instruction, "%d+") while scriptIdx <= string.len(script) do
local actionIdx, actionIdxEnd = string.find(instruction, "%u%l*") local instruction = {}
local count = 1 local repeatedMatchStartIdx, repeatedMatchEndIdx = string.find(script, "%d+%s*%b()", scriptIdx)
if countIdx ~= nil then local instructionMatchStartIdx, instructionMatchEndIdx = string.find(script, "%d*%u%l*", scriptIdx)
count = tonumber(string.sub(instruction, countIdx, countIdxEnd)) -- 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
if instruction.options == nil then instruction.options = {} end
table.insert(instructions, instruction)
scriptIdx = instructionMatchEndIdx + 1
else
error("Invalid script characters found at index " .. scriptIdx)
end end
local action = string.sub(instruction, actionIdx, actionIdxEnd)
if count < 1 or count > t.getFuelLimit() then
error("Instruction at index " .. actionIdx .. " has an invalid count of " .. count .. ". It should be >= 1 and <= " .. t.getFuelLimit())
end
if actionMap[action] == nil then
error("Instruction at index " .. actionIdx .. ", \"" .. action .. "\", does not refer to a valid action.")
end
table.insert(instructions, {action = action, count = count})
debug("Parsed instruction: " .. instruction, settings)
end end
return instructions return instructions
end end
function movescript.run(script, settings) function movescript.run(script, settings, preExecuteHook, postExecuteHook)
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) movescript.executeInstruction(instruction, settings, preExecuteHook, postExecuteHook)
end end
end end
function movescript.runFile(filename, settings) function movescript.runFile(filename, settings, preExecuteHook, postExecuteHook)
local f = fs.open(filename, "r") local f = fs.open(filename, "r")
local script = f.readAll() local script = f.readAll()
f.close() f.close()
movescript.run(script, settings) movescript.run(script, settings, preExecuteHook, postExecuteHook)
end
function movescript.validate(script, settings)
return pcall(function () movescript.parse(script, settings) end)
end
-- "Mirrors" a movescript; that is, swaps any "turn right" instructions for "turn left", and vice versa.
-- Note that it does not mirror "equip right" and "equip left" instructions.
function movescript.mirror(script)
local template = string.gsub(script, "L", "__LEFT__")
template = string.gsub(template, "R", "__RIGHT__")
local result = string.gsub(template, "__LEFT__", "R")
return string.gsub(result, "__RIGHT__", "L")
end end
return movescript return movescript

27
src/ms-installer.lua Normal file
View File

@ -0,0 +1,27 @@
--[[
An installation script that manages installing all movescript libraries easily.
]]--
local libs = {
"movescript.lua",
"itemscript.lua",
"buildscript.lua"
}
local BASE_URL = "https://andrewlalis.github.io/movescript/scripts/"
print("Running Movescript installer")
print("----------------------------")
for _, lib in pairs(libs) do
if fs.exists(lib) then
fs.delete(lib)
print("Deleted " .. lib)
end
local success = shell.run("wget", BASE_URL .. lib)
if not success then
error("Failed to install " .. lib)
end
print("Downloaded " .. lib)
end
print("----------------------------")
print("Done!")

46
tester.lua Normal file
View File

@ -0,0 +1,46 @@
-- 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}))
-- local bs = require("src/buildscript")
-- local args = {...}
-- local spec = {
-- num = { type = "number", required = true, idx = 1 },
-- name = { name = "name", type = "bool", required = true }
-- }
-- local success, result = bs.parseArgs(args, spec)
-- print(success)
-- print_r(result)
local is = require("src/itemscript")
local t = is.parseFilterExpression("!log")
print_r(t, "filter_expression_syntax_tree", " ")
local filter = is.compileFilter(t)
local item = {
name = "minecraft:oak_log",
count = 54
}
local matches = filter(item)
print(matches)