diff --git a/oop/oop.lua b/oop/oop.lua index f65faf4..0a723bc 100644 --- a/oop/oop.lua +++ b/oop/oop.lua @@ -1,5 +1,11 @@ local oop = {} +oop.Visibility = { + PUBLIC = "public", + PROTECTED = "protected", + PRIVATE = "private" +} + local tpack = table.pack or function(...) return { n = select("#", ...), ... } end @@ -30,12 +36,12 @@ oop._call_stack = {} local function normalize_visibility(visibility) if visibility == nil then - return "public" + return oop.Visibility.PUBLIC end - if visibility == "public" or visibility == "protected" or visibility == "private" then + if visibility == oop.Visibility.PUBLIC or visibility == oop.Visibility.PROTECTED or visibility == oop.Visibility.PRIVATE then return visibility end - error("visibility must be 'public', 'protected', or 'private'") + error("visibility must be Visibility.PUBLIC, Visibility.PROTECTED, or Visibility.PRIVATE") end local function is_class_table(target) @@ -58,21 +64,36 @@ local function current_caller() end local function can_access(owner, visibility, caller) - if visibility == "public" then + if visibility == oop.Visibility.PUBLIC then return true end if not caller or not owner then return false end - if visibility == "private" then + if visibility == oop.Visibility.PRIVATE then return caller == owner end - if visibility == "protected" then + if visibility == oop.Visibility.PROTECTED then return oop.issubclass(caller, owner) end return false end +local function class_name(cls) + if type(cls) ~= "table" then + return tostring(cls) + end + return rawget(cls, "__name") or "Anonymous" +end + +local function visibility_error(kind, member, visibility, owner, caller) + local from = caller and class_name(caller) or "outside" + local where = owner and class_name(owner) or "unknown" + local msg = "access violation: " .. kind .. " '" .. tostring(member) .. "' is " .. visibility + .. " in " .. where .. " (from " .. from .. ")" + error(msg, 3) +end + function oop.setattr(target, key, value, visibility) target[key] = value if is_class_table(target) then @@ -135,7 +156,7 @@ function lookup_visibility(cls, key) end cur = rawget(cur, "__base") end - return "public", nil + return oop.Visibility.PUBLIC, nil end function oop.class(def, base) @@ -143,6 +164,7 @@ function oop.class(def, base) cls.__name = "Anonymous" cls.__base = base cls.__visibility = {} + cls.visibility = oop.Visibility cls.__index = function(self, key) local fields = rawget(self, "__fields") if fields then @@ -150,7 +172,7 @@ function oop.class(def, base) if field_val ~= nil then local vis, owner = lookup_visibility(cls, key) if not can_access(owner, vis, current_caller()) then - error("attempt to access " .. vis .. " member '" .. tostring(key) .. "'") + visibility_error("field", key, vis, owner, current_caller()) end return field_val end @@ -163,7 +185,7 @@ function oop.class(def, base) local vis, vis_owner = lookup_visibility(cls, key) local access_owner = vis_owner or owner if not can_access(access_owner, vis, current_caller()) then - error("attempt to access " .. vis .. " member '" .. tostring(key) .. "'") + visibility_error("member", key, vis, access_owner, current_caller()) end if type(val) == "function" then return function(...) @@ -179,7 +201,7 @@ function oop.class(def, base) end local vis, owner = lookup_visibility(cls, key) if owner and not can_access(owner, vis, current_caller()) then - error("attempt to set " .. vis .. " member '" .. tostring(key) .. "'") + visibility_error("field", key, vis, owner, current_caller()) end local fields = rawget(self, "__fields") if not fields then @@ -284,6 +306,7 @@ function oop.install(env) target.setattr = oop.setattr target.isinstance = oop.isinstance target.issubclass = oop.issubclass + target.Visibility = oop.Visibility end oop.install()