Skip to content

Instantly share code, notes, and snippets.

@Brugarolas
Created June 9, 2024 06:48
Show Gist options
  • Select an option

  • Save Brugarolas/f818066c2ff3ac36bbea9cece81bc963 to your computer and use it in GitHub Desktop.

Select an option

Save Brugarolas/f818066c2ff3ac36bbea9cece81bc963 to your computer and use it in GitHub Desktop.
Lua Stuff (WIP)
-- @file adv[anced]_clean_m[odules].lua
-- @brief A clean module system for Lua, with caching
-- @author Andrés Brugarolas Martínez (2023)
-- Store original environment for possible future uses
_G.main_env = getfenv(1)
-- Store function references for performance optimization
local getfenv_local = getfenv
local setfenv_local = setfenv
local setmetatable_local = setmetatable
local g_require_local = require
local pcall_local = pcall
local loadfile_local = loadfile
local ipairs_local = ipairs
local rawset_local = rawset
-- Cache for compiled modules
local __compiled_cache = {}
-- Track globals set by each module
local __module_globals = {}
-- List of the files which won't be unloaded
local __keep_in_memory = {
-- Lua globals
["base"] = true,
["string"] = true,
["package"] = true,
["_G"] = true,
["os"] = true,
["table"] = true,
["math"] = true,
["coroutine"] = true,
["debug"] = true,
["io"] = true,
-- LuaJIT
["bit"] = true,
["ffi"] = true,
["jit"] = true
}
-- Returns a functions that wraps the function passed as argument and
-- logs if an exception is thrown during execution returning nil and allowing
-- Lua continuing its execution flow, because we don't want a "corrupted" module
-- to affect the rest of the modules and compromise the execution of the whole
-- app/game/framework/server. If the returned nil is not handled in calling module
-- or if the module is a core module and it is needed for other modules to work,
-- app will crash somewhere else and execution will be stopped, but it's important
-- it does NOT happen here
-- Note: in case you are wondering why, this library was initially made as
-- a framework for modding a game
local function __createLoggingHighOrderFunction(func)
return function (...)
local ok, err = pcall_local(func, ...)
if not ok then
return nil, false
end
return ok, true
end
end
-- Function to load a file (with loadfile, not with require,
-- so we should handle package.loaded manually in calling
-- functions), compile it, and save the compiled module
-- in a cache to save time if we are loading it again
local function __loadfile_compiled(name)
local path = package.searchpath(name, package.path)
if not path then
error("Module '" .. name .. "' not found")
end
-- Check if already compiled and cached
if __compiled_cache[path] then
return __compiled_cache[path]
end
-- If not in cache, compile and store
local f, err = loadfile_local(path)
if not f then
error("Error loading module '" .. name .. "' from file '" .. path .. "':\n\t" .. err)
end
__compiled_cache[path] = f
return f
end
-- Merge the properties of the second table into the first (main) table
local function __merge_table(main_table, secondary_table)
if not secondary_table then return end
for k, v in pairs(secondary_table) do main_table[k] = v end
end
-- Declare module cleanly:
-- module is registered in package.loaded,
-- but not inserted in the global namespace
-- so it does not pollute global environment
-- it only gets loaded into _G if specifically
-- declared as "_G.name = value"
local function _module(modname, ...)
-- Convert module name to all lower case to avoid cases like "Main" and "main" potentially leading to separate tables
local modulename = string.lower(modname)
local _M = {} -- namespace for module
setfenv_local(2, _M)
-- Define for partial compatibility with module()
_M._M = _M
_M._NAME = modulename
-- Keeps track of global defined vars with "_G.name = value"
local global_vars = {}
-- Apply decorators to the module, like module_compiled.seeall
if ... then
for _, func in ipairs_local({...}) do
local decorator_defined_global_vars = func(_M)
__merge_table(global_vars, decorator_defined_global_vars)
end
end
package.loaded[modulename] = _M
__module_globals[modulename] = global_vars
end
-- Modified _seeall function to allow specific writes to _G
local function _seeall(_M)
local priv = {} -- private environment for module
local global_vars = {} -- keep track of global vars defined with "_G.name = value"
local privmt = {
__index = function(privat, k)
return _M[k] or _G[k]
end,
__newindex = function(t, k, v)
if string.sub(k, 1, 3) == "_G." then
local propertyName = string.sub(k, 4)
-- TODO Would this also works if the "global" var is defined inside a function?
-- TODO I think it will, but I have to check it anyway
_G[propertyName] = v
global_vars[propertyName] = v
else
_M[k] = v
end
end
}
setmetatable_local(priv, privmt)
setfenv_local(3, priv)
return global_vars -- return global vars so "module" function can register and keep track of them
end
-- Require module with logging and cache
local function _require(name)
-- Convert module name to all lower case to avoid cases like "Main" and "main" potentially leading to separate tables
local modulename = string.lower(name)
-- Check if the module is already loaded
if package.loaded[modulename] then
return package.loaded[modulename]
end
-- Use our compile and cache mechanism
local loader = __loadfile_compiled(modulename)
local result = loader(modulename)
package.loaded[modulename] = result
rawset_local(getfenv_local(2), modulename, result)
return result
end
-- Unload a module and clear
local function _unrequire(name)
-- Convert module name to all lower case to avoid cases like "Main" and "main" potentially leading to separate tables
local modulename = string.lower(name)
-- Remove global variables set by this module
local globals = __module_globals[modulename]
if globals then
for k, _ in pairs(globals) do
_G[k] = nil
end
__module_globals[modulename] = nil
end
-- Remove the module from package.loaded
package.loaded[modulename] = nil
end
-- Clean all loaded packages except the ones we have
-- defined to keep in memory (mostly lua core stuff and
-- framework core stuff to)
local function _unrequire_all()
-- Convert module name to all lower case to avoid cases like "Main" and "main" potentially leading to separate tables
for module_name, _ in pairs(package.loaded) do
if not __keep_in_memory[module_name] then
_unrequire(module_name)
end
end
end
-- Force require a module without recompiling,
-- it loads and executes again an already loaded module,
-- but it skips the actual "loading" and compilating part
local function _force_require(name)
-- Convert module name to all lower case to avoid cases like "Main" and "main" potentially leading to separate tables
local modulename = string.lower(name)
package.loaded[modulename] = nil
return _require(modulename)
end
-- It is the same as regular _require but:
-- 1) It allows you to pass as an argument the namespace table where all variables will be saved
-- 2) So multiple modules can share the same namespace
-- 3) It does not checks if module is already loaded in package.loaded and does not register module there
local function _namespace_require(modulename, namespace_table)
if package.loaded[modulename] then
return package.loaded[modulename]
end
local loader = __loadfile_compiled(modulename)
local t = namespace_table or {}
setmetatable_local(t, { __index = getfenv(1) })
setfenv_local(loader, t)
return loader(modulename)
end
-- Ironically, this module is not itself clean, so that it can be used with 'require'
module(...)
module = __createLoggingHighOrderFunction(_module)
seeall = _seeall
require = __createLoggingHighOrderFunction(_require)
unrequire = __createLoggingHighOrderFunction(_unrequire)
unrequire_all = __createLoggingHighOrderFunction(_unrequire_all)
force_require = __createLoggingHighOrderFunction(_force_require)
namespace_require = __createLoggingHighOrderFunction(_namespace_require)
-- Explicit return is not necessary, but can be kept for clarity
return _M
-- @file prototype.lua
-- @brief Lua prototype-based object-oriented programming system.
-- @author Andrés Brugarolas Martínez (2024)
local ClassFactory = {}
local useGlobalEnv = false
-- Start a namespace and execute the provided callback within it.
-- The garbage collector is stopped during the execution of the callback and resumed afterward.
-- If no namespace is provided, a new one is created.
-- The JIT threshold is lowered to minimum, and then restored, but before restoring the test
-- function is run twice, effectively JIT compiling whatever the function is running from what
-- is created in the callback.
function ClassFactory.defineCompiledInNamespace(callback, test, namespace)
assert(type(callback) == "function", "Expected a function for the callback.")
-- If no namespace is provided, create a new one
local actualNamespace
if useGlobalEnv then
actualNamespace = namespace or _G
else
actualNamespace = namespace or {}
end
-- Create the environment
local environment = setmetatable({}, {
__index = actualNamespace,
__newindex = actualNamespace
})
-- Save current JIT options
local original_opts = {}
for _, opt in ipairs{"hotloop", "hotexit", "tracerecord", "instunroll", "loopunroll", "callunroll", "recunroll", "sizemcode", "maxmcode", "maxtrace", "maxrecord", "maxirconst", "maxside", "maxsnap", "maxdse", "maxmcodegap", "maxmcodeuse", "maxphitopic", "maxtopicuse"} do
original_opts[opt] = jit.opt.get(opt)
end
-- Stop the garbage collector
collectgarbage("stop")
-- Set the environment for the callback and execute it
setfenv(callback, environment)()
-- Set the environment fand execute the test twice to force JIT compile what's defined on callback
local dummyEnvironment = {}
setmetatable(dummyEnvironment, { __index = environment })
-- Execute the test twice in its own environment
setfenv(test, dummyEnvironment)
test()
test()
-- Resume the garbage collector
collectgarbage("restart")
-- Restore the original JIT options
local opts_str = ""
for opt, value in pairs(original_opts) do
opts_str = opts_str .. opt .. "=" .. tostring(value) .. " "
end
jit_opt.start(opts_str:match("^%s*(.-)%s*$"))
-- Return the namespaces
return environment, dummyEnvironment
end
-- Clean up a namespace by setting all of its entries to nil.
-- If an entry has a destroy method, it is called before the entry is set to nil.
-- The garbage collector is stopped during the cleanup and resumed afterward.
function ClassFactory.cleanAutomaticallyNamespace(namespace, callback)
assert(type(callback) == "function", "Expected a function for the callback.")
assert(type(namespace) == "table", "Expected a table for the namespace.")
-- Stop the garbage collector
collectgarbage("stop")
-- Execute the callback to allow for manual cleanup
callback(namespace)
-- Clean up the namespace
for k, v in pairs(namespace) do
if type(v) == "table" and v.destroy and type(v.destroy) == "function" then
v:destroy()
end
namespace[k] = nil
end
-- Resume the garbage collector
collectgarbage("restart")
end
function ClassFactory.defineClassSystem()
-- Metaclass for creating new classes
Prototype = {
prototype = {},
mixins = {}
}
Mixin = {}
-- Setting Class's metatable to itself
setmetatable(Prototype, Prototype)
setmetatable(Mixin, Mixin)
-- Method to NOT instantiate a mixin
function Mixin:new(...)
error("You cannot instantiate a mixin")
end
-- Method to create a new mixin/trait
function Mixin:create(extendFunc)
local newMixin = {}
newMixin.super = self
setmetatable(newMixin, newMixin)
if extendFunc then
extendFunc(newMixin)
end
return newMixin
end
-- Method to extend a mixin/trait
function Mixin:extend(extendFunc)
local newMixin = {}
newMixin.super = self
-- Allows newMixin to use methods defined in self.super mixin
setmetatable(newMixin, { __index = self.super })
if extendFunc then
extendFunc(newMixin)
end
return newMixin
end
-- Method to create a new class
function Prototype:extend(extendFunc)
local newClass = {}
newClass.super = self
newClass.mixins = {}
newClass.prototype = {}
setmetatable(newClass, newClass)
-- Allows newClass to use methods defined in Class.prototype
setmetatable(newClass.prototype, { __index = self.prototype })
-- Allows newClass to use methods defined in Class.mixins
setmetatable(newClass.mixins, { __index = self.mixins })
if extendFunc then
extendFunc(newClass)
end
return newClass
end
-- Method to implement a mixin in a class
function Prototype:implement(mixin)
self.mixins[#self.mixins + 1] = mixin
return self
end
-- Method to instantiate a class
function Prototype:new(...)
if self == Prototype then
error("You cannot instantiate a meta-prototype")
end
local instance = setmetatable({}, self.prototype)
for _, mixin in ipairs(self.mixins) do
for propertyName, propertyValue in pairs(mixin) do
if type(propertyValue) == "function" then
instance[propertyName] = function(self, ...)
return propertyValue(self, ...)
end
else
instance[propertyName] = propertyValue
end
end
end
instance.class = self
instance.type = "instance"
if instance.constructor then
instance:constructor(...)
end
return instance
end
-- Basic methods for all classes
Prototype.prototype.isSame = function(self, other)
return self.class == other.class
end
Prototype.prototype.isEqual = function(self, other)
local function tables_are_equal(t1, t2)
-- Check if both are not tables or are nil
if type(t1) ~= 'table' or type(t2) ~= 'table' then
return t1 == t2
end
-- Check if they have the same keys and values
for k1, v1 in pairs(t1) do
local v2 = t2[k1]
if v2 == nil or not tables_are_equal(v1, v2) then
return false
end
end
-- Check if t2 has keys not in t1
for k2, _ in pairs(t2) do
if t1[k2] == nil then
return false
end
end
return true
end
return tables_are_equal(self, other)
end
Prototype.prototype.isDeepEqual = function(self, other)
if type(self) ~= 'function' or type(other) ~= 'function' then
return false
end
return string.dump(self) == string.dump(other)
end
Prototype.prototype.toString = function(self)
local function tableToString(tbl)
local result = {}
local isArray = #tbl > 0
if isArray then
for i, v in ipairs(tbl) do
table.insert(result, tostring(v))
end
return "[" .. table.concat(result, ", ") .. "]"
else
for k, v in pairs(tbl) do
local keyStr = type(k) == "string" and '"' .. k .. '"' or "[" .. tostring(k) .. "]"
table.insert(result, keyStr .. ": " .. tostring(v))
end
return "{" .. table.concat(result, ", ") .. "}"
end
end
local valueType = type(self)
if valueType == "table" then
return tableToString(self)
elseif valueType == "string" then
return '"' .. self .. '"'
elseif valueType == "number" or valueType == "boolean" then
return tostring(self)
elseif valueType == "function" then
return "<function>"
elseif valueType == "userdata" or valueType == "thread" then
return "<" .. valueType .. ">"
else
return "nil"
end
end
Prototype.prototype.isInstanceOf = function(self, aClass)
local currentClass = self.class
while currentClass ~= nil do
if currentClass == aClass then
return true
end
currentClass = currentClass.super
end
return false
end
-- Object prototype, more user-friendly and with table methods
Object = Prototype:extend(function (Object)
-- Populate Object.prototype with table methods
for k, v in pairs(table) do
Object.prototype[k] = function(self, ...)
return v(self, ...)
end
end
end)
-- Array Implementation
Array = Object:extend(function(Array)
-- Constructor
function Array.constructor(self, ...)
self.elements = {...}
end
-- Length method
function Array:length()
return #self.elements
end
-- Push method
function Array:push(element)
table.insert(self.elements, element)
end
-- Pop method
function Array:pop()
return table.remove(self.elements)
end
-- Map method
function Array:map(callback)
local newArray = Array:new()
for i, v in ipairs(self.elements) do
newArray:push(callback(v, i))
end
return newArray
end
-- Filter method
function Array:filter(callback)
local newArray = Array:new()
for i, v in ipairs(self.elements) do
if callback(v, i) then
newArray:push(v)
end
end
return newArray
end
-- Reduce method
function Array:reduce(callback, initialValue)
local accumulator = initialValue
for i, v in ipairs(self.elements) do
accumulator = callback(accumulator, v, i)
end
return accumulator
end
-- Divide method
function Array:divide(conditionFunction)
local match = Array:new()
local others = Array:new()
for _, v in ipairs(self.elements) do
if conditionFunction(v) then
match:push(v)
else
others:push(v)
end
end
return { match = match, others = others }
end
end)
-- End Array Implementation
-- Set Implementation
Set = Object:extend(function(Set)
-- Constructor
function Set.constructor(self, elements)
self.elements = {}
if elements then
for _, element in ipairs(elements) do
self.elements[element] = true
end
end
end
-- Add method
function Set:add(element)
self.elements[element] = true
end
-- Delete method
function Set:delete(element)
self.elements[element] = nil
end
-- Has method
function Set:has(element)
return self.elements[element] ~= nil
end
-- Clear method
function Set:clear()
self.elements = {}
end
-- Size method
function Set:size()
local count = 0
for _ in pairs(self.elements) do
count = count + 1
end
return count
end
-- Union method
function Set:union(otherSet)
local resultSet = Set:new()
for element in pairs(self.elements) do
resultSet:add(element)
end
for element in pairs(otherSet.elements) do
resultSet:add(element)
end
return resultSet
end
-- Intersection method
function Set:intersection(otherSet)
local resultSet = Set:new()
for element in pairs(self.elements) do
if otherSet:has(element) then
resultSet:add(element)
end
end
return resultSet
end
-- Difference method
function Set:difference(otherSet)
local resultSet = Set:new()
for element in pairs(self.elements) do
if not otherSet:has(element) then
resultSet:add(element)
end
end
return resultSet
end
-- FilterBySet method
function Set:filterBySet(anotherSet)
local resultSet = Set:new()
for element in pairs(self.elements) do
if not anotherSet:has(element) then
resultSet:add(element)
end
end
return resultSet
end
-- IsIncludedIn method
function Set:isIncludedIn(element)
for item in pairs(self.elements) do
if string.find(element, item) then
return true
end
end
return false
end
-- ToTable method (utility to convert Set to table)
function Set:toTable()
local tbl = {}
for element in pairs(self.elements) do
table.insert(tbl, element)
end
return tbl
end
end)
-- End Set Implementation
-- String Implementation
String = Object:extend(function(String)
-- Constructor
function String.constructor(self, value)
self.value = value or ""
end
-- Length method
function String:length()
return #self.value
end
-- CharAt method
function String:charAt(index)
return self.value:sub(index, index)
end
-- Substring method
function String:substring(startIndex, endIndex)
return String:new(self.value:sub(startIndex, endIndex))
end
-- ToUpperCase method
function String:toUpperCase()
return String:new(self.value:upper())
end
-- ToLowerCase method
function String:toLowerCase()
return String:new(self.value:lower())
end
-- Split method
function String:split(separator)
local result = {}
for match in (self.value .. separator):gmatch("(.-)" .. separator) do
table.insert(result, String:new(match))
end
return result
end
-- Trim method
function String:trim()
return String:new(self.value:match("^%s*(.-)%s*$"))
end
-- HashCode method
function String:hashCode()
local hash = 0
if self.value == "" then
return tostring(hash)
end
for i = 1, #self.value do
local char = self.value:byte(i)
hash = ((hash << 5) - hash) + char
hash = hash & hash -- convert to 32bit integer
end
hash = hash >> 0 -- convert signed to unsigned
return tostring(hash):gsub("%-",""):format("%x") -- remove any negative sign and convert to hex
end
end)
-- End String Implementation
-- Start HashMap Implementation
HashMap = Object:extend(function(HashMap)
-- Constructor
function HashMap.constructor(self)
self.map = {}
self.size = 0
end
-- Set method
function HashMap:set(key, value)
local keyString = tostring(key)
if self.map[keyString] == nil then
self.size = self.size + 1
end
self.map[keyString] = value
end
-- Get method
function HashMap:get(key)
return self.map[tostring(key)]
end
-- Has method
function HashMap:has(key)
return self.map[tostring(key)] ~= nil
end
-- Delete method
function HashMap:delete(key)
local keyString = tostring(key)
if self.map[keyString] ~= nil then
self.map[keyString] = nil
self.size = self.size - 1
return true
end
return false
end
-- Clear method
function HashMap:clear()
self.map = {}
self.size = 0
end
-- Size method
function HashMap:size()
return self.size
end
-- Keys method
function HashMap:keys()
local keys = {}
for k, _ in pairs(self.map) do
table.insert(keys, k)
end
return keys
end
-- Values method
function HashMap:values()
local values = {}
for _, v in pairs(self.map) do
table.insert(values, v)
end
return values
end
-- Entries method
function HashMap:entries()
local entries = {}
for k, v in pairs(self.map) do
table.insert(entries, { key = k, value = v })
end
return entries
end
-- ForEach method
function HashMap:forEach(callback)
for k, v in pairs(self.map) do
callback(k, v)
end
end
-- Clone method
function HashMap:clone()
local newMap = HashMap:new()
for k, v in pairs(self.map) do
newMap:set(k, v)
end
return newMap
end
-- ToString method (useful for debugging)
function HashMap:toString()
local result = "{"
for k, v in pairs(self.map) do
result = result .. k .. ": " .. tostring(v) .. ", "
end
if result:sub(-2) == ", " then
result = result:sub(1, -3)
end
result = result .. "}"
return result
end
end)
-- End HashMap Implementation
-- Start Queue Implementation
Queue = Object:extend(function(Queue)
-- Constructor
function Queue.constructor(self)
self.elements = {}
self.front = 0
self.back = -1
end
-- Enqueue method
function Queue:enqueue(element)
self.back = self.back + 1
self.elements[self.back] = element
end
-- Dequeue method
function Queue:dequeue()
if self.front > self.back then
return nil
end
local element = self.elements[self.front]
self.elements[self.front] = nil
self.front = self.front + 1
return element
end
-- Peek method
function Queue:peek()
return self.elements[self.front]
end
-- IsEmpty method
function Queue:isEmpty()
return self.front > self.back
end
-- Clear method
function Queue:clear()
self.elements = {}
self.front = 0
self.back = -1
end
-- Size method
function Queue:size()
return self.back - self.front + 1
end
end)
-- End Queue Implementation
-- Start Stack Implementation
Stack = Object:extend(function(Stack)
-- Constructor
function Stack.constructor(self)
self.elements = {}
self.top = 0
end
-- Push method
function Stack:push(element)
self.top = self.top + 1
self.elements[self.top] = element
end
-- Pop method
function Stack:pop()
if self.top == 0 then
return nil
end
local element = self.elements[self.top]
self.elements[self.top] = nil
self.top = self.top - 1
return element
end
-- Peek method
function Stack:peek()
return self.elements[self.top]
end
-- IsEmpty method
function Stack:isEmpty()
return self.top == 0
end
-- Clear method
function Stack:clear()
self.elements = {}
self.top = 0
end
-- Size method
function Stack:size()
return self.top
end
end)
-- End Stack Implementation
-- Start Deque Implementation
Deque = Object:extend(function(Deque)
-- Constructor
function Deque.constructor(self)
self.elements = {}
self.front = 0
self.back = -1
end
-- PushFront method
function Deque:pushFront(element)
self.front = self.front - 1
self.elements[self.front] = element
end
-- PushBack method
function Deque:pushBack(element)
self.back = self.back + 1
self.elements[self.back] = element
end
-- PopFront method
function Deque:popFront()
if self.front > self.back then
return nil
end
local element = self.elements[self.front]
self.elements[self.front] = nil
self.front = self.front + 1
return element
end
-- PopBack method
function Deque:popBack()
if self.front > self.back then
return nil
end
local element = self.elements[self.back]
self.elements[self.back] = nil
self.back = self.back - 1
return element
end
-- PeekFront method
function Deque:peekFront()
return self.elements[self.front]
end
-- PeekBack method
function Deque:peekBack()
return self.elements[self.back]
end
-- IsEmpty method
function Deque:isEmpty()
return self.front > self.back
end
-- Clear method
function Deque:clear()
self.elements = {}
self.front = 0
self.back = -1
end
-- Size method
function Deque:size()
return self.back - self.front + 1
end
end)
-- End Deque Implementation
-- Start PriorityQueue Implementation
PriorityQueue = Object:extend(function(PriorityQueue)
-- Constructor
function PriorityQueue.constructor(self)
self.elements = {}
self.size = 0
end
-- Enqueue method
function PriorityQueue:enqueue(element, priority)
self.size = self.size + 1
local index = self.size
self.elements[index] = { element = element, priority = priority }
while index > 1 do
local parentIndex = math.floor(index / 2)
if self.elements[index].priority >= self.elements[parentIndex].priority then
break
end
self.elements[index], self.elements[parentIndex] = self.elements[parentIndex], self.elements[index]
index = parentIndex
end
end
-- Dequeue method
function PriorityQueue:dequeue()
if self.size == 0 then
return nil
end
local element = self.elements[1].element
self.elements[1] = self.elements[self.size]
self.elements[self.size] = nil
self.size = self.size - 1
local index = 1
while true do
local leftIndex = index * 2
local rightIndex = index * 2 + 1
local smallestIndex = index
if leftIndex <= self.size and self.elements[leftIndex].priority < self.elements[smallestIndex].priority then
smallestIndex = leftIndex
end
if rightIndex <= self.size and self.elements[rightIndex].priority < self.elements[smallestIndex].priority then
smallestIndex = rightIndex
end
if smallestIndex == index then
break
end
self.elements[index], self.elements[smallestIndex] = self.elements[smallestIndex], self.elements[index]
index = smallestIndex
end
return element
end
-- Peek method
function PriorityQueue:peek()
return self.elements[1].element
end
-- IsEmpty method
function PriorityQueue:isEmpty()
return self.size == 0
end
-- Clear method
function PriorityQueue:clear()
self.elements = {}
self.size = 0
end
-- Size method
function PriorityQueue:size()
return self.size
end
end)
-- End PriorityQueue Implementation
-- Start Graph Implementation
Graph = Object:extend(function(Graph)
-- Constructor
function Graph.constructor(self)
self.vertices = {}
self.edges = {}
end
-- AddVertex method
function Graph:addVertex(vertex)
self.vertices[vertex] = true
self.edges[vertex] = {}
end
-- AddEdge method
function Graph:addEdge(vertex1, vertex2, weight)
if not self.vertices[vertex1] or not self.vertices[vertex2] then
return
end
if not self.edges[vertex1][vertex2] then
self.edges[vertex1][vertex2] = weight
end
end
-- RemoveVertex method
function Graph:removeVertex(vertex)
self.vertices[vertex] = nil
self.edges[vertex] = nil
for _, edges in pairs(self.edges) do
edges[vertex] = nil
end
end
-- RemoveEdge method
function Graph:removeEdge(vertex1, vertex2)
if not self.vertices[vertex1] or not self.vertices[vertex2] then
return
end
if self.edges[vertex1][vertex2] then
self.edges[vertex1][vertex2] = nil
end
end
-- HasVertex method
function Graph:hasVertex(vertex)
return self.vertices[vertex] ~= nil
end
-- HasEdge method
function Graph:hasEdge(vertex1, vertex2)
return self.edges[vertex1][vertex2] ~= nil
end
-- GetWeight method
function Graph:getWeight(vertex1, vertex2)
return self.edges[vertex1][vertex2]
end
-- GetNeighbors method
function Graph:getNeighbors(vertex)
local neighbors = {}
for neighbor in pairs(self.edges[vertex]) do
table.insert(neighbors, neighbor)
end
return neighbors
end
end)
-- End Graph Implementation
-- Start DirectedGraph Implementation
DirectedGraph = Object:extend(function(DirectedGraph)
-- Constructor
function DirectedGraph.constructor(self)
self.vertices = {}
self.edges = {}
end
-- AddVertex method
function DirectedGraph:addVertex(vertex)
self.vertices[vertex] = true
self.edges[vertex] = {}
end
-- AddEdge method
function DirectedGraph:addEdge(vertex1, vertex2, weight)
if not self.vertices[vertex1] or not self.vertices[vertex2] then
return
end
if not self.edges[vertex1][vertex2] then
self.edges[vertex1][vertex2] = weight
end
end
-- RemoveVertex method
function DirectedGraph:removeVertex(vertex)
self.vertices[vertex] = nil
self.edges[vertex] = nil
for _, edges in pairs(self.edges) do
edges[vertex] = nil
end
end
-- RemoveEdge method
function DirectedGraph:removeEdge(vertex1, vertex2)
if not self.vertices[vertex1] or not self.vertices[vertex2] then
return
end
if self.edges[vertex1][vertex2] then
self.edges[vertex1][vertex2] = nil
end
end
-- HasVertex method
function DirectedGraph:hasVertex(vertex)
return self.vertices[vertex] ~= nil
end
-- HasEdge method
function DirectedGraph:hasEdge(vertex1, vertex2)
return self.edges[vertex1][vertex2] ~= nil
end
-- GetWeight method
function DirectedGraph:getWeight(vertex1, vertex2)
return self.edges[vertex1][vertex2]
end
-- GetNeighbors method
function DirectedGraph:getNeighbors(vertex)
local neighbors = {}
for neighbor in pairs(self.edges[vertex]) do
table.insert(neighbors, neighbor)
end
return neighbors
end
end)
-- End DirectedGraph Implementation
-- Start DirectedAcyclicGraph Implementation
DirectedAcyclicGraph = Object:extend(function(DirectedAcyclicGraph)
-- Constructor
function DirectedAcyclicGraph.constructor(self)
self.vertices = {}
self.edges = {}
end
-- AddVertex method
function DirectedAcyclicGraph:addVertex(vertex)
self.vertices[vertex] = true
self.edges[vertex] = {}
end
-- AddEdge method
function DirectedAcyclicGraph:addEdge(vertex1, vertex2, weight)
if not self.vertices[vertex1] or not self.vertices[vertex2] then
return
end
if not self.edges[vertex1][vertex2] then
self.edges[vertex1][vertex2] = weight
end
end
-- RemoveVertex method
function DirectedAcyclicGraph:removeVertex(vertex)
self.vertices[vertex] = nil
self.edges[vertex] = nil
for _, edges in pairs(self.edges) do
edges[vertex] = nil
end
end
-- RemoveEdge method
function DirectedAcyclicGraph:removeEdge(vertex1, vertex2)
if not self.vertices[vertex1] or not self.vertices[vertex2] then
return
end
if self.edges[vertex1][vertex2] then
self.edges[vertex1][vertex2] = nil
end
end
-- HasVertex method
function DirectedAcyclicGraph:hasVertex(vertex)
return self.vertices[vertex] ~= nil
end
-- HasEdge method
function DirectedAcyclicGraph:hasEdge(vertex1, vertex2)
return self.edges[vertex1][vertex2] ~= nil
end
-- GetWeight method
function DirectedAcyclicGraph:getWeight(vertex1, vertex2)
return self.edges[vertex1][vertex2]
end
-- GetNeighbors method
function DirectedAcyclicGraph:getNeighbors(vertex)
local neighbors = {}
for neighbor in pairs(self.edges[vertex]) do
table.insert(neighbors, neighbor)
end
return neighbors
end
-- TopologicalSort method
function DirectedAcyclicGraph:topologicalSort()
local visited = {}
local stack = Stack:new()
local function visit(vertex)
if visited[vertex] then
return
end
visited[vertex] = true
for neighbor in pairs(self.edges[vertex]) do
visit(neighbor)
end
stack:push(vertex)
end
for vertex in pairs(self.vertices) do
visit(vertex)
end
local result = {}
while not stack:isEmpty() do
table.insert(result, stack:pop())
end
return result
end
end)
-- End DirectedAcyclicGraph Implementation
-- Start RedBlackTree Implementation
-- Define colors
local RED = true
local BLACK = false
-- Node class
local RedBlackTreeNode = Object:extend(function(RedBlackTreeNode)
function RedBlackTreeNode.constructor(self, key, value, color)
self.key = key
self.value = value
self.color = color
self.left = nil
self.right = nil
self.parent = nil
end
function RedBlackTreeNode:isRed()
return self.color == RED
end
function RedBlackTreeNode:setColor(color)
self.color = color
end
end)
-- RedBlackTree class
RedBlackTree = Object:extend(function(RedBlackTree)
function RedBlackTree.constructor(self)
self.root = nil
end
function RedBlackTree:rotateLeft(node)
local rightChild = node.right
node.right = rightChild.left
if rightChild.left ~= nil then
rightChild.left.parent = node
end
rightChild.parent = node.parent
if node.parent == nil then
self.root = rightChild
elseif node == node.parent.left then
node.parent.left = rightChild
else
node.parent.right = rightChild
end
rightChild.left = node
node.parent = rightChild
end
function RedBlackTree:rotateRight(node)
local leftChild = node.left
node.left = leftChild.right
if leftChild.right ~= nil then
leftChild.right.parent = node
end
leftChild.parent = node.parent
if node.parent == nil then
self.root = leftChild
elseif node == node.parent.right then
node.parent.right = leftChild
else
node.parent.left = leftChild
end
leftChild.right = node
node.parent = leftChild
end
function RedBlackTree:fixInsert(node)
while node.parent and node.parent:isRed() do
if node.parent == node.parent.parent.left then
local uncle = node.parent.parent.right
if uncle and uncle:isRed() then
node.parent:setColor(BLACK)
uncle:setColor(BLACK)
node.parent.parent:setColor(RED)
node = node.parent.parent
else
if node == node.parent.right then
node = node.parent
self:rotateLeft(node)
end
node.parent:setColor(BLACK)
node.parent.parent:setColor(RED)
self:rotateRight(node.parent.parent)
end
else
local uncle = node.parent.parent.left
if uncle and uncle:isRed() then
node.parent:setColor(BLACK)
uncle:setColor(BLACK)
node.parent.parent:setColor(RED)
node = node.parent.parent
else
if node == node.parent.left then
node = node.parent
self:rotateRight(node)
end
node.parent:setColor(BLACK)
node.parent.parent:setColor(RED)
self:rotateLeft(node.parent.parent)
end
end
end
self.root:setColor(BLACK)
end
function RedBlackTree:insert(key, value)
local newNode = RedBlackTreeNode:new(key, value, RED)
if self.root == nil then
self.root = newNode
self.root:setColor(BLACK)
return
end
local currentNode = self.root
local parentNode = nil
while currentNode ~= nil do
parentNode = currentNode
if key < currentNode.key then
currentNode = currentNode.left
elseif key > currentNode.key then
currentNode = currentNode.right
else
currentNode.value = value
return
end
end
newNode.parent = parentNode
if key < parentNode.key then
parentNode.left = newNode
else
parentNode.right = newNode
end
self:fixInsert(newNode)
end
function RedBlackTree:search(key)
local currentNode = self.root
while currentNode ~= nil do
if key < currentNode.key then
currentNode = currentNode.left
elseif key > currentNode.key then
currentNode = currentNode.right
else
return currentNode.value
end
end
return nil
end
function RedBlackTree:inOrderTraversal(node, callback)
if node ~= nil then
self:inOrderTraversal(node.left, callback)
callback(node)
self:inOrderTraversal(node.right, callback)
end
end
function RedBlackTree:preOrderTraversal(node, callback)
if node ~= nil then
callback(node)
self:preOrderTraversal(node.left, callback)
self:preOrderTraversal(node.right, callback)
end
end
function RedBlackTree:postOrderTraversal(node, callback)
if node ~= nil then
self:postOrderTraversal(node.left, callback)
self:postOrderTraversal(node.right, callback)
callback(node)
end
end
function RedBlackTree:traverseInOrder(callback)
self:inOrderTraversal(self.root, callback)
end
function RedBlackTree:traversePreOrder(callback)
self:preOrderTraversal(self.root, callback)
end
function RedBlackTree:traversePostOrder(callback)
self:postOrderTraversal(self.root, callback)
end
end)
-- End RedBlackTree Implementation
-- Start Trie Implementation
-- TrieNode class
local TrieNode = Object:extend(function(TrieNode)
function TrieNode.constructor(self)
self.children = {}
self.isEndOfWord = false
end
end)
-- Trie class
Trie = Object:extend(function(Trie)
function Trie.constructor(self)
self.root = TrieNode:new()
end
function Trie:insert(word)
local currentNode = self.root
for i = 1, #word do
local char = word:sub(i, i)
if not currentNode.children[char] then
currentNode.children[char] = TrieNode:new()
end
currentNode = currentNode.children[char]
end
currentNode.isEndOfWord = true
end
function Trie:search(word)
local currentNode = self.root
for i = 1, #word do
local char = word:sub(i, i)
if not currentNode.children[char] then
return false
end
currentNode = currentNode.children[char]
end
return currentNode.isEndOfWord
end
function Trie:delete(word)
local function deleteHelper(node, word, index)
if index == #word then
if not node.isEndOfWord then
return false
end
node.isEndOfWord = false
return next(node.children) == nil
end
local char = word:sub(index + 1, index + 1)
if not node.children[char] then
return false
end
local shouldDeleteNode = deleteHelper(node.children[char], word, index + 1)
if shouldDeleteNode then
node.children[char] = nil
return next(node.children) == nil
end
return false
end
deleteHelper(self.root, word, 0)
end
end)
-- End Trie Implementation
-- Start BinaryHeap Implementation
-- BinaryHeap class
BinaryHeap = Object:extend(function(BinaryHeap)
function BinaryHeap.constructor(self, compareFunction)
self.elements = {}
self.compareFunction = compareFunction or function(a, b) return a < b end
end
function BinaryHeap:insert(element)
table.insert(self.elements, element)
local index = #self.elements
while index > 1 do
local parentIndex = math.floor(index / 2)
if self.compareFunction(self.elements[index], self.elements[parentIndex]) then
break
end
self.elements[index], self.elements[parentIndex] = self.elements[parentIndex], self.elements[index]
index = parentIndex
end
end
function BinaryHeap:extract()
if #self.elements == 0 then
return nil
end
local element = self.elements[1]
self.elements[1] = self.elements[#self.elements]
table.remove(self.elements)
local index = 1
while true do
local leftIndex = index * 2
local rightIndex = index * 2 + 1
local smallestIndex = index
if leftIndex <= #self.elements and self.compareFunction(self.elements[leftIndex], self.elements[smallestIndex]) then
smallestIndex = leftIndex
end
if rightIndex <= #self.elements and self.compareFunction(self.elements[rightIndex], self.elements[smallestIndex]) then
smallestIndex = rightIndex
end
if smallestIndex == index then
break
end
self.elements[index], self.elements[smallestIndex] = self.elements[smallestIndex], self.elements[index]
index = smallestIndex
end
return element
end
function BinaryHeap:peek()
return self.elements[1]
end
function BinaryHeap:isEmpty()
return #self.elements == 0
end
function BinaryHeap:size()
return #self.elements
end
end)
-- End BinaryHeap Implementation
-- Start AVLTree Implementation
-- AVLTreeNode class
local AVLTreeNode = Object:extend(function(AVLTreeNode)
function AVLTreeNode.constructor(self, key, value)
self.key = key
self.value = value
self.height = 1
self.left = nil
self.right = nil
end
end)
-- AVLTree class
AVLTree = Object:extend(function(AVLTree)
function AVLTree.constructor(self)
self.root = nil
end
function AVLTree:height(node)
return node and node.height or 0
end
function AVLTree:rotateRight(node)
local leftChild = node.left
node.left = leftChild.right
leftChild.right = node
node.height = math.max(self:height(node.left), self:height(node.right)) + 1
leftChild.height = math.max(self:height(leftChild.left), self:height(leftChild.right)) + 1
return leftChild
end
function AVLTree:rotateLeft(node)
local rightChild = node.right
node.right = rightChild.left
rightChild.left = node
node.height = math.max(self:height(node.left), self:height(node.right)) + 1
rightChild.height = math.max(self:height(rightChild.left), self:height(rightChild.right)) + 1
return rightChild
end
function AVLTree:rotateLeftRight(node)
node.left = self:rotateLeft(node.left)
return self:rotateRight(node)
end
function AVLTree:rotateRightLeft(node)
node.right = self:rotateRight(node.right)
return self:rotateLeft(node)
end
function AVLTree:balance(node)
local balanceFactor = self:height(node.left) - self:height(node.right)
if balanceFactor > 1 then
if self:height(node.left.left) >= self:height(node.left.right) then
node = self:rotateRight(node)
else
node = self:rotateLeftRight(node)
end
elseif balanceFactor < -1 then
if self:height(node.right.right) >= self:height(node.right.left) then
node = self:rotateLeft(node)
else
node = self:rotateRightLeft(node)
end
end
return node
end
function AVLTree:insert(node, key, value)
if node == nil then
return AVLTreeNode:new(key, value)
end
if key < node.key then
node.left = self:insert(node.left, key, value)
elseif key > node.key then
node.right = self:insert(node.right, key, value)
else
node.value = value
return node
end
node.height = math.max(self:height(node.left), self:height(node.right)) + 1
return self:balance(node)
end
function AVLTree:insertNode(key, value)
self.root = self:insert(self.root, key, value)
end
function AVLTree:search(node, key)
if node == nil then
return nil
end
if key < node.key then
return self:search(node.left, key)
elseif key > node.key then
return self:search(node.right, key)
else
return node.value
end
end
function AVLTree:searchNode(key)
return self:search(self.root, key)
end
function AVLTree:inOrderTraversal(node, callback)
if node ~= nil then
self:inOrderTraversal(node.left, callback)
callback(node)
self:inOrderTraversal(node.right, callback)
end
end
function AVLTree:preOrderTraversal(node, callback)
if node ~= nil then
callback(node)
self:preOrderTraversal(node.left, callback)
self:preOrderTraversal(node.right, callback)
end
end
function AVLTree:postOrderTraversal(node, callback)
if node ~= nil then
self:postOrderTraversal(node.left, callback)
self:postOrderTraversal(node.right, callback)
callback(node)
end
end
function AVLTree:traverseInOrder(callback)
self:inOrderTraversal(self.root, callback)
end
function AVLTree:traversePreOrder(callback)
self:preOrderTraversal(self.root, callback)
end
function AVLTree:traversePostOrder(callback)
self:postOrderTraversal(self.root, callback)
end
function AVLTree:delete(node, key)
if node == nil then
return nil
end
if key < node.key then
node.left = self:delete(node.left, key)
elseif key > node.key then
node.right = self:delete(node.right, key)
else
if node.left == nil or node.right == nil then
local temp = node.left or node.right
if temp == nil then
node = nil
else
node = temp
end
else
local temp = node.right
while temp.left do
temp = temp.left
end
node.key = temp.key
node.value = temp.value
node.right = self:delete(node.right, temp.key)
end
end
if node == nil then
return node
end
node.height = math.max(self:height(node.left), self:height(node.right)) + 1
return self:balance(node)
end
function AVLTree:deleteNode(key)
self.root = self:delete(self.root, key)
end
function AVLTree:clear()
self.root = nil
end
function AVLTree:isEmpty()
return self.root == nil
end
function AVLTree:size()
local count = 0
local function countNodes(node)
if node ~= nil then
countNodes(node.left)
count = count + 1
countNodes(node.right)
end
end
countNodes(self.root)
return count
end
function AVLTree:height()
return self:height(self.root)
end
function AVLTree:isBalanced()
local function checkHeight(node)
if node == nil then
return 0
end
local leftHeight = checkHeight(node.left)
if leftHeight == -1 then
return -1
end
local rightHeight = checkHeight(node.right)
if rightHeight == -1 then
return -1
end
local heightDiff = leftHeight - rightHeight
if math.abs(heightDiff) > 1 then
return -1
else
return math.max(leftHeight, rightHeight) + 1
end
end
return checkHeight(self.root) ~= -1
end
function AVLTree:isComplete()
local function checkComplete(node, index, count)
if node == nil then
return true
end
if index >= count then
return false
end
return checkComplete(node.left, 2 * index + 1, count) and checkComplete(node.right, 2 * index + 2, count)
end
local function countNodes(node)
if node == nil then
return 0
end
return 1 + countNodes(node.left) + countNodes(node.right)
end
return checkComplete(self.root, 0, countNodes(self.root))
end
function AVLTree:isFull()
local function checkFull(node)
if node == nil then
return true
end
if node.left == nil and node.right == nil then
return true
end
if node.left and node.right then
return checkFull(node.left) and checkFull(node.right)
end
return false
end
return checkFull(self.root)
end
function AVLTree:isPerfect()
local function checkPerfect(node, depth, level)
if node == nil then
return true
end
if node.left == nil and node.right == nil then
return depth == level + 1
end
if node.left == nil or node.right == nil then
return false
end
return checkPerfect(node.left, depth, level + 1) and checkPerfect(node.right, depth, level + 1)
end
return checkPerfect(self.root, self:height(self.root), 0)
end
function AVLTree:isSymmetric()
local function checkSymmetric(left, right)
if left == nil and right == nil then
return true
end
if left == nil or right == nil then
return false
end
return left.key == right.key and checkSymmetric(left.left, right.right) and checkSymmetric(left.right, right.left)
end
return checkSymmetric(self.root, self.root)
end
end)
-- End AVLTree Implementation
-- Start DoublyLinkedList Implementation
-- DoublyLinkedListNode class
local DoublyLinkedListNode = Object:extend(function(DoublyLinkedListNode)
function DoublyLinkedListNode.constructor(self, value)
self.value = value
self.next = nil
self.prev = nil
end
end)
-- DoublyLinkedList class
DoublyLinkedList = Object:extend(function(DoublyLinkedList)
function DoublyLinkedList.constructor(self)
self.head = nil
self.tail = nil
self.size = 0
end
function DoublyLinkedList:pushFront(value)
local newNode = DoublyLinkedListNode:new(value)
if self.head == nil then
self.head = newNode
self.tail = newNode
else
newNode.next = self.head
self.head.prev = newNode
self.head = newNode
end
self.size = self.size + 1
end
function DoublyLinkedList:pushBack(value)
local newNode = DoublyLinkedListNode:new(value)
if self.tail == nil then
self.head = newNode
self.tail = newNode
else
newNode.prev = self.tail
self.tail.next = newNode
self.tail = newNode
end
self.size = self.size + 1
end
function DoublyLinkedList:popFront()
if self.head == nil then
return nil
end
local value = self.head.value
self.head = self.head.next
if self.head == nil then
self.tail = nil
else
self.head.prev = nil
end
self.size = self.size - 1
return value
end
function DoublyLinkedList:popBack()
if self.tail == nil then
return nil
end
local value = self.tail.value
self.tail = self.tail.prev
if self.tail == nil then
self.head = nil
else
self.tail.next = nil
end
self.size = self.size - 1
return value
end
function DoublyLinkedList:peekFront()
return self.head and self.head.value or nil
end
function DoublyLinkedList:peekBack()
return self.tail and self.tail.value or nil
end
function DoublyLinkedList:isEmpty()
return self.size == 0
end
function DoublyLinkedList:size()
return self.size
end
function DoublyLinkedList:clear()
self.head = nil
self.tail = nil
self.size = 0
end
function DoublyLinkedList:toArray()
local array = {}
local currentNode
currentNode = self.head
while currentNode do
table.insert(array, currentNode.value)
currentNode = currentNode.next
end
return array
end
end)
-- End DoublyLinkedList Implementation
-- Start SkipList Implementation
local MAX_LEVEL = 16 -- Max level for the skip list
-- SkipListNode class
local SkipListNode = Object:extend(function(SkipListNode)
function SkipListNode.constructor(self, key, value, level)
self.key = key
self.value = value
self.forward = {}
for i = 1, level do
self.forward[i] = nil
end
end
end)
-- SkipList class
SkipList = Object:extend(function(SkipList)
function SkipList.constructor(self)
self.level = 1
self.header = SkipListNode:new(nil, nil, MAX_LEVEL)
end
function SkipList:randomLevel()
local level = 1
while math.random() < 0.5 and level < MAX_LEVEL do
level = level + 1
end
return level
end
function SkipList:insert(key, value)
local update = {}
local current = self.header
for i = self.level, 1, -1 do
while current.forward[i] and current.forward[i].key < key do
current = current.forward[i]
end
update[i] = current
end
current = current.forward[1]
if current and current.key == key then
current.value = value
else
local level = self:randomLevel()
if level > self.level then
for i = self.level + 1, level do
update[i] = self.header
end
self.level = level
end
local newNode = SkipListNode:new(key, value, level)
for i = 1, level do
newNode.forward[i] = update[i].forward[i]
update[i].forward[i] = newNode
end
end
end
function SkipList:search(key)
local current = self.header
for i = self.level, 1, -1 do
while current.forward[i] and current.forward[i].key < key do
current = current.forward[i]
end
end
current = current.forward[1]
if current and current.key == key then
return current.value
else
return nil
end
end
function SkipList:delete(key)
local update = {}
local current = self.header
for i = self.level, 1, -1 do
while current.forward[i] and current.forward[i].key < key do
current = current.forward[i]
end
update[i] = current
end
current = current.forward[1]
if current and current.key == key then
for i = 1, self.level do
if update[i].forward[i] ~= current then
break
end
update[i].forward[i] = current.forward[i]
end
while self.level > 1 and self.header.forward[self.level] == nil do
self.level = self.level - 1
end
end
end
function SkipList:traverse(callback)
local current = self.header.forward[1]
while current do
callback(current)
current = current.forward[1]
end
end
end)
-- End SkipList Implementation
-- Start TrieMap Implementation
-- TrieMap class
TrieMap = Object:extend(function(TrieMap)
function TrieMap.constructor(self)
self.root = {}
end
function TrieMap:insert(key, value)
local current = self.root
for i = 1, #key do
local char = key:sub(i, i)
if not current[char] then
current[char] = {}
end
current = current[char]
end
current.value = value
end
function TrieMap:search(key)
local current = self.root
for i = 1, #key do
local char = key:sub(i, i)
if not current[char] then
return nil
end
current = current[char]
end
return current.value
end
function TrieMap:delete(key)
local function deleteHelper(node, key, index)
if index == #key then
if not node.value then
return false
end
node.value = nil
return next(node) == nil
end
local char = key:sub(index + 1, index + 1)
if not node[char] then
return false
end
local shouldDeleteNode = deleteHelper(node[char], key, index + 1)
if shouldDeleteNode then
node[char] = nil
return next(node) == nil
end
return false
end
deleteHelper(self.root, key, 0)
end
end)
-- End TrieMap Implementation
-- Start Callable Implementation
-- Callable class
Callable = Object:extend(function(Callable)
function Callable.constructor(self, func)
self.func = func
end
function Callable:call(...)
return self.func(...)
end
end)
-- End Callable Implementation
-- Start Thenable Implementation
-- Thenable class
Thenable = Object:extend(function(Thenable)
function Thenable.constructor(self, func)
self.func = func
end
function Thenable:thenAfter(onFulfilled, onRejected)
return Thenable:new(function(resolve, reject)
self.func(function(value)
if onFulfilled then
resolve(onFulfilled(value))
else
resolve(value)
end
end, function(reason)
if onRejected then
resolve(onRejected(reason))
else
reject(reason)
end
end)
end)
end
end)
-- End Thenable Implementation
-- Start Promise Implementation
-- Promise class
Promise = Object:extend(function(Promise)
function Promise.constructor(self, executor)
self.status = "pending"
self.value = nil
self.reason = nil
self.onFulfilled = {}
self.onRejected = {}
local function resolve(value)
if self.status == "pending" then
self.status = "fulfilled"
self.value = value
for _, callback in ipairs(self.onFulfilled) do
callback(value)
end
end
end
local function reject(reason)
if self.status == "pending" then
self.status = "rejected"
self.reason = reason
for _, callback in ipairs(self.onRejected) do
callback(reason)
end
end
end
executor(resolve, reject)
end
function Promise:thenAfter(onFulfilled, onRejected)
return Promise:new(function(resolve, reject)
if self.status == "fulfilled" then
if onFulfilled then
resolve(onFulfilled(self.value))
else
resolve(self.value)
end
elseif self.status == "rejected" then
if onRejected then
resolve(onRejected(self.reason))
else
reject(self.reason)
end
else
table.insert(self.onFulfilled, function(value)
if onFulfilled then
resolve(onFulfilled(value))
else
resolve(value)
end
end)
table.insert(self.onRejected, function(reason)
if onRejected then
resolve(onRejected(reason))
else
reject(reason)
end
end)
end
end)
end
end)
-- End Promise Implementation
-- Start Future Implementation
-- Future class
Future = Object:extend(function(Future)
function Future.constructor(self, executor)
self.status = "pending"
self.value = nil
self.reason = nil
self.onFulfilled = {}
self.onRejected = {}
local function resolve(value)
if self.status == "pending" then
self.status = "fulfilled"
self.value = value
for _, callback in ipairs(self.onFulfilled) do
callback(value)
end
end
end
local function reject(reason)
if self.status == "pending" then
self.status = "rejected"
self.reason = reason
for _, callback in ipairs(self.onRejected) do
callback(reason)
end
end
end
executor(resolve, reject)
end
function Future:thenAfter(onFulfilled, onRejected)
return Future:new(function(resolve, reject)
if self.status == "fulfilled" then
if onFulfilled then
resolve(onFulfilled(self.value))
else
resolve(self.value)
end
elseif self.status == "rejected" then
if onRejected then
resolve(onRejected(self.reason))
else
reject(self.reason)
end
else
table.insert(self.onFulfilled, function(value)
if onFulfilled then
resolve(onFulfilled(value))
else
resolve(value)
end
end)
table.insert(self.onRejected, function(reason)
if onRejected then
resolve(onRejected(reason))
else
reject(reason)
end
end)
end
end)
end
end)
-- End Future Implementation
-- Start Deferred Implementation
-- Deferred class
Deferred = Object:extend(function(Deferred)
function Deferred.constructor(self)
self.promise = Promise:new(function(resolve, reject)
self.resolve = resolve
self.reject = reject
end)
return self
end
end)
-- End Deferred Implementation
-- Start Task Implementation
-- Task class
Task = Object:extend(function(Task)
function Task.constructor(self, executor)
self.executor = executor
end
function Task:run()
self.executor()
end
end)
-- End Task Implementation
-- Start Channel Implementation
-- Channel class
Channel = Object:extend(function(Channel)
function Channel.constructor(self)
self.queue = DoublyLinkedList:new()
self.isClosed = false
self.onReceive = Callable:new(function() end)
end
function Channel:send(value)
if self.isClosed then
return
end
self.queue:pushBack(value)
self.onReceive:call()
end
function Channel:receive()
if self.queue:isEmpty() then
return nil
end
return self.queue:popFront()
end
function Channel:close()
self.isClosed = true
end
end)
-- End Channel Implementation
-- Start Event Implementation
-- Event class
Event = Object:extend(function(Event)
function Event.constructor(self)
self.listeners = {}
end
function Event:subscribe(listener)
table.insert(self.listeners, listener)
end
function Event:unsubscribe(listener)
for i = #self.listeners, 1, -1 do
if self.listeners[i] == listener then
table.remove(self.listeners, i)
end
end
end
function Event:emit(...)
for _, listener in ipairs(self.listeners) do
listener(...)
end
end
end)
-- End Event Implementation
-- Start Worker Implementation
-- Worker class
Worker = Object:extend(function(Worker)
function Worker.constructor(self, func)
self.func = func
self.channel = Channel:new()
self.isRunning = false
self.thread = coroutine.create(function()
while true do
local value = self.channel:receive()
if value == nil then
break
end
self.func(value)
end
end)
end
function Worker:run()
self.isRunning = true
coroutine.resume(self.thread)
end
function Worker:send(value)
self.channel:send(value)
end
function Worker:close()
self.channel:close()
end
end)
-- End Worker Implementation
-- Start Mutex Implementation
-- Mutex class
Mutex = Object:extend(function(Mutex)
function Mutex.constructor(self)
self.queue = DoublyLinkedList:new()
self.isLocked = false
end
function Mutex:lock()
local deferred = Deferred:new()
if self.isLocked then
self.queue:pushBack(deferred)
else
self.isLocked = true
deferred.resolve()
end
return deferred.promise
end
function Mutex:unlock()
if not self.isLocked then
return
end
local deferred = self.queue:popFront()
if deferred then
deferred.resolve()
else
self.isLocked = false
end
end
end)
-- End Mutex Implementation
-- Start Semaphore Implementation
-- Semaphore class
Semaphore = Object:extend(function(Semaphore)
function Semaphore.constructor(self, count)
self.count = count
self.queue = DoublyLinkedList:new()
end
function Semaphore:wait()
local deferred = Deferred:new()
if self.count > 0 then
self.count = self.count - 1
deferred.resolve()
else
self.queue:pushBack(deferred)
end
return deferred.promise
end
function Semaphore:signal()
local deferred = self.queue:popFront()
if deferred then
deferred.resolve()
else
self.count = self.count + 1
end
end
end)
-- End Semaphore Implementation
-- Start Barrier Implementation
-- Barrier class
Barrier = Object:extend(function(Barrier)
function Barrier.constructor(self, count)
self.count = count
self.queue = DoublyLinkedList:new()
end
function Barrier:wait()
local deferred = Deferred:new()
self.queue:pushBack(deferred)
if self.queue:size() == self.count then
for i = 1, self.count do
self.queue:popFront():resolve()
end
end
return deferred.promise
end
end)
-- End Barrier Implementation
-- Start ConditionVariable Implementation
-- ConditionVariable class
ConditionVariable = Object:extend(function(ConditionVariable)
function ConditionVariable.constructor(self)
self.queue = DoublyLinkedList:new()
end
function ConditionVariable:wait(mutex)
local deferred = Deferred:new()
self.queue:pushBack(deferred)
mutex:unlock()
return deferred.promise
end
function ConditionVariable:signal()
local deferred = self.queue:popFront()
if deferred then
deferred.resolve()
end
end
end)
-- End ConditionVariable Implementation
-- Start ReadWriteLock Implementation
-- ReadWriteLock class
ReadWriteLock = Object:extend(function(ReadWriteLock)
function ReadWriteLock.constructor(self)
self.mutex = Mutex:new()
self.resource = Mutex:new()
self.readers = 0
end
function ReadWriteLock:readLock()
local deferred = Deferred:new()
self.mutex:lock():thenAfter(function()
self.readers = self.readers + 1
if self.readers == 1 then
self.resource:lock()
end
self.mutex:unlock()
deferred.resolve()
end)
return deferred.promise
end
function ReadWriteLock:readUnlock()
local deferred = Deferred:new()
self.mutex:lock():thenAfter(function()
self.readers = self.readers - 1
if self.readers == 0 then
self.resource:unlock()
end
self.mutex:unlock()
deferred.resolve()
end)
return deferred.promise
end
function ReadWriteLock:writeLock()
return self.resource:lock()
end
function ReadWriteLock:writeUnlock()
return self.resource:unlock()
end
end)
-- End ReadWriteLock Implementation
-- Start Latch Implementation
-- Latch class
Latch = Object:extend(function(Latch)
function Latch.constructor(self, count)
self.count = count
self.queue = DoublyLinkedList:new()
end
function Latch:wait()
local deferred = Deferred:new()
self.queue:pushBack(deferred)
if self.queue:size() == self.count then
for i = 1, self.count do
self.queue:popFront():resolve()
end
end
return deferred.promise
end
end)
-- End Latch Implementation
-- Start CountDownLatch Implementation
-- CountDownLatch class
CountDownLatch = Object:extend(function(CountDownLatch)
function CountDownLatch.constructor(self, count)
self.count = count
self.queue = DoublyLinkedList:new()
end
function CountDownLatch:countDown()
local deferred = self.queue:popFront()
if deferred then
deferred.resolve()
else
self.count = self.count - 1
end
end
function CountDownLatch:await()
local deferred = Deferred:new()
if self.count > 0 then
self.queue:pushBack(deferred)
else
deferred.resolve()
end
return deferred.promise
end
end)
-- End CountDownLatch Implementation
-- Start CyclicBarrier Implementation
-- CyclicBarrier class
CyclicBarrier = Object:extend(function(CyclicBarrier)
function CyclicBarrier.constructor(self, count)
self.count = count
self.queue = DoublyLinkedList:new()
end
function CyclicBarrier:await()
local deferred = Deferred:new()
self.queue:pushBack(deferred)
if self.queue:size() == self.count then
for i = 1, self.count do
self.queue:popFront():resolve()
end
end
return deferred.promise
end
end)
-- End CyclicBarrier Implementation
-- Start Phaser Implementation
-- Phaser class
Phaser = Object:extend(function(Phaser)
function Phaser.constructor(self, count)
self.count = count
self.queue = DoublyLinkedList:new()
end
function Phaser:arrive()
local deferred = self.queue:popFront()
if deferred then
deferred.resolve()
else
self.count = self.count - 1
end
end
function Phaser:await()
local deferred = Deferred:new()
if self.count > 0 then
self.queue:pushBack(deferred)
else
deferred.resolve()
end
return deferred.promise
end
end)
-- End Phaser Implementation
-- Start Exchanger Implementation
-- Exchanger class
Exchanger = Object:extend(function(Exchanger)
function Exchanger.constructor(self)
self.queue = DoublyLinkedList:new()
end
function Exchanger:exchange(value)
local deferred = Deferred:new()
self.queue:pushBack({ value = value, deferred = deferred })
if self.queue:size() == 2 then
local first = self.queue:popFront()
local second = self.queue:popFront()
first.deferred.resolve(second.value)
second.deferred.resolve(first.value)
end
return deferred.promise
end
end)
-- End Exchanger Implementation
-- Start Lock Implementation
-- Lock class
Lock = Object:extend(function(Lock)
function Lock.constructor(self)
self.mutex = Mutex:new()
end
function Lock:lock()
return self.mutex:lock()
end
function Lock:unlock()
return self.mutex:unlock()
end
end)
-- End Lock Implementation
-- Start ObjectPool Implementation
-- ObjectPool class
ObjectPool = Object:extend(function(ObjectPool)
function ObjectPool.constructor(self, factory, size)
self.factory = factory
self.size = size
self.pool = DoublyLinkedList:new()
for i = 1, size do
self.pool:pushBack(factory())
end
end
function ObjectPool:acquire()
if self.pool:isEmpty() then
return self.factory()
end
return self.pool:popFront()
end
function ObjectPool:release(object)
if self.pool:size() < self.size then
self.pool:pushBack(object)
end
end
end)
-- End ObjectPool Implementation
-- Start ObjectPoolFactory Implementation
-- ObjectPoolFactory class
ObjectPoolFactory = Object:extend(function(ObjectPoolFactory)
function ObjectPoolFactory.constructor(self, factory)
self.factory = factory
end
function ObjectPoolFactory:create(size)
return ObjectPool:new(self.factory, size)
end
end)
-- End ObjectPoolFactory Implementation
-- Start Observable Implementation
-- Observable class
Observable = Object:extend(function(Observable)
function Observable.constructor(self)
self.listeners = {}
end
function Observable:subscribe(listener)
table.insert(self.listeners, listener)
end
function Observable:unsubscribe(listener)
for i = #self.listeners, 1, -1 do
if self.listeners[i] == listener then
table.remove(self.listeners, i)
end
end
end
function Observable:notify(...)
for _, listener in ipairs(self.listeners) do
listener(...)
end
end
end)
-- End Observable Implementation
-- Start Subject Implementation
-- Subject class
Subject = Object:extend(function(Subject)
function Subject.constructor(self)
self.observers = {}
end
function Subject:subscribe(observer)
table.insert(self.observers, observer)
end
function Subject:unsubscribe(observer)
for i = #self.observers, 1, -1 do
if self.observers[i] == observer then
table.remove(self.observers, i)
end
end
end
function Subject:notify(...)
for _, observer in ipairs(self.observers) do
observer:notify(...)
end
end
end)
-- End Subject Implementation
-- Start Mediator Implementation
-- Mediator class
Mediator = Object:extend(function(Mediator)
function Mediator.constructor(self)
self.handlers = {}
end
function Mediator:subscribe(event, handler)
if not self.handlers[event] then
self.handlers[event] = {}
end
table.insert(self.handlers[event], handler)
end
function Mediator:unsubscribe(event, handler)
if self.handlers[event] then
for i = #self.handlers[event], 1, -1 do
if self.handlers[event][i] == handler then
table.remove(self.handlers[event], i)
end
end
end
end
function Mediator:publish(event, ...)
if self.handlers[event] then
for _, handler in ipairs(self.handlers[event]) do
handler(...)
end
end
end
end)
-- End Mediator Implementation
-- Start Command Implementation
-- Command class
Command = Object:extend(function(Command)
function Command.constructor(self, func)
self.func = func
end
function Command:execute()
self.func()
end
end)
-- End Command Implementation
-- Start CommandFactory Implementation
-- CommandFactory class
CommandFactory = Object:extend(function(CommandFactory)
function CommandFactory.constructor(self)
self.commands = {}
end
function CommandFactory:createCommand(name, func)
self.commands[name] = Command:new(func)
end
function CommandFactory:executeCommand(name)
self.commands[name]:execute()
end
end)
-- End CommandFactory Implementation
-- Start CommandInvoker Implementation
-- CommandInvoker class
CommandInvoker = Object:extend(function(CommandInvoker)
function CommandInvoker.constructor(self)
self.commands = {}
end
function CommandInvoker:execute(command)
table.insert(self.commands, command)
command:execute()
end
end)
-- End CommandInvoker Implementation
-- Start CommandProcessor Implementation
-- CommandProcessor class
CommandProcessor = Object:extend(function(CommandProcessor)
function CommandProcessor.constructor(self)
self.invoker = CommandInvoker:new()
end
function CommandProcessor:process(command)
self.invoker:execute(command)
end
end)
-- End CommandProcessor Implementation
-- Start Observer Implementation
-- Observer class
Observer = Object:extend(function(Observer)
function Observer.constructor(self)
self.subjects = {}
end
function Observer:subscribe(subject)
table.insert(self.subjects, subject)
end
function Observer:unsubscribe(subject)
for i = #self.subjects, 1, -1 do
if self.subjects[i] == subject then
table.remove(self.subjects, i)
end
end
end
function Observer:notify(...)
for _, subject in ipairs(self.subjects) do
subject:notify(...)
end
end
end)
-- End Observer Implementation
-- Start Visitor Implementation
-- Visitor class
Visitor = Object:extend(function(Visitor)
function Visitor.constructor(self)
self.elements = {}
end
function Visitor:visit(element)
table.insert(self.elements, element)
end
end)
-- End Visitor Implementation
-- Start Computed Implementation
-- Computed class
Computed = Object:extend(function(Computed)
function Computed.constructor(self, func)
self.func = func
end
function Computed:compute()
return self.func()
end
end)
-- End Computed Implementation
end
function ClassFactory.testClassFactory()
-- Example class definition
local MyClass = Object:extend(function (MyClass)
function MyClass.constructor(self, x, y)
self.x = x
self.y = y
end
function MyClass.static.resetX(self)
self.x = nil
end
end)
-- Example mixin definition
local MyMixin = Mixin:create(function (MyMixin)
function MyMixin:printX()
print(self.x)
end
end)
Mixin:extend(function (Mixin)
function Mixin:printY()
print(self.y)
end
end)
MyClass:implement(MyMixin)
local myInstance = MyClass:new(10, 25)
-- Example Object implementation
local myObject = Object:new({ x = 10, y = 25 })
local myOtherObject = Object:new({ x = 10, y = 25 })
-- Example Array implementation
local myArray = Array:new(1, 2, 3, 4, 5, 6)
local divided = myArray:divide(function(element) return element % 2 == 0 end)
local mapped = myArray:map(function(element) return element * 2 end)
local filtered = myArray:filter(function(element) return element % 2 == 0 end)
local reduced = myArray:reduce(function(accumulator, element) return accumulator + element end, 0)
myArray:push(7)
myArray:pop()
myArray:length()
-- Example Set implementation
local mySet = Set:new({"apple", "banana", "cherry"})
local anotherSet = Set:new({"banana", "cherry", "date"})
local filteredSet = mySet:filterBySet(anotherSet)
local isIncluded = mySet:isIncludedIn("I have an apple and a banana.")
local unionSet = mySet:union(anotherSet)
local intersectionSet = mySet:intersection(anotherSet)
local differenceSet = mySet:difference(anotherSet)
local size = mySet:size()
mySet:clear()
-- Example String implementation
local myString = String:new("hello world")
local myStringValue = myString:toUpperCase().value -- "HELLO WORLD"
local myStringHash = myString:hashCode() -- prints the hash code of "hello world"
local myStringSplit = myString:split(" ") -- {"hello", "world"}
local myStringTrim = String:new(" hello world "):trim().value -- "hello world"
local myStringSubstring = myString:substring(1, 5).value -- "hello"
local myStringCharAt = myString:charAt(1) -- "h"
local myStringLength = myString:length() -- 11
-- Example HashMap implementation
local myMap = HashMap:new()
myMap:set("one", 1)
myMap:set("two", 2)
local myMapGetOne = myMap:get("one") -- 1
local myMapHasTwo myMap:has("two") -- true
myMap:delete("one")
local myMapHasOne = myMap:has("one") -- false
local myMapSize = myMap:size() -- 1
local myMapString = myMap:toString() -- {two: 2}
local keys = myMap:keys()
local values = myMap:values()
local entries = myMap:entries()
myMap:forEach(function(key, value)
local file = key .. ": " .. value
end)
local clonedMap = myMap:clone()
-- Example HashSet implementation
local mySet = HashSet:new()
mySet:add("apple")
mySet:add("banana")
local mySetHasApple = mySet:has("apple") -- true
mySet:delete("apple")
local mySetHasBanana = mySet:has("banana") -- false
local mySetSize = mySet:size() -- 1
local mySetString = mySet:toString() -- {banana}
local mySetKeys = mySet:keys()
local mySetValues = mySet:values()
local mySetEntries = mySet:entries()
mySet:forEach(function(value)
local file = value
end)
local clonedSet = mySet:clone()
-- Example Stack implementation
local myStack = Stack:new()
myStack:push(1)
myStack:push(2)
local myStackPeek = myStack:peek() -- 2
local myStackPop = myStack:pop() -- 2
local myStackIsEmpty = myStack:isEmpty() -- false
local myStackSize = myStack:size() -- 1
myStack:clear()
-- Example Queue implementation
local myQueue = Queue:new()
myQueue:enqueue(1)
myQueue:enqueue(2)
local myQueuePeek = myQueue:peek() -- 1
local myQueueDequeue = myQueue:dequeue() -- 1
local myQueueIsEmpty = myQueue:isEmpty() -- false
local myQueueSize = myQueue:size() -- 1
myQueue:clear()
-- Example PriorityQueue implementation
local myPriorityQueue = PriorityQueue:new()
myPriorityQueue:enqueue(1)
myPriorityQueue:enqueue(2)
local myPriorityQueuePeek = myPriorityQueue:peek() -- 1
local myPriorityQueueDequeue = myPriorityQueue:dequeue() -- 1
local myPriorityQueueIsEmpty = myPriorityQueue:isEmpty() -- false
local myPriorityQueueSize = myPriorityQueue:size() -- 1
myPriorityQueue:clear()
-- Example Deque implementation
local myDeque = Deque:new()
myDeque:pushFront(1)
myDeque:pushBack(2)
local myDequePeekFront = myDeque:peekFront() -- 1
local myDequePeekBack = myDeque:peekBack() -- 2
local myDequePopFront = myDeque:popFront() -- 1
local myDequePopBack = myDeque:popBack() -- 2
local myDequeIsEmpty = myDeque:isEmpty() -- true
local myDequeSize = myDeque:size() -- 0
myDeque:clear()
-- Example AVLTree implementation
local myAVLTree = AVLTree:new()
myAVLTree:insert(1)
myAVLTree:insert(2)
local myAVLTreeHasOne = myAVLTree:has(1) -- true
local myAVLTreeHasTwo = myAVLTree:has(2) -- true
local myAVLTreeHasThree = myAVLTree:has(3) -- false
local myAVLTreeHeight = myAVLTree:height() -- 1
local myAVLTreeSize = myAVLTree:size() -- 2
myAVLTree:delete(1)
myAVLTree:clear()
end
local classDefinitions, testEnvironment = ClassFactory.defineCompiledInNamespace(ClassFactory.defineClassSystem, ClassFactory.testClassFactory)
ClassFactory.cleanAutomaticallyNamespace(testEnvironment)
return classDefinitions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment