Last active
December 22, 2024 13:35
-
-
Save SkyyySi/d26554e1b10890debdda5f5ca66105c2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local getmetatable = getmetatable | |
local type = type | |
local assert = assert | |
local rawget = rawget | |
---@class private | |
local _M = {} | |
--- A helper function to check whether the type of a given parameter is correct, | |
--- generating an error message if it is not. | |
---@generic T | |
---@param function_name string | |
---@param expected_type `T` | type | |
---@param parameter_name string | |
---@param parameter_index integer | |
---@param parameter_value T | |
---@return boolean type_is_ok | |
---@return nil | string error_message | |
local function check_parameter_type(function_name, expected_type, parameter_name, parameter_index, parameter_value) | |
local parameter_type = type(parameter_value) | |
if parameter_type == expected_type then | |
return true, nil | |
end | |
return false, ("Wrong type of parameter #%d '%s' to function '%s()'! (expected %s but got %s)"):format( | |
parameter_index, | |
parameter_name, | |
function_name, | |
expected_type, | |
parameter_type | |
) | |
end | |
--- A helper function that converts a readable key name (like `"foo"`) into a | |
--- private key name (like `"__PRIVATE_MyClass::foo__"`). | |
--- | |
--- The table `tb` **must** have a metatable with a `__name` string field. | |
---@generic K : string | |
---@generic V | |
---@param tb table<K, V> | |
---@param key string | |
---@return K | nil private_key | |
---@return nil | string error_message | |
local function private_key_helper(tb, key) | |
---@type metatable | nil | |
local mt = getmetatable(tb) | |
if type(mt) ~= "table" then | |
return nil, "The given table has no metatable!" | |
end | |
---@type string | nil | |
local name = rawget(mt, "__name") | |
if type(name) ~= "string" then | |
return nil, "The given table has no '__name' metatable field!" | |
end | |
local private_key = ("__PRIVATE__%s::%s__"):format(name, key) | |
return private_key, nil | |
end | |
--- Get the value of a private field `key` of a table `tb`. | |
--- | |
--- The table `tb` **must** have a metatable with a `__name` string field. | |
---@generic K : string | |
---@generic V | |
---@param tb table<K, V> | |
---@param key string | |
---@return V value | |
function _M.get(tb, key) | |
assert(check_parameter_type("get", "table", "tb", 1, tb)) | |
assert(check_parameter_type("get", "string", "key", 2, key)) | |
local private_key = assert(private_key_helper(tb, key)) | |
local value = tb[private_key] | |
return value | |
end | |
--- Set a private field `key` of a table `tb` to `value`. | |
--- | |
--- The table `tb` **must** have a metatable with a `__name` string field. | |
---@generic K : string | |
---@generic V | |
---@param tb table<K, V> | |
---@param key string | |
---@param value V | |
---@return nil | |
function _M.set(tb, key, value) | |
assert(check_parameter_type("set", "table", "tb", 1, tb)) | |
assert(check_parameter_type("set", "string", "key", 2, key)) | |
local private_key = assert(private_key_helper(tb, key)) | |
tb[private_key] = value | |
return nil | |
end | |
-------------------------------------------------------------------------------- | |
--- Testing code --- | |
-------------------------------------------------------------------------------- | |
---@param args string[] | |
---@retuen integer exit_status_code | |
function _M.main(args) | |
local print = print | |
local format = string.format | |
local test_class_name = "MyClass" | |
local test_key = "foo" | |
local test_value = "bar" | |
local test_tb = setmetatable({}, { | |
__name = test_class_name, | |
}) | |
print(format([[>>> test_tb = setmetatable({}, { | |
>>> __name = %q, | |
>>> }]], test_class_name)) | |
print() | |
print(format(">>> private.get(test_tb, %q) = %s", test_key, _M.get(test_tb, test_key))) | |
print(format(">>> private.set(test_tb, %q, %q)", test_key, test_value)) | |
_M.set(test_tb, test_key, test_value) | |
print(format(">>> private.get(test_tb, %q) = %q", test_key, _M.get(test_tb, test_key))) | |
print() | |
for k, v in pairs(test_tb) do | |
print(format("test_tb[%q] = %q", k, v)) | |
end | |
end | |
--- This is only `true` if this module is directly being executed, and not if | |
--- it is loaded with `require()`. | |
--- | |
--- Note: `load()`, `dofile()`, etc. will also run this, so you're better off | |
--- not using them. | |
if ... == nil then | |
os.exit(_M.main(arg)) | |
end | |
return _M |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment