Spaces:
Sleeping
Sleeping
| --!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 |