Added proper itemscript
This commit is contained in:
parent
cfe8cc8a20
commit
f76c72019a
|
@ -5,120 +5,258 @@ Author: Andrew Lalis <andrewlalisofficial@gmail.com>
|
|||
|
||||
|
||||
]]--
|
||||
VERSION = "0.0.1"
|
||||
|
||||
local t = turtle
|
||||
|
||||
-- The itemscript module. Functions defined within this table are exported.
|
||||
local itemscript = {}
|
||||
itemscript.VERSION = "0.0.1"
|
||||
|
||||
-- Determines if an item stack matches the given name.
|
||||
-- If fuzzy, then the item name will be matched against the given name.
|
||||
local function stackMatches(itemStack, name, fuzzy)
|
||||
return itemStack ~= nil and
|
||||
(
|
||||
(not fuzzy and itemStack.name == name) or
|
||||
string.find(itemStack.name, name)
|
||||
)
|
||||
if itemStack == nil or itemStack.name == nil then return false end
|
||||
if fuzzy then return string.find(itemStack.name, name) ~= nil end
|
||||
return itemStack.name == name
|
||||
end
|
||||
|
||||
local function notFilter(filter)
|
||||
return function(item)
|
||||
return not filter(item)
|
||||
local function splitString(str, sep)
|
||||
if sep == nil then sep = "%s" end
|
||||
local result = {}
|
||||
for s in string.gmatch(str, "([^"..sep.."]+)") do
|
||||
table.insert(result, s)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function andFilter(filters)
|
||||
return function(item)
|
||||
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.
|
||||
-- Parses a filter expression string and returns a table representing the syntax tree.
|
||||
-- An error is thrown if compilation fails.
|
||||
--[[
|
||||
Item Filter Expressions:
|
||||
|
||||
A filter expression is a way to define a complex method of matching item
|
||||
stacks.
|
||||
|
||||
Prepending ! will match any item stack whose name does not match.
|
||||
Prepending # will do a fuzzy match using string.find.
|
||||
Grammar:
|
||||
|
||||
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:
|
||||
|
||||
"coal" matches only "minecraft:coal" items
|
||||
"!#wood" matches all items except any that contain the phrase "wood"
|
||||
"#iron" matches all items that contain the phrase "iron"
|
||||
"#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)
|
||||
local prefixIdx, prefixIdxEnd = string.find(expr, "^[!#]+")
|
||||
function itemscript.parseFilterExpression(str)
|
||||
str = str:gsub("^%s*(.-)%s*$", "%1") -- Trim whitespace from the beginning and end of the string.
|
||||
print("Parsing expr: " .. str)
|
||||
|
||||
-- Parse group constructs
|
||||
local ignoreRange = nil
|
||||
if string.sub(str, 1, 1) == "(" then
|
||||
local idx1, idx2 = string.find(str, "%b()")
|
||||
if idx1 == nil then
|
||||
error("Invalid group construct: \"" .. str .. "\".")
|
||||
end
|
||||
-- If the group is the whole expression, parse it. Otherwise, defer parsing to later.
|
||||
if idx2 == #str then
|
||||
print("Found GROUP")
|
||||
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
|
||||
print("Found " .. operator.name)
|
||||
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
|
||||
print("Found " .. typeName)
|
||||
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
|
||||
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
|
||||
|
||||
-- Parse NOT operator.
|
||||
if string.sub(str, 1, 1) == "!" then
|
||||
print("Found NOT")
|
||||
return {
|
||||
type = "NOT",
|
||||
expr = itemscript.parseFilterExpression(string.sub(str, 2, -1))
|
||||
}
|
||||
end
|
||||
|
||||
-- Parse fuzzy and plain words.
|
||||
local fuzzy = false
|
||||
local negated = false
|
||||
if prefixIdx ~= nil then
|
||||
for i = prefixIdx, prefixIdxEnd do
|
||||
local char = string.sub(expr, i, i)
|
||||
if char == "!" then
|
||||
negated = true
|
||||
elseif char == "#" then
|
||||
if string.sub(str, 1, 1) == "#" then
|
||||
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
|
||||
expr = string.sub(expr, prefixIdxEnd + 1, string.len(expr))
|
||||
end
|
||||
local namespaceSeparatorIdx = string.find(expr, ":")
|
||||
if namespaceSeparatorIdx == nil and not fuzzy then
|
||||
expr = "minecraft:" .. expr
|
||||
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)
|
||||
if item == nil then return false end
|
||||
local matches = stackMatches(item, expr, fuzzy)
|
||||
if negated then
|
||||
matches = not matches
|
||||
return stackMatches(item, expr.value, expr.fuzzy)
|
||||
end
|
||||
return matches
|
||||
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
|
||||
|
||||
-- Converts an arbitrary variable into a filter; useful for any function that's public, so users can supply any filter.
|
||||
-- It converts the following:
|
||||
-- filter function tables directly.
|
||||
-- strings and lists of strings are translated into an item names filter.
|
||||
-- Functions are added with default fuzzy and whitelist parameters.
|
||||
local function convertToFilter(var)
|
||||
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
|
||||
|
||||
-- Gets the total number of items in the turtle's inventory that match the given expression.
|
||||
function itemscript.totalCount(filterExpr)
|
||||
local filter = convertToFilter(filterExpr)
|
||||
local filter = itemscript.filterize(filterExpr)
|
||||
local count = 0
|
||||
for i = 1, 16 do
|
||||
local item = t.getItemDetail(i)
|
||||
|
@ -129,56 +267,81 @@ function itemscript.totalCount(filterExpr)
|
|||
return count
|
||||
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.
|
||||
function itemscript.select(filterExpr)
|
||||
local filter = convertToFilter(filterExpr)
|
||||
local filter = itemscript.filterize(filterExpr)
|
||||
for i = 1, 16 do
|
||||
local item = t.getItemDetail(i)
|
||||
local item = turtle.getItemDetail(i)
|
||||
if filter(item) then
|
||||
t.select(i)
|
||||
turtle.select(i)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Selects a random slot containing a matching item stack.
|
||||
function itemscript.selectRandom(filterExpr)
|
||||
local filter = itemscript.filterize(filterExpr)
|
||||
local eligibleSlots = {}
|
||||
for i = 1, 16 do
|
||||
local item = turtle.getItemDetail(i)
|
||||
if filter(item) then
|
||||
table.insert(eligibleSlots, i)
|
||||
end
|
||||
end
|
||||
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 minCount (or 1) of an item type matching
|
||||
-- the given filter expression.
|
||||
function itemscript.selectOrWait(filterExpr, minCount)
|
||||
minCount = minCount or 1
|
||||
while itemscript.totalCount(filterExpr) < minCount do
|
||||
local filter = itemscript.filterize(filterExpr)
|
||||
while itemscript.totalCount(filter) < minCount do
|
||||
print("Couldn't find at least " .. minCount .. " item(s) matching the filter expression: \"" .. filterExpr .. "\". Please add it.")
|
||||
os.pullEvent("turtle_inventory")
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper function to drop items in a flexible way, using a drop function and filtering function.
|
||||
local function dropFiltered(dropFunction, filter)
|
||||
-- Selects an empty slot.
|
||||
function itemscript.selectEmpty()
|
||||
for i = 1, 16 do
|
||||
local item = t.getItemDetail(i)
|
||||
local item = turtle.getItemDetail(i)
|
||||
if item == nil then
|
||||
turtle.select(i)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Helper function to drop items in a flexible way, using a drop function and filtering function.
|
||||
local function dropFiltered(dropFunction, filterExpr)
|
||||
local filter = itemscript.filterize(filterExpr)
|
||||
for i = 1, 16 do
|
||||
local item = turtle.getItemDetail(i)
|
||||
if filter(item) then
|
||||
t.select(i)
|
||||
turtle.select(i)
|
||||
dropFunction()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function itemscript.dropAll(filterExpr)
|
||||
dropFiltered(t.drop, convertToFilter(filterExpr))
|
||||
dropFiltered(turtle.drop, filterExpr)
|
||||
end
|
||||
|
||||
function itemscript.dropAllDown(filterExpr)
|
||||
dropFiltered(t.dropDown, convertToFilter(filterExpr))
|
||||
dropFiltered(turtle.dropDown, filterExpr)
|
||||
end
|
||||
|
||||
function itemscript.dropAllUp(filterExpr)
|
||||
dropFiltered(t.dropUp, convertToFilter(filterExpr))
|
||||
end
|
||||
|
||||
-- Cleans up the turtle's inventory by compacting all stacks of items.
|
||||
function itemscript.organize()
|
||||
error("Not yet implemented.")
|
||||
dropFiltered(turtle.dropUp, filterExpr)
|
||||
end
|
||||
|
||||
return itemscript
|
|
@ -7,8 +7,6 @@ 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.
|
||||
|
||||
]]--
|
||||
VERSION = "0.0.1"
|
||||
|
||||
local t = turtle
|
||||
-- For testing purposes, if the turtle API is not present, we inject our own.
|
||||
if not t then t = {
|
||||
|
@ -17,6 +15,7 @@ if not t then t = {
|
|||
|
||||
-- The movescript module. Functions defined within this table are exported.
|
||||
local movescript = {}
|
||||
movescript.VERSION = "0.0.1"
|
||||
|
||||
movescript.defaultSettings = {
|
||||
debug = false,
|
||||
|
|
27
tester.lua
27
tester.lua
|
@ -24,12 +24,23 @@ function print_r (t, name, indent)
|
|||
-- 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 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 success, result = bs.parseArgs(args, spec)
|
||||
print(success)
|
||||
print_r(result)
|
||||
local matches = filter(item)
|
||||
print(matches)
|
||||
|
|
Loading…
Reference in New Issue