Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Axis parser hv rework #111

Merged
merged 6 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .tests/parse/axes/axes_keyword.test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
local Vector3 = require("worldeditadditions_core.utils.vector3")

local facing_dirs = dofile("./.tests/parse/axes/include_facing_dirs.lua")

local parse = require("worldeditadditions_core.utils.parse.axes_parser")
local parse_keyword = parse.keyword


describe("parse_keyword", function()

-- Basic tests
it("should work on single axes", function()
local ktype, axis, sign = parse_keyword("x")
assert.are.equals("axis", ktype)
assert.are.same({"x"}, axis)
assert.are.equals(1, sign)
end)

it("should work with axis clumping", function()
local ktype, axis, sign = parse_keyword("zx")
assert.are.equals("axis", ktype)
assert.are.same({"x", "z"}, axis)
assert.are.equals(1, sign)
end)

it("should work with h and v", function()
local ktype, axis, sign = parse_keyword("hv")
assert.are.equals("axis", ktype)
assert.are.same(
{"x", "y", "z", rev={"x", "y", "z"}},
axis)
assert.are.equals(1, sign)
end)

it("should work with h and v in clumping", function()
local ktype, axis, sign = parse_keyword("hyxz")
assert.are.equals("axis", ktype)
assert.are.same(
{"x", "y", "z", rev={"x", "z"}},
axis)
assert.are.equals(1, sign)
end)

it("should work with negatives", function()
local ktype, axis, sign = parse_keyword("-xv")
assert.are.equals("axis", ktype)
assert.are.same({"x", "y", rev={"y"}}, axis)
assert.are.equals(-1, sign)
end)

it("should work with dirs", function()
local ktype, axis, sign = parse_keyword("left")
assert.are.equals("dir", ktype)
assert.are.equals("left", axis)
assert.are.equals(1, sign)
end)

it("should work with negative dirs", function()
local ktype, axis, sign = parse_keyword("-right")
assert.are.equals("dir", ktype)
assert.are.equals("right", axis)
assert.are.equals(-1, sign)
end)

it("should work with mirroring", function()
local ktype, axis, sign = parse_keyword("m")
assert.are.equals("rev", ktype)
assert.are.equals("mirroring", axis)
assert.are.equals(nil, sign)
end)

-- Error tests
it("should return error for bad axis", function()
local ktype, axis, sign = parse_keyword("-axv")
assert.are.equals("err", ktype)
end)

end)
16 changes: 13 additions & 3 deletions .tests/parse/axes/axes_parser.test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,24 @@ describe("parse_axes", function()
"10",
}, facing_dirs.x_pos)
assert.is.truthy(minv)
assert.are.same(Vector3.new(-3, 0, -3), minv)
assert.are.same(Vector3.new(-13, -10, -13), minv)
assert.are.same(Vector3.new(10, 10, 10), maxv)
end)

it("should work for h and v", function()
local minv, maxv = parse_axes({
"h", "3",
"-v", "4",
}, facing_dirs.x_pos)
assert.is.truthy(minv)
assert.are.same(Vector3.new(-3, -4, -3), minv)
assert.are.same(Vector3.new(3, 4, 3), maxv)
end)

it("should work on directions and their abriviations", function()
local minv, maxv = parse_axes({
"l", "3", -- +z
"-r", "-3", -- +z
"-right", "-3", -- +z
"b", "-10", -- -x
}, facing_dirs.x_pos)
assert.is.truthy(minv)
Expand Down Expand Up @@ -155,4 +165,4 @@ describe("parse_axes", function()
assert.are.same("string", type(maxv))
end)

end)
end)
175 changes: 85 additions & 90 deletions worldeditadditions_core/utils/parse/axes_parser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,45 @@ end

local wea_c = worldeditadditions_core or nil
local Vector3
local key_instance
if worldeditadditions_core then
key_instance = dofile(wea_c.modpath.."/utils/parse/key_instance.lua")
Vector3 = dofile(wea_c.modpath.."/utils/vector3.lua")
else
Vector3 = require("worldeditadditions_core.utils.vector3")
key_instance = require("worldeditadditions_core.utils.parse.key_instance")
end


