{"id":18740048,"url":"https://github.com/mwilsnd/lava","last_synced_at":"2025-07-22T21:36:55.939Z","repository":{"id":148232837,"uuid":"239172812","full_name":"mwilsnd/lava","owner":"mwilsnd","description":"OOP for Lua","archived":false,"fork":false,"pushed_at":"2020-12-08T14:12:25.000Z","size":47,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-20T04:36:11.152Z","etag":null,"topics":["lua","lua-library"],"latest_commit_sha":null,"homepage":null,"language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mwilsnd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-02-08T17:23:36.000Z","updated_at":"2024-05-27T13:10:47.000Z","dependencies_parsed_at":"2023-05-19T12:00:25.651Z","dependency_job_id":null,"html_url":"https://github.com/mwilsnd/lava","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mwilsnd/lava","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwilsnd%2Flava","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwilsnd%2Flava/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwilsnd%2Flava/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwilsnd%2Flava/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mwilsnd","download_url":"https://codeload.github.com/mwilsnd/lava/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwilsnd%2Flava/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266578700,"owners_count":23951150,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lua","lua-library"],"created_at":"2024-11-07T15:38:19.420Z","updated_at":"2025-07-22T21:36:55.900Z","avatar_url":"https://github.com/mwilsnd.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lava\nLava is an easy to use OOP library for Lua 5.1/5.2/5.3 and LuaJIT\n\n### Note: Lava is currently experimental software\n\nTo use Lava in your project, just `require \"lava\"`.\nFor examples, read the example scripts in `examples/`\n\nThe lava module will return a table of function exports for you to use:\n* abstract( string name )\n* singleton( string name )\n* class( string name )\n* interface( string name )\n* mixin( string name )\n* is_a( instance, definition )\n* implements( instance, interface )\n* validClass( instance )\n* loadClass( string filePath )\n\n## Basic Overview\nClasses in lava come in 5 flavors - `class`, `abstract`, `singleton`, `interface` and `mixin`.\nLava supports abstract classes, interfaces, mixins, singletons and single inheritance.\n\n#### class\n`class` is the primary object type. Classes can be instantiated with the `:New()` syntax,\ninherit from base classes using the `extends \"Base\" : from \"namespace\"` syntax,\nimplement interfaces using the `implements \"Interface\" : from \"namespace\"` syntax and\ncan have mixins applied using the `mixin \"Mixin\" : from \"namespace\"` syntax.\n`class` objects require that a constructor method be defined somewhere along the parent-child hierarchy. Constructors are defined by creating a function inside the class named `Initialize`.\n\n#### singleton\n`singleton` behaves just like `class` with the exception being only 1 instance of a singleton can ever exist at the same time.\nSubsequent calls to `:New()` will return the same instance.\n\n#### abstract\n`abstract` objects, like `class` and `singleton` can implement interfaces and apply mixins, but cannot be instantiated. `abstract` exists purely to be extended on by child objects.\n\n#### interface\n`interface` objects are nothing more than a collection of methods and, optionally, member variables.\nMethods from interfaces are not inherited by classes that implement them per-se, rather a class that implements an interface is required to at least define the same set of methods as they appear in the interface.\nFailing to define a method in a class that is mentioned in an `implemented` interface will result in an error.\nParent/base classes can implement interfaces, be aware however that implementing the same interface in a parent and child class will result in an error.\n\n#### mixin\n`mixin` objects are simple collections of methods and member variables which can be \"mixed in\" to other objects.\nIf a mixin is applied twice, say for example once in a base class and then again in a child class, an error will be thrown.\nIf method or member variable names from a mixin conflict with other method/member names found in the class, that too will throw an error.\n\n`interface` and `mixin` types cannot utilize inheritance via `extends`, nor can they implement interfaces or utilize mixins.\n\n## Writing and loading lava classes\nAll lava class types must follow the 1 class per-file rule - lava modifies the metatable of the main scope of the defining classes environment (to enable 'magic' methods and variables mentioned further down, amongst other things).\nLava will enforce this rule for you, throwing an error if you attempt to create more than 1 class definition in a file.\n\nWhen you've written a lava class file, you can load it using the exported `loadClass( string filePath )` method.\nIf the Lua environment you are using does not support `loadfile`/`dofile` then you will have to modify the `loadClass` method or create your own inside the lava module.\n\n## 'Magic' variables and class definition scope\nWhen defining a class in lava, global functions are redirected to the class definition and a number of variables visible only inside the class definition are made available. These are:\n* this\n* super\n* shared\n* super_shared\n\nAdditionally, the following methods become available:\n* accessor\n* getter\n* setter\n* shared_block\n* finally\n\nLet's run through each of these and describe what they do.\n\n#### this\n`this` refers to the definition object of the class itself.\n`this` can be used to store custom data on the class definition or to access the `members` and `methods` tables for manually inserting data.\n\n#### super\n`super` refers to the method table of the parent class, if one exists. `super` is used primarily to call overloaded functions from child classes.\n\n#### shared\n`shared` refers to the shared table defined by the method `shared_block`. Shared blocks are used to store data which is shared amongst all instances of a class.\n\n#### super_shared\n`super_shared` refers to the shared table of the parent class, if one exists. Parent/base classes can define shared blocks and they can be accessed by child classes via `super_shared`\n\n#### shared_block\n`shared_block` is a function used to define the existence and initial layout of a shared block in a class.\nShared blocks are populated with this initial table when the first instance of a class is created and are cleared when the last instance is garbage collected.\n\n#### getter\n`getter` is a function which generates Get() functions on the class. To use getter, pass in a single string as follows:\n```lua\ngetter \"MethodName-\u003ememberVariable\"\n```\n`MethodName` will become `GetMethodName()` and will return `instance.memberVariable` when called.\n\n#### setter\n`setter`, much like `getter`, generates Set() functions on the class. To use setter, pass in a single string as follows:\n```lua\nsetter \"MethodName-\u003ememberVariable\"\n```\n`MethodName` will become `SetMethodName( value )` and will set `instance.memberVariable` to `value` when called.\n\n#### accessor\n`accessor` is a combination of `getter` and `setter`, creating both a Get() and Set() method.\n\n#### finally\n`finally` can be used to invoke a method when a class definition is finalized - allowing you to then register your class with other systems or instantiate it right away.\nAn example:\n```lua\ndo singleton \"Example\"\n\t{}\n\n\tfunction Initialize( self )\n\tend\n\n\tfinally( function()\n\t\t_G.mySingleton = this:New()\n\tend )\nend\n```\n\n## Working with class instances\nLet's define and create an instance of a simple class.\n\nFirst, we load lava and place some methods in the global scope:\n```lua\nlava = require \"lava\"\n\n-- Placing these methods in _G is optional\nabstract = lava.abstract\nsingleton = lava.singleton\nclass = lava.class\ninterface = lava.interface\nmixin = lava.mixin\nis_a = lava.is_a\nvalidClass = lava.validClass\n```\n\nNext we create a new file and write our class definition:\n```lua\ndo class \"Example\" : namespace \"examples\"\n  {\n    memberVariable = \"\",\n  }\n  \n  accessor \"Message-\u003ememberVariable\"\n  \n  function Initialize( self, messageString )\n    self.memberVariable = messageString\n  end\nend\n```\n\nWe can load and create an instance and manipulate the class like so:\n```lua\nlava.loadClass( \"MyClass.lua\" )\nlocal myInstance = examples.Example:New( \"Hello World!\" )\nprint( myInstance:GetMessage() ) -- prints \"Hello World!\"\n\nmyInstance:SetMessage( \"1234\" )\nprint( myInstance:GetMessage() ) -- prints \"1234\"\n```\n\nIf we want to grab all active instances of a class, we can do so:\n```lua\nfor _, instance in pairs( examples.Example:GetInstances() ) do\n  print( instance:GetMessage() )\nend\n```\n\nInstances will be automatically removed when garbage collected, however you can explicitly remove them:\n```lua\nmyInstance:Remove() -- Will remove this class from the instance list and invoke examples.Example:OnRemove(), if defined\n```\n**NOTE:** If you manually :Remove() a class, the instance variable will still be reachable!\nYou should set all references to your instance to `nil` after calling :Remove() or undefined behavior could result.\n\nWhen garbage collected, classes that define a `__GC()` method will have that method invoked.\n```lua\ndo class \"Example\" : namespace \"examples\"\n  {\n    memberVariable = \"\",\n  }\n  \n  accessor \"Message-\u003ememberVariable\"\n  \n  function Initialize( self, messageString )\n    self.memberVariable = messageString\n  end\n\n  function __GC( self )\n    print \"Goodbye, cruel world!\"\n  end\nend\n```\n**NOTE:** For Lua 5.1 and LuaJIT, the __GC method is achieved by proxying your instance through zero-sized userdata via `newporxy`.\nThus, class instances in 5.1 and JIT are actually `userdata` and not `table`\n\n## Namespaces\nIn lava, you can specify a namespace for your class/interface/mixin by using the `namespace` method as so:\n```lua\ndo class \"Example\" : namespace \"examples\"\n```\n\n`namespace` supports nested tables too!\n```lua\ndo class \"Example\" : namespace \"examples.basic.myStuff\"\n```\n\nIf you are extending a class that is in a different namespace from your child class, you can use `from` to specify where to find the parent class\n```lua\ndo class \"ChildExample\" : namespace \"examples\" : extends \"ParentExample\" : from \"examples.parents\"\n```\n\nIf namespaces are the same, you can omit `from` and lava will assume it can be found in the same namespace.\nIf none of your classes specify a namespace, your classes will be placed in `_G`.\n\n## Interfaces\nInterfaces in lava are simple prototype classes, implementing no other interfaces, using mixins or extending from other classes.\nA simple interface looks like this:\n```lua\ndo interface \"Printable\" : namespace \"interfaces\"\n  {}\n\n  function Print( self )end\nend\n```\n\nAnd an example using this interface in a class:\n```lua\ndo class \"Example\" : namespace \"examples\"\n  : implements \"Printable\" : from \"interfaces\"\n  {\n    m_strMessage = \"Hello World!\",\n  }\n\n  function Initialize( self )\n  end\n\n  function Print( self )\n    print( self.m_strMessage )\n  end\nend\n```\n\nIf we forget to define the function `Print` inside our class, lava will throw an error.\nWe can query if a class implements an interface like so:\n```lua\nif lava.implements( instance, interfaces.Printable ) then\n  instance:Print()\nend\n```\n\n## Mixins\nMixins are basic classes containing methods and variables that may not contain other mixins, implement interfaces or extend from other classes.\nHere is a basic mixin example:\n```lua\ndo mixin \"Position\" : namespace \"mixins\"\n  {\n    x = 0,\n    y = 0,\n  }\n\n  accessor \"X-\u003ex\"\n  accessor \"Y-\u003ey\"\n\n  function SetPos( self, x, y )\n    self.x = x\n    self.y = y\n  end\n\n  function GetPos( self )\n    return self.x, self.y\n  end\nend\n```\n\nWe can use the above mixin like so:\n```lua\ndo class \"Person\" : namespace \"examples\"\n  : mixin \"Position\" : from \"mixins\"\n  {\n    name = \"\",\n  }\n\n  function Initialize( self, name )\n    self.name = name\n  end\nend\n```\n\n```lua\nlocal bob = examples.Person:New( \"Bob\" )\nbob:SetPos( 1, 2 )\nprint( bob:GetPos() )\nbob:SetX( 3 )\nprint( bob:GetPos() )\n```\n\n## Inheritance\nFor another example, we will extend a class and overload one of it's methods\n```lua\ndo abstract \"Base\" : namespace \"examples.bases\"\n  {\n    message = \"\",\n  }\n\n  function SetMessage( self, message )\n    self.message = message\n  end\nend\n```\n```lua\ndo class \"Child\" : namespace \"examples\" : extends \"Base\" : from \"examples.bases\"\n  {}\n\n  function Initialize( self, message )\n    self.message = message\n  end\n\n  function SetMessage( self, message )\n    super.SetMessage( self, message )\n    print( message )\n  end\nend\n```\nNow, calling `SetMessage` on our child instance both sets the message variable and prints the message.\n\nIf you wish to prevent people from extending your classes, you can mark a class as `final`:\n```lua\ndo class \"Example\" : namespace \"examples\" : final()\n  {}\n  \n  function Initialize( self )\n  end\nend\n```\n\n## Shared blocks\nShared blocks can be a useful tool in certain situations. Let's look at how to use them.\n```lua\ndo class \"Example\" : namespace \"examples\"\n  {\n    myMessage = \"\",\n  }\n\n  shared_block {\n    ourMessage = \"\",\n  }\n\n  accessor \"MyMessage-\u003emyMessage\"\n\n  function Initialize( self, message )\n    self.myMessage = message\n  end\n\n  function SetOurMessage( self, message )\n    shared.ourMessage = message\n  end\n\n  function GetOurMessage( self, message )\n    return shared.ourMessage\n  end\nend\n```\n\n```lua\nlocal a = examples.Example:New( \"Hello\" )\nlocal b = examples.Example:New( \"World\" )\n\na:SetOurMessage( \"Hey There!\" )\nprint( b:GetOurMessage() ) -- prints \"Hey There!\"\n```\n\nShared blocks can be shared from parent to child via `super_shared`. Let's extend our example and try it out:\n```lua\ndo class \"ChildA\" : namespace \"examples\" : extends \"Example\"\n  {}\n\n  shared_block {\n    ourMessage = \"\",\n  }\n\n  function SetOurMessage( self, message )\n    shared.ourMessage = message\n  end\n\n  function GetOurMessage( self, message )\n    return shared.ourMessage\n  end\n\n  function SetParentMessage( self, message )\n    super_shared.ourMessage = message\n  end\n\n  function GetParentMessage( self, message )\n    return super_shared.ourMessage\n  end\nend\n```\n```lua\ndo class \"ChildB\" : namespace \"examples\" : extends \"Example\"\n  {}\n\n  shared_block {\n    ourMessage = \"\",\n  }\n\n  function SetOurMessage( self, message )\n    shared.ourMessage = message\n  end\n\n  function GetOurMessage( self, message )\n    return shared.ourMessage\n  end\n\n  function SetParentMessage( self, message )\n    super_shared.ourMessage = message\n  end\n\n  function GetParentMessage( self, message )\n    return super_shared.ourMessage\n  end\nend\n```\n```lua\nlocal a = examples.ChildA:New( \"Hello\" )\nlocal b = examples.ChildB:New( \"World\" )\n\na:SetOurMessage( \"Hey There!\" )\nprint( b:GetOurMessage() ) -- prints nothing, as ChildA and ChildB have different shared blocks\n\na:SetParentMessage( \"Hey There!\" )\nprint( b:GetParentMessage() ) -- prints \"Hey There!\", as ChildA and ChildB extend from the same parent with a shared block\n```\n\n**NOTE:** Shared blocks are an advanced feature. Be aware that calling a parent method which reads or writes from `shared` will try to read from the child's shared block and not the parent's!\nYou can only read from a parent's shared block via `super_shared`.\n\n## Common issues\n\n### Class instances inside a definition member block\nWhen defining a class, you may be tempted to place an instance of another class in the members block. Doing this is undefined behavior and should be avoided - Copied instances are not correctly registered with the lava library and strange issues can result.\nInstead, just instantiate your member classes inside the constructor or another method.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmwilsnd%2Flava","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmwilsnd%2Flava","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmwilsnd%2Flava/lists"}