diff --git a/.tests/parse/axes/axes_keyword.test.lua b/.tests/parse/axes/axes_keyword.test.lua new file mode 100644 index 0000000..b6e137c --- /dev/null +++ b/.tests/parse/axes/axes_keyword.test.lua @@ -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) diff --git a/.tests/parse/axes/axes_parser.test.lua b/.tests/parse/axes/axes_parser.test.lua index 51f8946..00546cd 100644 --- a/.tests/parse/axes/axes_parser.test.lua +++ b/.tests/parse/axes/axes_parser.test.lua @@ -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) @@ -155,4 +165,4 @@ describe("parse_axes", function() assert.are.same("string", type(maxv)) end) -end) +end) \ No newline at end of file diff --git a/worldeditadditions_core/utils/parse/axes_parser.lua b/worldeditadditions_core/utils/parse/axes_parser.lua index 1f47ef1..678a050 100644 --- a/worldeditadditions_core/utils/parse/axes_parser.lua +++ b/worldeditadditions_core/utils/parse/axes_parser.lua @@ -16,13 +16,10 @@ 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 @@ -30,34 +27,34 @@ end 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 }, } @@ -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. @@ -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 @@ -110,15 +107,17 @@ 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 @@ -126,13 +125,16 @@ end -- @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) @@ -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} - for i,v in ipairs(tbl) do - if v:sub(1,1) == "+" then v = v:sub(2) end + --- 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 + 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, -} +} \ No newline at end of file diff --git a/worldeditadditions_core/utils/parse/key_instance.lua b/worldeditadditions_core/utils/parse/key_instance.lua deleted file mode 100644 index 727fa43..0000000 --- a/worldeditadditions_core/utils/parse/key_instance.lua +++ /dev/null @@ -1,91 +0,0 @@ ---- A container for transmitting (axis table, sign) or (dir, sign) pairs --- and other data within parsing functions. --- @internal --- @class worldeditadditions_core.parse.key_instance -local key_instance = {} -key_instance.__index = key_instance -key_instance.__name = "Key Instance" - --- Allowed values for "type" field -local types = { - err = true, rev = true, - axis = true, dir = true, - replace = { - error = "err", - axes = "axis", - }, -} - --- Simple function for putting stuff in quotes -local function enquote(take) - if type(take) == "string" then - return '"'..take..'"' - else return tostring(take) end -end - ---- Creates a new Key Instance. --- This is a table with a "type" string, an entry string or table --- and an optional signed integer (or code number in the case of errors) --- @param: type: String: Key type (axis, dir(ection), rev (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 types.replace[type] then - type = types.replace[type] - elseif not types[type] then - return key_instance.new("err", - "Key Instance internal error: Invalid type "..enquote(type)..".", - 500) - 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 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