├── .gitpod.yml ├── Docs.md ├── LICENSE ├── README.md ├── Self.lua ├── Stack.lua ├── Test.lua └── self-0.2.2-0.rockspec /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: gitpod/workspace-base 2 | 3 | tasks: 4 | - name: Setup workspace 5 | command: | 6 | sudo apt update 7 | sudo apt upgrade -y 8 | sudo apt install -y lua5.1 9 | curl -L https://github.com/luvit/lit/raw/master/get-lit.sh | sh 10 | sudo mv lit /usr/local/bin 11 | sudo mv luvi /usr/local/bin 12 | sudo mv luvit /usr/local/bin 13 | 14 | vscode: 15 | extensions: 16 | - sumneko.lua 17 | -------------------------------------------------------------------------------- /Docs.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | Self is intended to be simple, fast and easy to use. The difference between Self and others OOP libraries is mainly in the design/features: 4 | 5 | - All clases inherits directly from `Object` 6 | - Inheritance isn't supported ***at all*** (I'll try to explain this later) 7 | - At the moment, custom `__index` and `__newindex` metamethods aren't supported 8 | - There's an optional global constructor function called `New` (offering an C#/Java-like syntax) 9 | - Tries to generate clean tables (no `_thing` or `__thing` crap) 10 | - More """realistic""" OOP 11 | 12 | At the moment, there's not much to say about Self: 13 | 14 | ```lua 15 | local class = require("Self") 16 | local Car = class { 17 | model = '', 18 | manufacturer = '' 19 | } 20 | 21 | function Car:new(manufacturer, model) 22 | self.manufacturer = manufacturer 23 | self.model = model 24 | end 25 | 26 | function Car:start(...) 27 | -- ... 28 | end 29 | 30 | local fordFiesta = Car("Ford", "Fiesta") 31 | ``` 32 | 33 | ## Installing Self 34 | 35 | You can either use Luarocks or just download [Self.lua](Self.lua) and put it in your project. 36 | 37 | ### What about `Stack.lua`? 38 | 39 | `Stack.lua` is just a simple implementation of the stack data structure that I made a while ago. Self doesn't require it, but if you like it, you're free to use it. You can `require()` it by like: 40 | 41 | ```lua 42 | local stack = require("Self.Stack") 43 | ``` 44 | 45 | If you installed Self with Luarocks. 46 | 47 | ## About inheritance 48 | 49 | With the recent changes (since this [commit][commit]) in Self, I have removed the support for inheritance. Instead, I'm trying to use a composition-like focus, so now inheritance is basically the same as implementing a set of functions into a class, keeping all the classes/objects with just one parent: `Object`. I made that because supporting inheritance sometimes is a pain in the ass: 50 | 51 | - Performance issues 52 | - Hard to mantain 53 | - ***M e t a t a b l e s*** 54 | 55 | Well, it can depend on how is implemented... But still is a pain in the ass. 56 | 57 | So... *"How I can **implement** functions in my class?"*. Well, that's quite simple: 58 | 59 | ```lua 60 | local class = require("Self") 61 | 62 | local MyFunctions = { 63 | a = function() 64 | -- ... 65 | end 66 | 67 | -- ... 68 | } 69 | 70 | local MyClass = Class { 71 | -- ... 72 | } 73 | 74 | MyClass:implements(MyFunctions) 75 | ``` 76 | 77 | You can also pass another class (by example): 78 | 79 | ```lua 80 | -- ... 81 | MyClass:implements(ClassA, ClassB, ClassC) 82 | ``` 83 | 84 | And isn't required to pass one by one, the `implements` functions accepts an undefined number of arguments. 85 | 86 | ## The `class` function 87 | 88 | This is the main function to get OOP done with Self, the table that u pass to this function is used like a "template" or "blueprint". By example: 89 | 90 | ```lua 91 | local class = require("Self") 92 | local Person = class { name = "" } 93 | 94 | function Person:new(name) 95 | self.name = name 96 | end 97 | 98 | function Person:greet() 99 | print(("Hello, my name is %s!"):format(self.name)) 100 | end 101 | 102 | local me = Person("Miqueas") 103 | -- This will throw an error, because there's no `age` property (it wasn't 104 | -- declared in the 2nd line) and allows to prevent pushing unexpected 105 | -- things in objects. 106 | me.age = 21 107 | ``` 108 | 109 | ## Object _immutability_ 110 | 111 | Self objects (class instances) are, in a sense, inmutable. As you saw above, you can only write existing properties in an object. For methods is a bit different: you can't overwrite methods from an object. By example (following the above person example): 112 | 113 | ```lua 114 | -- ... 115 | 116 | -- This will throw an error 117 | me.greet = 29 118 | ``` 119 | 120 | This is the reason why (at the moment) there's no way to support custom `__index` and `__newindex` metamethods, because this behavior is handled using these metamethods. 121 | 122 | [commit]: https://github.com/Miqueas/Self/commit/575093ae9f53fe67c930543891cd117410235993 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | zlib License 2 | 3 | Copyright (c) 2020 - 2024 Miqueas Martinez 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 18 | 2. Altered source versions must be plainly marked as such, and must not be 19 | misrepresented as being the original software. 20 | 21 | 3. This notice may not be removed or altered from any source 22 | distribution. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License][LicenseBadge]][licenseURL] 2 | 3 | # Self 4 | 5 | A small and simple library for OOP in Lua. 6 | 7 | ## Basic usage 8 | 9 | Here's a simple example: 10 | 11 | ```lua 12 | local class = require("Self") 13 | local Point = class { 14 | x = 0, 15 | y = 0 16 | } 17 | 18 | function Point:new(x, y) 19 | self.x = x 20 | self.y = y 21 | end 22 | 23 | local p = Point(10, 40) 24 | ``` 25 | 26 | Read the docs [here](Docs.md) 27 | 28 | [LicenseBadge]: https://img.shields.io/badge/License-Zlib-brightgreen?style=flat 29 | [LicenseURL]: https://opensource.org/licenses/Zlib 30 | -------------------------------------------------------------------------------- /Self.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Author: Miqueas Martinez (miqueas2020@yahoo.com) 3 | Date: 2020/04/30 4 | Git repository: https://github.com/Miqueas/Self 5 | 6 | zlib License 7 | 8 | Copyright (c) 2020 - 2024 Miqueas Martinez 9 | 10 | This software is provided 'as-is', without any express or implied 11 | warranty. In no event will the authors be held liable for any damages 12 | arising from the use of this software. 13 | 14 | Permission is granted to anyone to use this software for any purpose, 15 | including commercial applications, and to alter it and redistribute it 16 | freely, subject to the following restrictions: 17 | 18 | 1. The origin of this software must not be misrepresented; you must not 19 | claim that you wrote the original software. If you use this software 20 | in a product, an acknowledgment in the product documentation would be 21 | appreciated but is not required. 22 | 23 | 2. Altered source versions must be plainly marked as such, and must not be 24 | misrepresented as being the original software. 25 | 26 | 3. This notice may not be removed or altered from any source 27 | distribution. 28 | ]] 29 | 30 | local unp = unpack or table.unpack 31 | local getmt = getmetatable 32 | local setmt = setmetatable 33 | local get = rawget 34 | local set = rawset 35 | 36 | --[[ Private stuff ]] 37 | 38 | --- A simple custom version of `assert()` with built-in 39 | --- `string.format()` support 40 | --- @generic Expr 41 | --- @param exp Expr Expression to evaluate 42 | --- @param msg string Error message to print 43 | --- @vararg any Additional arguments to format for `msg` 44 | --- @return Expr 45 | local function err(exp, msg, ...) 46 | local msg = msg:format(...) 47 | 48 | if not (exp) then 49 | return error(msg) 50 | end 51 | 52 | return exp 53 | end 54 | 55 | --- Argument type checking function 56 | --- @generic Any 57 | --- @generic Type 58 | --- @param argn number The argument position in function 59 | --- @param arg_ Any The argument to check 60 | --- @param expected Type The type expected (`string`) 61 | --- @return nil 62 | local function check_arg(argn, arg_, expected) 63 | local argt = type(arg_) 64 | local msg = "Bad argument #%d, `%s` expected, got `%s`." 65 | 66 | if argt ~= expected then 67 | error(msg:format(argn, expected, argt)) 68 | end 69 | end 70 | 71 | --- Same as `check_arg()`, except that this don't throw 72 | --- and error if the argument is `nil` 73 | --- @generic Any 74 | --- @generic Type 75 | --- @param argn number The argument position in function 76 | --- @param arg_ Any The argument to check 77 | --- @param expected Type The type expected (`string`) 78 | --- @return nil 79 | local function opt_arg(argn, arg_, expected) 80 | local argt = type(arg_) 81 | local msg = "Bad argument #%d, `%s` or `nil` expected, got `%s`." 82 | 83 | if argt ~= expected and argt ~= "nil" then 84 | error(msg:format(argn, expected, argt)) 85 | end 86 | end 87 | 88 | --[[ Public stuff ]] 89 | 90 | --- Parent class for all classes 91 | --- @class Object 92 | local Class = {} 93 | 94 | --- Creates a class 95 | --- @param def table Class "template" 96 | --- @return table 97 | local function createClass(def) 98 | check_arg(1, def, "table") 99 | 100 | local class = setmt(def, Class) 101 | 102 | return class 103 | end 104 | 105 | --- Return `true` if `instance` is type of `class`, 106 | --- otherwise returns `false`. If `class` is `nil`, 107 | --- then returns "Object". 108 | --- @param instance table The object 109 | --- @param class table The class 110 | --- @return boolean|string 111 | function Class.is(instance, class) 112 | check_arg(1, instance, "table") 113 | opt_arg(2, class, "table") 114 | 115 | local mt = getmt(instance) 116 | 117 | if not class then 118 | return "Object" 119 | end 120 | 121 | return mt == class or getmt(mt) == Class 122 | end 123 | 124 | --- Implements all functions from one or more classes/tables given. 125 | --- Can't use this from an instance. 126 | --- @param class table The class where implement functions 127 | --- @param ... table Tables/classes with functions to import from 128 | --- @return nil 129 | function Class.implements(class, ...) 130 | check_arg(1, class, "table") 131 | 132 | -- Prevent using this method from an instance 133 | err( 134 | getmt(class) == Class, 135 | "Trying to call 'implements' method from an instance" 136 | ) 137 | 138 | for index, iface in ipairs({ ... }) do 139 | check_arg(index, iface, "table") 140 | 141 | for name, func in pairs(iface) do 142 | if not get(class, name) then 143 | set(class, name, func) 144 | end 145 | end 146 | end 147 | end 148 | 149 | --- Global constructor 150 | --- @param class table The class to use to create the object 151 | --- @param ... any Additional arguments to pass to the class constructor 152 | --- @return table 153 | function Class.__call(class, ...) 154 | check_arg(1, class, "table") 155 | 156 | -- At the moment, these two meta-fields aren't writables 157 | set(class, "__index", class) 158 | set(class, "__newindex", Class.__newindex) 159 | 160 | local o = setmt({}, class) 161 | 162 | err(o.new ~= nil, "This class doesn't have a constructor method") 163 | o:new(...) 164 | 165 | return o 166 | end 167 | 168 | --- Getter 169 | --- @param class table The object 170 | --- @param key string The key 171 | --- @return any 172 | function Class.__index(class, key) 173 | return get(class, key) or get(Class, key) 174 | end 175 | 176 | --- Setter 177 | --- @param class table The object 178 | --- @param key string The key 179 | --- @param val any The value 180 | --- @return nil 181 | function Class.__newindex(class, key, val) 182 | local mt = getmt(class) 183 | local _val = get(class, key) or get(mt, key) 184 | 185 | if mt == Class then 186 | -- From a class 187 | set(class, key, val) 188 | else 189 | -- From an instance 190 | err(_val ~= nil, "Field '%s' doesn't exists.", key) 191 | err(type(_val) ~= "function", "Trying to overwrite method '%s'.", key) 192 | set(class, key, val) 193 | end 194 | end 195 | 196 | return createClass -------------------------------------------------------------------------------- /Stack.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Author: Miqueas Martinez (miqueas2020@yahoo.com) 3 | Date: 2020/04/30 4 | Git repository: https://github.com/Miqueas/Self 5 | ]] 6 | 7 | local class = require("Self") 8 | local unp = unpack or table.unpack 9 | local Stack = class { 10 | reg = { main = {} }, 11 | current = "main", 12 | stacks = { "main" } 13 | } 14 | 15 | function Stack:new(...) 16 | local args = {...} 17 | 18 | if #args > 0 then 19 | for _, v in ipairs(args) do 20 | if type(v) == "string" then 21 | self.reg[v] = {} 22 | table.insert(self.stacks, v) 23 | end 24 | end 25 | end 26 | end 27 | 28 | function Stack:push(...) 29 | local args = {...} 30 | 31 | if #args > 0 then 32 | for _, v in ipairs(args) do 33 | table.insert(self.reg[self.current], v) 34 | end 35 | end 36 | end 37 | 38 | function Stack:pop(pos) 39 | table.remove(self.reg[self.current], pos) 40 | end 41 | 42 | function Stack:get(pos) 43 | local last = self.reg[self.current][#self.reg[self.current]] 44 | 45 | if pos then 46 | return self.reg[self.current][pos] 47 | end 48 | 49 | return last 50 | end 51 | 52 | function Stack:switch(name) 53 | assert(self.reg[name], ("Stack '%s' not exists"):format(name)) 54 | self.current = name 55 | end 56 | 57 | function Stack:name() return self.current end 58 | function Stack:len() return #self.reg[self.current] end 59 | function Stack:unpack() return unp(self.reg[self.current]) end 60 | 61 | function Stack:create(name) 62 | self.reg[name] = {} 63 | table.insert(self.stacks, name) 64 | end 65 | 66 | function Stack:clear(name) 67 | local name = name or self.current 68 | assert(self.reg[name], ("Stack '%s' not exists"):format(name)) 69 | self.reg[name] = {} 70 | end 71 | 72 | function Stack:list() return unp(self.stacks) end 73 | 74 | function Stack:index(val) 75 | for i, v in ipairs(self.reg[self.current]) do 76 | if v == val then 77 | return i 78 | end 79 | end 80 | end 81 | 82 | function Stack:destroy(name) 83 | local name = name or self.current 84 | assert(self.reg[name], ("Stack '%s' not exists"):format(name)) 85 | assert(name ~= "main", "'main' stack can't be destroyed") 86 | for i, v in ipairs(self.stacks) do 87 | if v == name then 88 | table.remove(self.stacks, i) 89 | end 90 | end 91 | self.reg[name] = nil 92 | end 93 | 94 | return Stack -------------------------------------------------------------------------------- /Test.lua: -------------------------------------------------------------------------------- 1 | local class = require("Self") 2 | local Person = class { 3 | name = "", 4 | age = 0 5 | } 6 | 7 | function Person:new(name, age) 8 | self.name = name 9 | self.age = age 10 | end 11 | 12 | function Person:greet() 13 | print(("Hello, my name is %s and I'm %dyo"):format(self.name, self.age)) 14 | end 15 | 16 | local a = Person("A", 34) 17 | local b = Person("B", 56) 18 | local c = Person("C", 86) 19 | 20 | print(a:is()) 21 | print(b:is(Person)) 22 | 23 | -- Pretty print with Luvit 24 | if p then 25 | p(a) 26 | p(b) 27 | p(c) 28 | p(Person) 29 | end 30 | 31 | a:greet() 32 | b:greet() 33 | c:greet() -------------------------------------------------------------------------------- /self-0.2.2-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "Self" 2 | -- SemVer: 0.2.2 3 | -- Revision: 0 4 | version = "0.2.2-0" 5 | source = { 6 | url = "git://github.com/Miqueas/Self", 7 | tag = "v0.2.2" 8 | } 9 | 10 | description = { 11 | summary = "A small and lightweight OOP library", 12 | detailed = "Self provides a simple API that allows to use/implement some basic OOP features.", 13 | homepage = "https://github.com/Miqueas/Self", 14 | license = "MIT" 15 | } 16 | 17 | dependencies = { 18 | "lua >= 5.1, < 5.5" 19 | } 20 | 21 | build = { 22 | type = "builtin", 23 | modules = { 24 | ["Self"] = "Self.lua", 25 | ["Self.Stack"] = "Stack.lua" 26 | } 27 | } 28 | --------------------------------------------------------------------------------