2025-10-21 00:40:14 +02:00

648 lines
14 KiB
Lua

------------------------------------------------ LevelOOPer Library 2.2.6 ------------------------------------------------
function _G.pairs(t)
local mt = getmetatable(t)
if mt and type(mt.__pairs) == "function" then
return mt.__pairs(t)
else
return next, t, nil
end
end
local function fread(file)
local f = fs.open(file,"r")
local txt = f.readAll()
f.close()
return txt
end
local env = _ENV
local oop = {}
local classCache = {}
local privCache = {}
local publicCache = {}
local objectCache = {}
local dir = fs.getDir(({...})[1] or shell.getRunningProgram())
local function typeRestriction(index, sType, value)
if not oop.isType(value, sType) then
return false, "cannot convert "..sType.." '"..index.."' to '"..oop.type(value, true).."'"
else
return true
end
end
local function getNumberType(number)
if number%1 == 0 then
return "int"
else
return "double"
end
end
oop.setEnv = function(newEnv)
env = newEnv
end
oop.setDir = function(newDir)
oop.expect(1, newDir, "string")
dir = newDir
end
oop.getDir = function()
return dir
end
local default = {
int = 0,
double = 0,
number = 0,
string = "",
["function"] = function() end,
table = {},
boolean = false,
}
local varTypes = {
int = "int",
double = "double",
num = "number",
str = "string",
func = "function",
tbl = "table",
bool = "boolean",
}
local newVar = {
var = function(name)
local tbl = {name=name}
setmetatable(tbl, {
__call = function(self, value)
self.value = value
return self
end
})
return tbl
end,
obj = function(class)
oop.expect(1, class, "class")
local var = {type=classCache[class].name}
setmetatable(var, {
__call = function(self, name)
self.name = name
setmetatable(var, {
__call = function(self, value)
local ok,err = typeRestriction(name, var.type, value)
if not ok then
error(err, 2)
else
self.value = value
return self
end
end,
})
return self
end,
})
return var
end,
}
local const = function(name)
if type(name) == "function" then
return function(realname)
local var = name(realname)
var.const = true
return var
end
elseif type(name) == "string" then
local var = {name=name, const=true}
setmetatable(var, {
__call = function(self, value)
self.value = value
return self
end,
})
return var
end
end
for k,v in pairs(varTypes) do
newVar[k] = function(name)
local tbl = {name=name, type=varTypes[k]}
setmetatable(tbl, {
__call = function(self, value)
local stat,err = typeRestriction(name, tbl.type, value)
if not stat then
error(err, 2)
else
self.value = value
return self
end
end,
})
return tbl
end
end
oop.injectEnv = function(env)
env.oop = oop
env.class = oop.class
env.const = const
env.import = oop.import
env.type = oop.type
env.getPublicObject = oop.getPublicObject
for k,v in pairs(newVar) do
env[k] = v
end
end
local metamethods = {
["__add"] = true,
["__sub"] = true,
["__mul"] = true,
["__div"] = true,
["__mod"] = true,
["__pow"] = true,
["__unm"] = true,
["__concat"] = true,
["__len"] = true,
["__eq"] = true,
["__lt"] = true,
["__le"] = true,
["__index"] = true,
["__newindex"] = true,
["__call"] = true,
["__tostring"] = true,
["__metatable"] = true,
["__pairs"] = true,
["__ipairs"] = true
}
local function sanitizeArgs(...)
local args = table.pack(...)
for k,v in pairs(args) do
if publicCache[v] then
args[k] = publicCache[v]
end
end
return table.unpack(args, nil, args.n)
end
local function instantiate(orig)
local function deepCopy(o, seen)
seen = seen or {}
if seen[o] then
return seen[o]
end
local copy
if type(o) == 'table' then
copy = {}
seen[o] = copy
for k,v in pairs(o) do
copy[deepCopy(k, seen)] = deepCopy(v, seen)
end
setmetatable(copy, deepCopy(getmetatable(o), seen))
else
copy = o
end
return copy
end
return deepCopy(orig)
end
local function makeClass(name, classTbl, inherit)
local class = {
static = {
},
private = {
},
public = {
},
properties = {
static = {
},
private = {
},
public = {
},
types = {
},
consts = {
},
},
metatable = {
public = {
},
private = {
},
},
name = name,
}
class.metatable.static = {
__index = class.static,
__newindex = function(tbl, k, v)
if class.properties.static[k] then
if class.properties.consts[k] then
error("cannot modify const '"..tostring(k).."'", 2)
elseif class.properties.types[k] then
local stat,err = typeRestriction(k, class.properties.types[k], v)
if not stat then
error(err, 2)
else
class.static[k] = v
end
end
elseif class.properties.private[k] then
error("cannot modify private property '"..tostring(k).."' outside of class", 2)
elseif class.properties.public[k] then
error("cannot modify public property '"..tostring(k).."' outside of object", 2)
end
end,
__call = function(self, ...)
local privObj = {}
local obj = {}
privCache[obj] = privObj
publicCache[privObj] = obj
local properties = {}
local privProperties = {}
setmetatable(properties, {__index=class.public})
setmetatable(privProperties, {
__index=function(t, k)
return properties[k] or class.private[k]
end
})
local mt = {
__index = function(t, k)
local p = properties
if t == privObj then
p = privProperties
end
local v = p[k]
if type(v) == "function" then
local tEnv = {self=privObj}
oop.injectEnv(tEnv)
setmetatable(tEnv, {__index=env})
setfenv(v, tEnv)
end
return v
end
}
local inConstructor = false
mt.__pairs = function(tbl)
local loopThroughTables = {properties, class.public, class.static}
if tbl == privObj then
table.insert(loopThroughTables, 2, class.private)
end
local cls = class
while cls.parent do -- this WONT WORK
cls = cls.parent
if tbl == privObj then
table.insert(loopThroughTables, cls.private)
end
table.insert(loopThroughTables, cls.public)
table.insert(loopThroughTables, cls.static)
end
local currentIndex = 1
local had = {}
local function customIterator(_, prevKey)
local key, value
while currentIndex <= #loopThroughTables do
key, value = next(loopThroughTables[currentIndex], prevKey)
while key ~= nil and had[key] do -- skip keys that were already found
key, value = next(loopThroughTables[currentIndex], key)
end
if key ~= nil then
had[key] = true
return key, tbl[key]
else
prevKey = nil
currentIndex = currentIndex + 1
end
end
return nil
end
return customIterator, tbl, nil
end
mt.__newindex = function(tbl, k, v)
if class.properties.consts[k] and not inConstructor then
error("cannot modify const '"..tostring(k).."'", 2)
end
if tbl ~= privObj and class.properties.private[k] then
error("cannot modify private property '"..tostring(k).."' outside of class", 2)
end
if class.properties.types[k] then
local stat,err = typeRestriction(k, class.properties.types[k], v)
if not stat then
error(err, 2)
end
end
if class.properties.static[k] then
-- change in whole class
class.static[k] = v
elseif class.properties.private[k] then
privProperties[k] = v
else
properties[k] = v
end
end
local mt2 = {}
for k,v in pairs(mt) do
mt2[k] = v
end
for k,v in pairs(class.metatable.public) do
mt[k] = function(...)
local tEnv = {self=privObj}
oop.injectEnv(tEnv)
setmetatable(tEnv, {__index=env})
setfenv(v, tEnv)
return v(sanitizeArgs(...))
end
end
for k,v in pairs(class.metatable.private) do
mt2[k] = function(...)
local tEnv = {self=privObj}
oop.injectEnv(tEnv)
setmetatable(tEnv, {__index=env})
setfenv(v, tEnv)
return v(sanitizeArgs(...))
end
end
setmetatable(obj, mt)
setmetatable(privObj, mt2)
local constructors = {class.constructor}
local cls = class
while cls.parent do
cls = cls.parent
if cls.constructor then
table.insert(constructors, 1, cls.constructor)
end
end
for t=1,#constructors do
inConstructor = true
local tEnv = {self=privCache[obj] or obj}
oop.injectEnv(tEnv)
setmetatable(tEnv, {__index=env})
setfenv(constructors[t], tEnv)
local status,tObj = pcall(constructors[t],sanitizeArgs(...))
if not status then
error(tObj, 2)
end
if tObj == false then
return false
elseif tObj then
obj = publicCache[tObj] or tObj
end
inConstructor = false
end
objectCache[obj] = class
objectCache[privObj] = class
for k,v in pairs(privObj) do
if type(v) == "table" and v == class.private[k] then
privObj[k] = instantiate(v)
end
end
return obj
end,
}
local iClass
if inherit then
iClass = classCache[inherit]
class.parent = iClass
setmetatable(class.static, {__index=iClass.static})
setmetatable(class.public, {
__index = function(tbl, key)
if iClass.public[key] ~= nil then
return iClass.public[key]
else
return class.static[key]
end
end,
})
setmetatable(class.private, {
__index = function(tbl, key)
if iClass.private[key] ~= nil then
return iClass.private[key]
else
return class.public[key]
end
end,
})
for k,v in pairs(class.properties) do
setmetatable(v, {__index=iClass.properties[k]})
end
setmetatable(class, {__index=iClass})
else
setmetatable(class.public, {__index=class.static})
setmetatable(class.private, {__index=class.public})
end
loopThrough = {"static","private","public"}
for i,l in ipairs(loopThrough) do
if classTbl[l] then
for k,v in pairs(classTbl[l]) do
if type(k) == "number" then
if type(v) == "table" and v.name then
if v.const then
class.properties.consts[v.name] = true
end
if v.type then
class.properties.types[v.name] = v.type
end
if v.value or v.type then
class[l][v.name] = v.value or default[v.type]
end
class.properties[l][v.name] = true -- now values can be generic typed starting with nil but still recognized
elseif type(v) == "string" then
class.properties[l][v] = true
end
elseif type(k) == "string" then
if l == "public" and k == name then -- constructor
if type(v) ~= "function" then
error("invalid constructor: expected function, got "..type(v),2)
end
class.constructor = v
elseif metamethods[k] then
class.metatable[l][k] = v
else
class[l][k] = v
class.properties[l][k] = true
end
end
end
end
end
local classObj = {}
classCache[classObj] = class
class.obj = classObj
setmetatable(classObj, class.metatable.static)
env[name] = classObj
end
function oop.class(name)
return function(class)
if classCache[class] then -- a class object was passed meaning we're gonna inherit
return function(realclass)
makeClass(name, realclass, class)
end
else
makeClass(name, class)
end
end
end
local oType = type
function oop.type(value, numberType)
if oType(value) == "table" then
if objectCache[value] then
return objectCache[value].name
elseif classCache[value] then
return "class"
else
return "table"
end
elseif numberType and oType(value) == "number" then
return getNumberType(value)
else
return oType(value)
end
end
function oop.getClassName(class)
oop.expect(1, class, "class")
return classCache[class].name
end
function oop.isType(value, ...)
local types = {oop.type(value), oType(value), oop.type(value, true)}
if types[1] ~= types[2] and types[3] ~= "class" then
types[3] = "object"
end
local allowedTypes = {...}
for i,t in ipairs(types) do
for i=1, #allowedTypes do
if allowedTypes[i] == t or (allowedTypes[i] == "double" and t == "number") then
return true
end
end
end
return false
end
function oop.expect(index, value, ...)
local level = 3
local allowedTypes = {...}
if type(allowedTypes[1]) == "number" then
level = allowedTypes[1]
table.remove(allowedTypes, 1)
end
if type(allowedTypes[1]) ~= "string" then
error("bad argument #3: expected string", 2)
end
if oop.isType(value, ...) then
return value
end
local expectlist
local numbertype = false
for t=1,#allowedTypes do
if allowedTypes[t] == "int" then
numbertype = true
end
end
if #allowedTypes > 1 then
local lastType = allowedTypes[#allowedTypes]
allowedTypes[#allowedTypes] = nil
expectlist = table.concat(allowedTypes, ", ").." or "..lastType
else
expectlist = allowedTypes[1]
end
local t = oop.type(value, numbertype)
error("bad argument #"..index..": expected "..expectlist.." got "..t.." ("..tostring(value)..")", level)
end
function oop.getPublicObject(privateObject)
return publicCache[privateObject] or privateObject
end
function oop.import(filepath)
oop.expect(1, filepath, "string")
local makePackage = require("cc.require")
local tEnv = setmetatable({shell=shell, multishell=multishell},{__index=env})
tEnv.require, tEnv.package = makePackage.make(tEnv, dir)
tEnv.oop = oop
oop.injectEnv(tEnv)
local pathlog = filepath
local filepath2 = filepath..".lua"
pathlog = pathlog.."\n"..filepath2
if filepath:sub(1,1) ~= "/" then
pathlog = pathlog.."\n"..fs.combine(dir, filepath)
pathlog = pathlog.."\n"..fs.combine(dir, filepath2)
if fs.exists(fs.combine(dir, filepath)) then
filepath = fs.combine(dir, filepath)
elseif fs.exists(fs.combine(dir, filepath2)) then
filepath = fs.combine(dir, filepath2)
end
end
if fs.exists(filepath2) and not fs.exists(filepath) then
filepath = filepath2
end
if not fs.exists(filepath) then
error("Could not find file:"..pathlog,2)
end
local f = fread(filepath)
local func, err = load(f, "@"..filepath, nil, tEnv)
if not func then
error(err, 2)
else
local ok,err = pcall(func)
if not ok then
error(err, 2)
end
end
end
return oop