axes_parser implemented (not added to init)

This commit is contained in:
VorTechnix 2022-05-20 19:27:47 -07:00
parent bcfe39cd21
commit a6b4d047b8
2 changed files with 273 additions and 0 deletions

@ -0,0 +1,204 @@
-- ██████ █████ ██████ ███████ ███████ █████ ██ ██ ███████ ███████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██████ ███████ ██████ ███████ █████ ███████ ███ █████ ███████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██ ██ ██ ██ ██ ███████ ███████ ██ ██ ██ ██ ███████ ███████
-- Error codes: https://kinsta.com/blog/http-status-codes/
--- FOR TESTING ---
local function unpack(tbl)
if table.unpack then
return table.unpack(tbl)
else return unpack(tbl) end
end
---------------
local Vector3
if worldeditadditions then
local wea = worldeditadditions
Vector3 = dofile(wea.modpath.."/utils/vector3.lua")
else
Vector3 = require("worldeditadditions.utils.vector3")
end
local key_instance
if worldeditadditions then
local wea = worldeditadditions
key_instance = dofile(wea.modpath.."/utils/parse/key_instance.lua")
else
key_instance = require("worldeditadditions.key_instance")
end
--- Unified Axis Keywords banks
local keywords = {}
-- Direction keywords
keywords.dir = {
["?"] = "front", f = "front",
facing = "front", front = "front",
b = "back", back = "back",
behind = "back", rear = "back",
l = "left", left = "left",
r = "right", right = "right",
u = "up", up = "up",
d = "down", down = "down",
}
-- Mirroring keywords
keywords.mirroring = {
sym = true, symmetrical = true,
mirror = true, mir = true,
rev = true, reverse = true,
["true"] = true
}
--- Initialize parser function container
local parse = {}
--- Processes an axis declaration into ordered xyz format. (Supports axis clumping)
-- For example, "zzy" would become "yz"
-- @param: str: String: Axis declaration to parse
-- @returns: Table|Bool: axis | axes | false
function parse.axes(str)
local axes, ret = {"x","y","z"}, {}
for i,v in ipairs(axes) do
if str:match(v) then table.insert(ret,v) end
end
if #ret > 0 and str:match("^[xyz]+$") then
return ret
elseif str == "h" then
return {"x", "z"}
elseif str == "v" then
return {"y"}
else return false end
end
--- Processes an number from a string.
-- @param: str: String: string to parse
-- @returns: Number|Bool: processed number | false
function parse.num(str)
str = tostring(str) -- To prevent meltdown if str isn't a string
local num = str:match("^-?%d+%.?%d*$")
if num then
return tonumber(num)
else return false end
end
--- Checks if a string is a valid Unified Axis Keyword. (Supports axis clumping)
-- @param: str: String: Keyword instance to parse
-- @returns: Key Instance: returns keyword type, processed keyword content and signed number (or nil)
function parse.keyword(str)
if type(str) ~= "string" then
return key_instance.new("err", "Error: \""..tostring(str).."\" is not a string.", 404)
end
local sign = 1
if str:sub(1,1) == "-" then
sign = -1
str = str:sub(2)
end
local axes = parse.axes(str)
if axes then
return key_instance.new("axis", axes, sign)
elseif keywords.dir[str] then
return key_instance.new("dir", keywords.dir[str], sign)
elseif keywords.mirroring[str] then
return key_instance.new("rev", "mirroring")
else return key_instance.new("err", "Error: \""..str.."\" is not a valid axis, direction or keyword.", 422)
end
end
--- Creates a vector with a length of (@param: value * @param: sign)
-- on each axis in @param: axes.
-- @param: axes: Table: List of axes to set
-- @param: value: Number: length of to set axes
-- @param: sign: Number: sign multiplier for axes
-- @returns: Vector3: processed vector
function parse.vectorize(axes,value,sign)
local ret = Vector3.new()
for i,v in ipairs(axes) do
ret[v] = value * sign
end
return ret
end
--- Converts Unified Axis Keyword table into Vector3 instances.
-- @param: tbl: Table: Keyword table to parse
-- @param: facing: Table: Output from worldeditadditions.player_dir(name)
-- @param: sum: Bool: Return a single vector by summing the 2 output vectors together
-- @returns: Vector3, [Vector3]: returns min, max Vector3s or sum Vector3 (if @param: sum ~= nil)
-- if error: @returns: false, String: error message
function parse.keytable(tbl, facing, sum)
local min, max = Vector3.new(), Vector3.new()
local expected, tmp = 1, {axes = {}, num = 0, sign = 1, mirror = false}
function tmp:reset() self.axis, self.sign = "", 1 end
for i,v in ipairs(tbl) do
if v:sub(1,1) == "+" then v = v:sub(2) end
tmp.num = parse.num(v)
if expected == 1 then -- Mode 1 of state machine
-- State machine expects string
if tmp.num then
-- If this is a number treat as all axes and add to appropriate vector
if tmp.num * tmp.sign >= 0 then
max = max:add(parse.vectorize({"x","y","z"}, tmp.num, tmp.sign))
else
min = min:add(parse.vectorize({"x","y","z"}, tmp.num, tmp.sign))
end
-- We are still looking for axes so the state machine should remain
-- in Mode 1 for the next iteration
else
-- Else parse.keyword
local key_inst = parse.keyword(v)
-- Stop if error and return message
if key_inst:is_error() then return false, key_inst.entry end
-- Check key type and process further
if key_inst.type == "axis" then
tmp.axes = key_inst.entry
elseif key_inst.type == "dir" then
tmp.axes = {facing[key_inst.entry].axis}
tmp.sign = facing[key_inst.entry].sign
elseif key_inst.type == "rev" then
tmp.mirror = true
else
-- If key type is error or unknown throw error and stop
if key_inst.type == "err" then
return false, key_inst.entry
else
return false, "Error: Unknown Key Instance type \""..
tostring(key_inst.type).."\". Contact the devs!"
end
end
expected = 2 -- Toggle state machine to expect number (Mode 2)
end
else -- Mode 2 of state machine
-- State machine expects number
if tmp.num then
-- If this is a number process num and add to appropriate vector
if tmp.num * tmp.sign >= 0 then
max = max:add(parse.vectorize(tmp.axes, tmp.num, tmp.sign))
else
min = min:add(parse.vectorize(tmp.axes, tmp.num, tmp.sign))
end
expected = 1 -- Toggle state machine to expect string (Mode 1)
else
-- Else throw an error and stop everything
return false, "Error: Expected number after \""..tostring(tbl[i-1])..
"\", got \""..tostring(v).."\"."
end
end -- End of state machine
end -- End of main for loop
-- Handle Mirroring
if tmp.mirror then
max = max:max(min:abs())
min = max:multiply(-1)
end
if sum then return min:add(max)
else return min, max end
end
return parse

