Spaces:
Running
Running
--!strict | |
--!native | |
local CS = {} | |
local assemblyGlobal = {} | |
local function chainIndex(location: table, ...: table): () -> any | |
local names = {...} | |
return function(t, k) | |
for _, name in names do | |
local tbl = location[name] | |
local v = tbl[k] | |
if v ~= nil then | |
return v | |
end | |
end | |
end | |
end | |
local function createArithmeticOperators(self, mt, fieldName): table | |
-- TODO: bitwise ops (if necessary) (or possible) | |
local function getNumericValue(value: table | number): number | |
return if typeof(value) == "table" and value.__isEnumMember then value[fieldName] else value | |
end | |
function mt:__add(other) | |
return self[fieldName] + getNumericValue(other) | |
end | |
function mt:__sub(other) | |
return self[fieldName] - getNumericValue(other) | |
end | |
function mt:__mul(other) | |
return self[fieldName] * getNumericValue(other) | |
end | |
function mt:__div(other) | |
return self[fieldName] / getNumericValue(other) | |
end | |
function mt:__idiv(other) | |
return self[fieldName] // getNumericValue(other) | |
end | |
function mt:__mod(other) | |
return self[fieldName] % getNumericValue(other) | |
end | |
function mt:__pow(other) | |
return self[fieldName] ^ getNumericValue(other) | |
end | |
function mt:__unm() | |
return -self[fieldName] | |
end | |
return mt | |
end | |
export type Class = table; | |
export type Namespace = { | |
name: string; | |
parent: Namespace?; | |
members: { Namespace | Class }; | |
class: (self: Namespace, name: string, create: (self: Namespace) -> Class) -> nil; | |
} | |
local CSNamespace = {} do | |
@native | |
function CSNamespace.new(name, parent) | |
local self = {} | |
self.name = name | |
self.parent = parent | |
self.members = {} | |
self["$loadCallbacks"] = {} | |
if self.parent ~= nil then | |
self = setmetatable(self, { __index = self.parent }) | |
end | |
return setmetatable(self, CSNamespace) | |
end | |
@native | |
function CSNamespace:__index(index) | |
return self.members[index] or CSNamespace[index] | |
end | |
@native | |
function CSNamespace:__newindex(index, value) | |
self.members[index] = value | |
end | |
@native | |
function CSNamespace:__tostring(index) | |
return self.name | |
end | |
CSNamespace["$getMember"] = @native function(self, name) | |
return self.members[name] | |
end | |
CSNamespace["$onLoaded"] = @native function(self, callback) | |
table.insert(self["$loadCallbacks"], callback) | |
end | |
@native | |
function CSNamespace:class(name, create) | |
CS.class(name, create, self) | |
end | |
@native | |
function CSNamespace:namespace(name, registerMembers) | |
CS.namespace(name, registerMembers, self.members, self) | |
end | |
end | |
@native | |
function CS.classInstance(class: Class, mt: table, namespace: Namespace?) | |
local instance = {} | |
instance["$className"] = class.__name | |
@native | |
local function getSuperclass() | |
if class.__superclass == nil then return end | |
if class.__superclass:match(".") == nil then | |
return assemblyGlobal[class.__superclass] | |
end | |
local pieces = class.__superclass:split("."); | |
local result = assemblyGlobal | |
for _, piece in pieces do | |
result = result[piece] or result | |
end | |
return result | |
end | |
@native | |
function mt.__tostring() | |
return class.__name | |
end | |
instance["$base"] = @native function(...) | |
if instance["$superclass"] ~= nil then return end | |
local Superclass = getSuperclass() | |
local superclassInstance = Superclass.new(...) | |
instance["$superclass"] = superclassInstance | |
mt.__index = superclassInstance | |
end | |
return setmetatable(instance, mt) | |
end | |
@native | |
function CS.classDef(name: string, namespace: Namespace?, superclass: string?, ...: string) | |
local mt = {} | |
mt.__index = chainIndex(if namespace ~= nil then namespace else assemblyGlobal, ...) | |
@native | |
function mt.__tostring() | |
return name | |
end | |
local class = {} | |
class.__name = name | |
class.__superclass = superclass | |
return setmetatable(class, mt) | |
end | |
@native | |
function CS.class(name: string, create: (namespace: Namespace?) -> table, namespace: Namespace?) | |
local location = if namespace ~= nil then namespace.members else assemblyGlobal | |
local class = create(namespace) | |
location[name] = class | |
end | |
@native | |
function CS.namespace(name: string, registerMembers: () -> nil, location: table?): Namespace | |
local parent = location | |
if location == nil then | |
location = assemblyGlobal | |
end | |
local namespaceDefinition = location[name] or CSNamespace.new(name, parent) | |
registerMembers(namespaceDefinition) | |
location[name] = namespaceDefinition | |
for _, callback in namespaceDefinition["$loadCallbacks"] do | |
callback() | |
end | |
return namespaceDefinition | |
end | |
@native | |
function CS.enum(name: string, definition: table, location: table): table | |
if location == nil then | |
location = assemblyGlobal | |
end | |
definition.__name = name | |
@native | |
function definition:__index(index: string | number): table | |
if index == "__name" then return name end | |
local member = { | |
name = index, | |
value = definition[index], | |
__isEnumMember = true | |
} | |
return setmetatable(member, createArithmeticOperators(member, { | |
__eq = @native function(self, other) | |
return typeof(other) == "table" and other.__isEnumMember and self.value == other.value | |
end, | |
__tostring = @native function(self) | |
return self.name | |
end | |
}, "value")) | |
end | |
@native | |
function definition:__eq(other: table): boolean | |
return self.__name == other.__name | |
end | |
@native | |
function definition:__tostring(): string | |
return self.__name | |
end | |
location[name] = location[name] or table.freeze(setmetatable({}, definition)) | |
return location[name] | |
end | |
@native | |
function CS.is(object: any, class: Class | string): boolean | |
if typeof(class) == "table" and type(class.__name) == "string" then | |
return typeof(object) == "table" and type(object["className"]) == "string" and object["className"] == class.__name | |
end | |
-- metatable check | |
if typeof(object) == "table" then | |
obj = getmetatable(obj) | |
while object ~= nil do | |
if object == class then | |
return true | |
end | |
local mt = getmetatable(object) | |
if mt then | |
object = mt.__index | |
else | |
object = nil | |
end | |
end | |
end | |
if typeof(class) == "string" then | |
return if typeof(object) == "Instance" then object:IsA(class) else typeof(object) == class | |
end | |
return false | |
end | |
@native | |
function CS.getAssemblyType(name) | |
local env | |
if getfenv == nil then | |
env = _ENV | |
else | |
env = getfenv() | |
end | |
return assemblyGlobal[name] or env[name] | |
end | |
CS.class("Exception", @native function() | |
local class = CS.classDef("Exception") | |
@native | |
function class.new(message: string?): Exception | |
local mt = {} | |
local self = CS.classInstance(class, mt) :: Exception | |
if message == nil then message = "An error occurred" end | |
self.Message = message | |
@native | |
function mt.__tostring(): string | |
return `{self["$className"]}: {self.Message}` | |
end | |
@native | |
function self.Throw(withinTryBlock: boolean): nil | |
error(if withinTryBlock then self else tostring(self)) | |
return nil | |
end | |
return self | |
end | |
return class | |
end) | |
export type Exception = { | |
Message: string; | |
Throw: () -> nil; | |
} | |
type CatchBlock = { | |
exceptionClass: string; | |
block: (ex: Exception?, rethrow: () -> nil) -> nil | |
} | |
@native | |
function CS.try(block: () -> nil, finallyBlock: () -> nil, catchBlocks: { CatchBlock }) | |
local success: boolean, ex: Exception | string | nil = pcall(block) | |
if not success then | |
if typeof(ex) == "string" then | |
ex = CS.getAssemblyType("Exception").new(ex, false) | |
end | |
for _, catchBlock in catchBlocks do | |
if catchBlock.exceptionClass ~= nil and catchBlock.exceptionClass ~= ex["$className"] then continue end | |
catchBlock.block(ex :: Exception, (ex :: Exception).Throw) | |
end | |
end | |
if finallyBlock ~= nil then | |
finallyBlock() | |
end | |
end | |
return CS |