diff --git a/main.lua b/main.lua index f7cf62e..f27293f 100644 --- a/main.lua +++ b/main.lua @@ -1,12 +1,16 @@ -utils = require("oop/utils") +oop = require("oop/oop") -class = { - name = "MyClass", - sayHello = function(self) - print("Hello from " .. self.name) +local function MyClass(cls) + cls.setattr(cls, "name") + + function cls.__init(this, name) + this.name = name end -} -obj = utils.deepcopy(class) -obj.name = "Carl" -obj.sayHello(obj) -- Output: Hello from Carl + function cls.test(this, name) + print("hi " .. this.name .. " from " .. name) + end +end + +local obj = oop.new(MyClass, "Carl") +obj.test("tom") -- Output: hi Carl from tom diff --git a/oop/oop.lua b/oop/oop.lua index 120695e..93fc3c2 100644 --- a/oop/oop.lua +++ b/oop/oop.lua @@ -1,9 +1,125 @@ ---[[ - the plan is to create a simple oop system in lua +local oop = {} - it might not work liky python +local function super_call(self, method_name, ...) + local class = getmetatable(self) + if not class then + error("super() called on non-instance") + end + local base = rawget(class, "__base") + if not base then + error("super() called but no base class") + end + local fn = base[method_name] + if not fn then + error("base class has no method '" .. tostring(method_name) .. "'") + end + return fn(self, ...) +end - think of it more like a facory system - with at least the concept of self -]] +local function_def_cache = setmetatable({}, { __mode = "k" }) +function oop.setattr(target, key, value) + target[key] = value +end + +function oop.new(class, ...) + local actual_class = class + if type(class) == "function" then + actual_class = function_def_cache[class] + if not actual_class then + actual_class = oop.class(class) + function_def_cache[class] = actual_class + end + end + local obj = setmetatable({}, actual_class) + if actual_class.__init then + actual_class.__init(obj, ...) + end + return obj +end + +local function lookup_in_class(cls, key) + local cur = cls + while cur do + local val = rawget(cur, key) + if val ~= nil then + return val + end + cur = rawget(cur, "__base") + end + return nil +end + +function oop.class(def, base) + local cls = {} + cls.__name = "Anonymous" + cls.__base = base + cls.__index = function(self, key) + local val = lookup_in_class(cls, key) + if type(val) == "function" then + return function(...) + return val(self, ...) + end + end + return val + end + + function cls:super(method_name, ...) + return super_call(self, method_name, ...) + end + function cls.setattr(target, key, value) + return oop.setattr(target, key, value) + end + + setmetatable(cls, { + __index = base, + __call = function(c, ...) + return oop.new(c, ...) + end + }) + + if type(def) == "string" then + cls.__name = def + elseif type(def) == "function" then + def(cls) + elseif def ~= nil then + error("class definition must be a function or name string") + end + + return cls +end + +function oop.isinstance(obj, class) + local mt = getmetatable(obj) + while mt do + if mt == class then + return true + end + mt = rawget(mt, "__base") + end + return false +end + +function oop.issubclass(class, base) + local mt = class + while mt do + if mt == base then + return true + end + mt = rawget(mt, "__base") + end + return false +end + +function oop.install(env) + local target = env or _G + target.class = oop.class + target.new = oop.new + target.setattr = oop.setattr + target.isinstance = oop.isinstance + target.issubclass = oop.issubclass +end + +oop.install() + +return oop