648 lines
14 KiB
Lua
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 |