@ -0,0 +1,69 @@
--- A container for transmitting axis, sign pairs within parsing functions.
-- @class
local key_instance = {}
key_instance.__index = key_instance
key_instance.__name = "Key Instance"
--- Creates a new Key Instance.
-- @param: type: String: Key type (axis, direction, mirroring or error).
-- @param: entry: String: The main content of the key.
-- @param: sign: Int: The signed multiplier of the key (if any).
-- @return: Key Instance: The new Key Instance.
function key_instance.new(type,entry,sign)
if type == "error" then type = "err" end
local tbl = {type = type, entry = entry}
if sign and sign ~= 0 then
if type == "err" then tbl.code = sign
else tbl.sign = sign end
end
return setmetatable(tbl, key_instance)
end
--- Checks if Key Instance "entry" field is a table.
-- @param: tbl: Key Instance: The Key Instance to check.
-- @return: Bool: Returns true if Key Instance has a non 0 sign value.
function key_instance.entry_table(a)
if type(a.entry) == "table" then
return true
else return false end
end
--- Checks if Key Instance has a signed multiplier.
-- @param: tbl: Key Instance: The Key Instance to check.
-- @return: Bool: Returns true if Key Instance has a non 0 sign value.
function key_instance.has_sign(a)
if not a.sign or a.sign == 0 then
return false
else return true end
end
--- Checks if Key Instance is an error.
-- @param: tbl: Key Instance: The Key Instance to check.
-- @return: Bool: Returns true if Key Instance is an error.
function key_instance.is_error(a)
if a.type == "err" then return true
else return false end
end
function key_instance.__tostring(a)
local function enquote(take)
if type(take) == "string" then
return '"'..take..'"'
else return tostring(take) end
end
local ret = "{type = "..enquote(a.type)..", entry = "
if type(a.entry) == "table" and #a.entry <= 3 then
ret = ret.."{"
for _i,v in ipairs(a.entry) do
ret = ret..enquote(v)..", "
end
ret = ret:sub(1,-3).."}"
else ret = ret..enquote(a.entry) end
if a:is_error() and a.code then ret = ret..", code = "..a.code.."}"
elseif a:has_sign() then ret = ret..", sign = "..a.sign.."}"
else ret = ret.."}" end
return ret
end
return key_instance