From 10a27a7a250eea160a8c172e63d8153c792bb53b Mon Sep 17 00:00:00 2001 From: SquidDev Date: Sat, 18 Apr 2020 14:38:20 +0100 Subject: [PATCH] Add utility to check table keys expect.field acts very similarly to expect.expect, though checks a specific table key rather than a function argument. --- .../lua/rom/modules/main/cc/expect.lua | 61 +++++++++++++----- .../test-rom/spec/modules/cc/expect_spec.lua | 62 +++++++++++++------ 2 files changed, 87 insertions(+), 36 deletions(-) diff --git a/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/expect.lua b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/expect.lua index 2f4c648b6..24ca66d56 100644 --- a/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/expect.lua +++ b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/expect.lua @@ -5,28 +5,29 @@ local native_select, native_type = select, type ---- Expect an argument to have a specific type. --- --- @tparam number index The 1-based argument index. --- @param value The argument's value. --- @tparam string ... The allowed types of the argument. --- @throws If the value is not one of the allowed types. -local function expect(index, value, ...) - local t = native_type(value) - for i = 1, native_select("#", ...) do - if t == native_select(i, ...) then return true end - end - +local function get_type_names(...) local types = table.pack(...) for i = types.n, 1, -1 do if types[i] == "nil" then table.remove(types, i) end end - local type_names if #types <= 1 then - type_names = tostring(...) + return tostring(...) else - type_names = table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types] + return table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types] + end +end +--- Expect an argument to have a specific type. +-- +-- @tparam number index The 1-based argument index. +-- @param value The argument's value. +-- @tparam string ... The allowed types of the argument. +-- @return The given `value`. +-- @throws If the value is not one of the allowed types. +local function expect(index, value, ...) + local t = native_type(value) + for i = 1, native_select("#", ...) do + if t == native_select(i, ...) then return value end end -- If we can determine the function name with a high level of confidence, try to include it. @@ -36,6 +37,7 @@ local function expect(index, value, ...) if ok and info.name and #info.name ~= "" and info.what ~= "C" then name = info.name end end + local type_names = get_type_names(...) if name then error(("bad argument #%d to '%s' (expected %s, got %s)"):format(index, name, type_names, t), 3) else @@ -43,4 +45,31 @@ local function expect(index, value, ...) end end -return { expect = expect } +--- Expect an field to have a specific type. +-- +-- @tparam table tbl The table to index. +-- @tparam string index The field name to check. +-- @tparam string ... The allowed types of the argument. +-- @return The contents of the given field. +-- @throws If the field is not one of the allowed types. +local function field(tbl, index, ...) + expect(1, tbl, "table") + expect(2, index, "string") + + local value = tbl[index] + local t = native_type(value) + for i = 1, native_select("#", ...) do + if t == native_select(i, ...) then return value end + end + + if value == nil then + error(("field '%s' missing from table"):format(index), 3) + else + error(("bad field '%s' (expected %s, got %s)"):format(index, get_type_names(...), t), 3) + end +end + +return { + expect = expect, + field = field, +} diff --git a/src/test/resources/test-rom/spec/modules/cc/expect_spec.lua b/src/test/resources/test-rom/spec/modules/cc/expect_spec.lua index 36f076f60..125e43640 100644 --- a/src/test/resources/test-rom/spec/modules/cc/expect_spec.lua +++ b/src/test/resources/test-rom/spec/modules/cc/expect_spec.lua @@ -1,31 +1,53 @@ describe("cc.expect", function() local e = require("cc.expect") - it("checks a single type", function() - expect(e.expect(1, "test", "string")):eq(true) - expect(e.expect(1, 2, "number")):eq(true) + describe("expect", function() + it("checks a single type", function() + expect(e.expect(1, "test", "string")):eq("test") + expect(e.expect(1, 2, "number")):eq(2) - expect.error(e.expect, 1, nil, "string"):eq("bad argument #1 (expected string, got nil)") - expect.error(e.expect, 2, 1, "nil"):eq("bad argument #2 (expected nil, got number)") + expect.error(e.expect, 1, nil, "string"):eq("bad argument #1 (expected string, got nil)") + expect.error(e.expect, 2, 1, "nil"):eq("bad argument #2 (expected nil, got number)") + end) + + it("checks multiple types", function() + expect(e.expect(1, "test", "string", "number")):eq("test") + expect(e.expect(1, 2, "string", "number")):eq(2) + + expect.error(e.expect, 1, nil, "string", "number"):eq("bad argument #1 (expected string or number, got nil)") + expect.error(e.expect, 2, false, "string", "table", "number", "nil") + :eq("bad argument #2 (expected string, table or number, got boolean)") + end) + + it("includes the function name", function() + local function worker() + expect(e.expect(1, nil, "string")):eq(true) + end + local function trampoline() + worker() + end + + expect.error(trampoline):eq("expect_spec.lua:27: bad argument #1 to 'worker' (expected string, got nil)") + end) end) - it("checks multiple types", function() - expect(e.expect(1, "test", "string", "number")):eq(true) - expect(e.expect(1, 2, "string", "number")):eq(true) + describe("field", function() + it("checks a single type", function() + expect(e.field({ k = "test" }, "k", "string")):eq("test") + expect(e.field({ k = 2 }, "k", "number")):eq(2) - expect.error(e.expect, 1, nil, "string", "number"):eq("bad argument #1 (expected string or number, got nil)") - expect.error(e.expect, 2, false, "string", "table", "number", "nil") - :eq("bad argument #2 (expected string, table or number, got boolean)") - end) + expect.error(e.field, { k = nil }, "k", "string"):eq("field 'k' missing from table") + expect.error(e.field, { l = 1 }, "l", "nil"):eq("bad field 'l' (expected nil, got number)") + end) - it("includes the function name", function() - local function worker() - expect(e.expect(1, nil, "string")):eq(true) - end - local function trampoline() - worker() - end + it("checks multiple types", function() + expect(e.field({ k = "test" }, "k", "string", "number")):eq("test") + expect(e.field({ k = 2 }, "k", "string", "number")):eq(2) - expect.error(trampoline):eq("expect_spec.lua:26: bad argument #1 to 'worker' (expected string, got nil)") + expect.error(e.field, { k = nil }, "k", "string", "number") + :eq("field 'k' missing from table") + expect.error(e.field, { l = false }, "l", "string", "table", "number", "nil") + :eq("bad field 'l' (expected string, table or number, got boolean)") + end) end) end)