--- Unified Axis Keywords banks
local keywords = {
-- Compass keywords
compass = {
n = "z", north = "z",
["n"] = "z", ["north"] = "z",
["-n"] = "-z", ["-north"] = "-z",
s = "-z", south = "-z",
["s"] = "-z", ["south"] = "-z",
["-s"] = "z", ["-south"] = "z",
e = "x", east = "x",
["e"] = "x", ["east"] = "x",
["-e"] = "-x", ["-east"] = "-x",
w = "-x", west = "-x",
["w"] = "-x", ["west"] = "-x",
["-w"] = "x", ["-west"] = "x",
["u"] = "y", ["up"] = "y",
["d"] = "-y", ["down"] = "-y",
},

-- Direction 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",
["?"] = "front", ["f"] = "front",
["facing"] = "front", ["front"] = "front",
["b"] = "back", ["back"] = "back",
["behind"] = "back", ["rear"] = "back",
["l"] = "left", ["left"] = "left",
["r"] = "right", ["right"] = "right",
},

-- Mirroring keywords
mirroring = {
sym = true, symmetrical = true,
mirror = true, mir = true,
rev = true, reverse = true,
["true"] = true
["sym"] = true, ["symmetrical"] = true,
["mirror"] = true, ["mir"] = true,
["rev"] = true, ["reverse"] = true,
["m"] = true, ["true"] = true
},
}

Expand All @@ -69,17 +66,17 @@ local parse = {}
-- @param: str: String: Axis declaration to parse
-- @returns: Table|Bool: axis | axes | false
function parse.axis(str)
local axes, ret = {"x","y","z"}, {}
for i,v in ipairs(axes) do
if str:match(v) then table.insert(ret,v) end
local ret = { rev={} }
if str:match("[^hvxyz]") then return false end

for _,v in ipairs({{"x","h"},{"y","v"},{"z","h"}}) do
if str:match(v[2]) then table.insert(ret.rev,v[1])
str = str..v[1] end
if str:match(v[1]) then table.insert(ret,v[1]) 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

if #ret.rev < 1 then ret.rev = nil end
return ret
end

--- Processes an number from a string.
Expand All @@ -98,7 +95,7 @@ end
-- @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)
return "err", "Error: \""..tostring(str).."\" is not a string.", 404
elseif keywords.compass[str] then
str = keywords.compass[str]
end
Expand All @@ -110,29 +107,34 @@ function parse.keyword(str)

local axes = parse.axis(str)
if axes then
return key_instance.new("axis", axes, sign)
return "axis", axes, sign
elseif keywords.dir[str] then
return key_instance.new("dir", keywords.dir[str], sign)
return "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)
return "rev", "mirroring"
else
return "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)
-- TODO: Add hv compatability
local ret = Vector3.new()
for i,v in ipairs(axes) do
for _,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_core.player_dir(name)
Expand All @@ -141,80 +143,73 @@ end
-- 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
local expected = 1
local tmp = {axes = {}, num = 0, sign = 1, mirror = false}

--- Processes a number and adds it to the min and max vectors.
-- @param num The number to process.
-- @param axes The axes to apply the number to.
-- @param sign The sign of the number.
local function parseNumber(num, axes, sign)
if axes.rev then parseNumber(num, axes.rev, -sign) end
if num * sign >= 0 then
max = max:add(parse.vectorize(axes, num, sign))
else
min = min:add(parse.vectorize(axes, num, sign))
end
end

for i,v in ipairs(tbl) do
if v:sub(1,1) == "+" then v = v:sub(2) 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 expected == 1 then
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
parseNumber(tmp.num, {"x", "y", "z", rev={"x", "y", "z"}}, tmp.sign)
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
tmp.sign = key_inst.sign
elseif key_inst.type == "dir" then
tmp.axes = {facing[key_inst.entry].axis}
tmp.sign = facing[key_inst.entry].sign * key_inst.sign
elseif key_inst.type == "rev" then
local key_type, key_entry, key_sign = parse.keyword(v)

if key_type == "axis" then
tmp.axes = key_entry
tmp.sign = key_sign
elseif key_type == "dir" then
tmp.axes = {facing[key_entry].axis}
tmp.sign = facing[key_entry].sign * key_sign
elseif key_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
return false, key_entry
end
expected = 2 -- Toggle state machine to expect number (Mode 2)

expected = 2
end

else -- Mode 2 of state machine
-- State machine expects number
else
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)
parseNumber(tmp.num, tmp.axes, tmp.sign)
expected = 1
else
-- Else throw an error and stop everything
return false, "Error: Expected number after \""..tostring(tbl[i-1])..
"\", got \""..tostring(v).."\"."
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
end
end

-- Handle Mirroring
if tmp.mirror and not sum then
max = max:max(min:abs())
min = max:multiply(-1)
end

if sum then return min:add(max)
else return min, max end

if sum then
return min:add(max)
else
return min, max
end
end


return {
keyword = parse.keyword,
keytable = parse.keytable,
}
}
Loading
Loading