Jul 28th, 2021 (edited)
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. -- The Class function attempts emulate the creation of class based objects used within OOP languages.
  2. -- C++ is the inspiration for the design of this function.
  3. -- Details on how this works and how to use it will be at the bottom of this document.
  5. -- Read Only Function
  6. readOnly = function(t, ro)
  7.     return setmetatable(t, {
  8.         __index = ro,
  9.         __newindex = function(t,k,v)
  10.             error("attempt to update a protected member", 3)
  11.         end
  12.     })
  13. end
  15. -- Type Error Function
  16. typeCheck = function(member, memberName, types)
  17.     local err = true
  18.     local errorMessage = "expected '"..memberName.."' to be type "
  19.     for a, b in ipairs(types) do
  20.         if a > 1 then errorMessage = errorMessage.." or " end
  21.         errorMessage = errorMessage..b
  22.         if type(member) == b then
  23.             err = false
  24.         end
  25.     end
  26.     errorMessage = errorMessage..", got "..type(member)
  27.     if err then error(errorMessage, 3) end
  28. end
  30. -- Object Constructor Function
  31. Class = function(members)
  32.     if type(members) ~= "table" then
  33.         error("attempt to create an object with no class (expected table argument)", 2)
  34.     end
  36.     typeCheck(members.public, "public", {"table", "nil"})
  37.     typeCheck(members.protected, "protected", {"table", "nil"})
  38.     typeCheck(members.inherit, "inherit", {"table", "nil"})
  39.     members.public = members.public or {}
  40.     members.protected = members.protected or {}
  42.     if members.inherit ~= nil then
  43.         for a, b in pairs(members.inherit) do members.public[a] = b end
  44.         for a, b in pairs(getmetatable(members.inherit).__index) do members.protected[a] = b end
  45.     end
  47.     return readOnly(members.public, members.protected)
  48. end
  52. -- Examples
  53. -- Base Class Function
  54. local baseClass = function()
  55.     local private = {}
  56.     local protected = {}
  57.     protected.baseClass = function()
  58.         protected.var6 = 7
  60.         protected.getVar3 = function()
  61.             return private.var3
  62.         end
  64.         return Class{ctor = "baseClass", protected = protected}
  65.     end
  66.     return protected.baseClass()
  67. end
  69. -- Derived Class Function
  70. local myClass = function(setBool)
  71.     local private = {}
  72.     local public = {
  73.         var4 = 8.4 -- public
  74.     }
  75.     local protected = {}
  77.     public.myClass = function(setBool) -- Constructor, public
  78.         local var1 = 12 -- private
  79.         protected.var2 = "Hello World" -- protected
  80.         private.var3 = true -- private
  81.         public.var5 = 0 -- public
  83.         local product = function() -- private
  84.             public.var5 = var1 * public.var4
  85.         end
  87.         public.printVar3 = function() -- public
  88.             print(private.var3)
  89.         end
  91.         public.getVar1 = function() -- public
  92.             return var1
  93.         end
  95.         protected.getVar2 = function() -- protected
  96.             return protected.var2
  97.         end
  99.         protected.setVar2 = function(val) -- protected
  100.             protected.var2 = val
  101.         end
  103.         protected.setVar6 = function(val) -- protected
  104.             if type(val) == "number" then
  105.                 protected.var6 = val
  106.                 var1 = val * 2
  107.             end
  108.         end
  110.         if type(setBool) == "boolean" then
  111.             var3 = setBool
  112.             product()
  113.         end
  115.         return Class{
  116.             protected = protected,
  117.             public = public,
  118.             inherit = baseClass()
  119.         }
  120.     end
  121.     return public.myClass(setBool)
  122. end
  124. -- Proof of Concept
  125. local testProgram = function()
  126.     local obj = myClass(true)
  127.     local obj2 = myClass(false)
  128.     print(obj.var2)
  129.     print(obj2.var2)
  130.     obj.setVar2("hey")
  131.     obj2.setVar2("yo")
  132.     print(obj.var2)
  133.     print(obj2.var2)
  134.     --obj.var2 = "Hello World"
  135.     --obj2.var2 = "Hello World"
  136.     --print(obj.var2)
  137.     --print(obj2.var2)
  138.     print()
  140.     print(obj.var4)
  141.     print(obj2.var4)
  142.     print(obj.var5)
  143.     print(obj2.var5)
  144.     obj.var5 = 25
  145.     obj2.var5 = 26
  146.     print(obj.var5)
  147.     print(obj2.var5)
  148.     print()
  150.     print(obj.var6)
  151.     print(obj2.var6)
  152.     print(obj.getVar1())
  153.     print(obj2.getVar1())
  154.     --obj.var6 = 0
  155.     --obj2.var6 = 1
  156.     --print(obj.var6)
  157.     --print(obj2.var6)
  158.     obj.setVar6(777)
  159.     obj2.setVar6(777777)
  160.     print(obj.var6)
  161.     print(obj2.var6)
  162.     print(obj.getVar1())
  163.     print(obj2.getVar1())
  164.     print()
  166.     --print(obj.getVar3())
  167.     --print(obj2.getVar3())
  168.     obj.printVar3()
  169.     obj2.printVar3()
  170.     --obj.var10 = "test"
  171.     --print(obj.var10)
  172.     print()
  174.     print(obj.getVar1)
  175.     obj.getVar1 = function() return "test" end
  176.     print(obj.getVar1)
  177.     print(obj.getVar1())
  178.     print()
  180.     obj.myClass()
  181.     print(obj.var2)
  182.     print(obj.var4)
  183.     print(obj.var5)
  184.     print(obj.var6)
  185.     obj.printVar3()
  186.     print(obj.getVar1())
  187.     print(obj.getVar2())
  188.     obj.setVar2("Aye")
  189.     print(obj.var2)
  190.     obj.setVar6(21)
  191.     print(obj.getVar1())
  192.     print(obj.var6)
  193.     print()
  195.     print(obj2.var2)
  196.     print(obj2.var4)
  197.     print(obj2.var5)
  198.     print(obj2.var6)
  199.     obj2.printVar3()
  200.     print(obj2.getVar1())
  201.     print(obj2.getVar2())
  202. end
  203. --testProgram()
  208. --[[ Read Only Function
  209.     First argument is the base table.
  210.     Second argument is a table containing the read-only elements of the table.
  211.     This functions takes the second table and sets it as a metatable for the base table that is read-only.
  212. ]]
  214. --[[ Type Error Function
  215.     First argument is the variable to check the type of.
  216.     Second argument should be a string matching the name of the variable.
  217.     Third argument is a table containing strings of types the variable should be.
  218.     The variable type is checked against the list of types, then throws an error if the variable type doesn't match any of the listed types.
  219. ]]
  221. --[[ Object Constructor Function
  222.     From here on I will refer to the 'Class(members)' function as the object constructor (not to be confused with the class constructor, which is the constructor method of a class).
  223.     This function processes the data from a class and creates an object from that data.
  224.     The members argument is the class that an object will be constructed from.
  225.     The class should be a function that returns a table of 0-3 subtables containing the appropriate members of the class.
  226.     Any elements and metatables other than what is required in members gets ignored and is not used by the program.
  227.     If the object being created is from a derived class, the public and protected members of the base class will be inherited by this one.
  228.     The protected members will be set as a read-only metatable to the public members.
  229.     The result is the object that is returned to the user.
  230.     More details about the different types of members below.
  231. ]]
  233. --[[ members
  234.     Public members must be declared within a table in order to be used outside the class.
  235.     Public members can be accessed and changed externally.
  236.     This member type must be passed to the object constructor as 'public'.
  238.     Private members don't need to be declared within a table since they are not used outside the class and it would be redundant to do so, however it may be useful as a visual label for private variables.
  239.     Private members cannot be accessed or changed externally.
  240.     This is the only member type that does not get passed to the object constructor.
  242.     Protected members must be declared within a table just like public members.
  243.     Protected members are like a mix of public and private members.
  244.     They can be accessed externally, but they are read-only, meaning they cannot be changed externally, only internally.
  245.     The object itself is also read-only in the sense that new members cannot be externally added to the object table.
  246.     Methods within the class are allowed to add members to the object. Existing public members are not affected by this.
  247.     If a protected member is a table, the elements and metatables within that table can still be changed.
  248.     This member type must be passed to the object constructor as 'protected'.
  250.     Inherited members are the public and protected members from another class.
  251.     This is declared by creating a new object from the base class by calling the base class function.
  252.     Private members of the base class are not accessible at all to the derived class, but the protected and public members are. In addition, inherited protected members are fully accessible to the derived class, but will remain externally read-only when an object is created.
  253.     Methods from the base class will not be able to access any members from the derived class because the members created in the derived class are outside the scope of the base class, but methods in the derived class can access the members inherited from the base class. Inheritance only works one way.
  254.     The base class object must be passed to the object constructor as 'inherit'.
  256.     All types are optional but one (other than 'inherit') is technically required to contain the class constructor method.
  257.     All members need to be declared within the class function. More details on class functions below.
  258.     If the appropriate members (other than 'inherit') are not passed to the object constructor, they are created automatically as empty tables. If they exist but are not tables, an error gets thrown.
  260.     When declaring variables, don't make public, private or protected variables with the same name.
  261.     For example, don't have public.var and protected.var.
  262.     While this is technically valid because Lua treats these as two different things, an object called obj will only have access to the public variable when calling obj.var, treating the protected variable like its private.
  263.     Although, if you can find a valid use case for this then go nuts.
  264.     2021/07/29 - I found a valid use case for this. A read-only protected metatable can be used to reference a private table without allowing it to be changed externally.
  265.     For example:
  266.         local list = {[1] = "a"} ->  creates a private table with element 1 being "a".
  267.         protected.list = readOnly({}, list) -> creates a read-only protected table that references 'list'.
  268.         list[1] = "b" -> successfully changes list[1] to "b".
  269.         protected.list[1] = "c" -> throws the read-only error.
  270. ]]
  272. --[[ Derived Class Function
  273.     The class function is declared with arguments that match the arguments taken by the class constructor, if any.
  274.     Alternatively you can use ... to take any number of arguments and pass that to the constructor.
  275.     The constructor name should match the class name for consistency. It can be declared as private, public or protected.
  276.     At the end of your class constructor you need to pass the appropriate members as a single table argument to the object constructor, ommitting whatever is not used in the given class.
  278.     There's a neat quirk with Lua functions. If you're passing a single table argument to a function, you can call the function with the table itself without parentheses.
  279.     This is optional of course, but I think it looks nice if you pass the members to the object constructor like this.
  281.     The object returned by the object constructor must then be passed to a return statement.
  282.     The constructor method needs to be called and returned to the user at the end of the class function.
  283.     This way the constructor method is called every time an object is created.
  284.     It is also possible to create a class without a constructor method, in which case the constructed object would be returned directly to the user from the class function.
  285.     The final result will be a brand new object.
  287.     public.getVar1 can be changed externally, however doing so will permanently remove the ability to get the value of the private variable var1.
  288.     The function can not be changed externally to return private variables or change private/protected variables.
  289.     However, with this class the method can be reset due to its initial definition being inside the constructor method.
  290.     Though, since the constructor is public, if that gets changed, changing getVar1 really will be permanent; a new object will need to be created to reset it.
  292.     Printing obj.var6 will return the protected value inherited from the base class.
  293.     If the inherited class is ommitted, this will create a new protected variable within the object.
  294. ]]
  296. --[[ Base Class
  297.     This is a small class that will be used to demonstrate inheritance.
  298.     'protected.getVar3()' will return a nil value, even if it gets inherited by a class with the same variable name.
  299.     This is because the variable in the derived class is outside the scope of the base class where this function was declared. The derived class will full have access to protected.var6 because it was inherited from the base class.
  300. ]]
  302. --[[ Proof of Concept
  303.     This function shows proof of concept. Uncomment the function call if you want run it and see.
  304.     The commented lines in the test program are lines that will throw errors or return nil if they are run.
  305.     If you uncomment those and they do, everything is working exactly as it should.
  306. ]]
  308. -- As a final note, I primarily use Lua for ComputerCraft, so that's what this was written for, but I have not used any functions not standard to Lua in the object constructor, so it should work with any Lua application.
  309. -- This program was tested in CCEmuX which is a ComputerCraft "emulator" so that I don't have to start up all of Minecraft every time I want to make something in CC that's not explicitly Minecraft related.
  310. -- You can use this with ComputerCraft's os.loadAPI() to load the Class() function into a separate program file.
  311. -- Alternatively, you can use the require() function, which does effectively the same thing but is newer to ComputerCraft.
  312. -- If you can I highly recommend using require() instead because it's way more efficient, and it's a standard Lua function so it should work beyond ComputerCraft, but I have not tested this to be sure.
  314. --[[ Changelog
  315.     2021/08/18:
  316.         Exposed 'typeCheck' as a public function.
  317.         Added a comment indicating where example code starts.
  318.         Updated the documentation.
  319.     2021/07/30:
  320.         Fixed a bug caused by a typo.
  321.     2021/07/29:
  322.         The code that sets protected variables as a read-only metatable has been moved from the object constructor to its own global function so it can be used to set read-only tables beyond the scope of the object constructor.
  323.         I've appended the end of the instructions about members to reflect this change: I found a valid use case for different members with the same name. A read-only protected metatable can be used to reference a private table of the same name without allowing it to be changed externally.
  324.     Day 1 patch:
  325.         Fixed a bug from a last minute change I did before I posted it on pastebin.
  326.         Added an instruction to the comments about protected table variables that I didn't think of until now.
  327. ]]
RAW Paste Data