TypeChecker

Run Settings
LanguageLua
Language Version
Run Command
require("Dependencies") Cool = TypeProtect( { a = "number|string", b = "number|string", c = "boolean?" }, "any...", 0, function(Def, ...) if Def.c then return Def.a - Def.b, ... else return Def.a + Def.b, ... end end ) print(Cool({ a = 1, b = 3, c = true })) print(Col3(1, 0, 1):ToHSV()) --[[ print(Cool({ a = 1, b = 2, c = false }, "hi", true, 5)) print(Cool({ a = 1, b = false, c = false }, "hello")) ]] print(unpack({"hi", "hello", 3}, 1, -1))
require("Util") require("TypeProtect") require("Class") require("Col3")
function string.split(inputstr, sep) sep = sep or '%s' sep = sep ~= "" and "([^"..sep.."]*)("..sep.."?)" or "." local t = {} for field, s in string.gmatch(inputstr, sep) do table.insert(t, field) if s == "" then return t end end return t end function table.print(t, Indent) if not Indent then print(string.rep(" ", Indent or 0).."{") end local CallWith = {} for i, v in next, t do if type(v) == "table" then CallWith[i] = v else print(string.rep(" ", Indent or 0), "["..i.."] =", v) end end for i, v in next, CallWith do local Indent = Indent and Indent + 1 or 1 print(string.rep(" ", Indent).."["..i.."] = {") RecursePrint(v, Indent) end print(string.rep(" ", Indent and Indent or 0).."}") end function typeof(v) local Type = type(v) return Type == "table" and v.__Type or Type end
local function ParseTypeTable(t, ErrorDepth, Path) -- iterating over and modifying a table at the same time can lead to unpredictable behaviour once in a blue moon local Modifications = {} -- so we'll store the modifications here to add them to the table later on for i, v in next, t do if type(v) == "table" then ParseTypeTable(v, ErrorDepth + 1, (Path and Path.."." or "#")..i) elseif type(v) == "string" then local IsInfInput repeat local Replaced = 0 local Got v, Got = v:gsub("%.%.%.", "") Replaced = Replaced + Got IsInfInput = IsInfInput or Got ~= 0 v, Got = v:gsub("%?$", "|nil") Replaced = Replaced + Got v, Got = v:gsub("^%?", "nil|") Replaced = Replaced + Got until Replaced == 0 local Seperated = v:split("|") Seperated.__TypeBundle = true for i, v in ipairs(Seperated) do if v == "any" then Seperated = {__Any = true, __TypeBundle = true} break end end Modifications[i] = Seperated if IsInfInput then Modifications.__InfInput = Seperated end else error(string.format("Expected string or table, got %s at %s%s.", typeof(v), Path and Path.."." or "", i), ErrorDepth) end end for i, v in next, Modifications do t[i] = v end end local function VerifyTableTypes(t, Types, ErrorDepth, Path) local InfInput = Types.__InfInput if not InfInput or not InfInput.__Any then for i, v in next, t do if not Types[i] then if InfInput then local Type = typeof(v) for i, v in ipairs(InfInput) do if Type == v then goto continue end end local Types if #InfInput > 1 then Types = table.concat(InfInput, ", ", 1, #InfInput - 1).." or "..InfInput[#InfInput] else Types = InfInput[1] end error(string.format("Expected %s, got %s at #%s%s.", Types, Type, Path and Path.."." or "", i), ErrorDepth) ::continue:: else error(string.format("Expected nil, got %s at %s%s.", typeof(v), Path and Path.."." or "", i), ErrorDepth) end end end end local Debug = Types for i, v in next, Types do if i == "__TypeBundle" or i == "__InfInput" or i == "__Any" then goto continue end local Type = typeof(t[i]) if v.__TypeBundle then if v.__Any then goto continue end for i, v in ipairs(v) do if Type == v then goto continue end end local Types if #v > 1 then Types = table.concat(v, ", ", 1, #v - 1).." or "..v[#v] else Types = v[1] end table.print(Debug) error(string.format("Expected %s, got %s at #%s%s.", Types, Type, Path and Path.."." or "", i), ErrorDepth) elseif not t[i] then error(string.format("Unexpected table structure at at #%s%s.", Path and Path.."." or "", i), ErrorDepth) else if type(t[i]) ~= "table" then print(Path.."."..i) end VerifyTableTypes(t[i], v, ErrorDepth + 1, Path and Path.."."..i or i) end ::continue:: end end function TypeProtect(...) local Types = {...} local Func = table.remove(Types) local ErrorDepth = table.remove(Types) do -- initial error checking/processing if type(Func) ~= "function" or type(ErrorDepth) ~= "number" then error(string.format("Expected a number and a function as the last 2 parameters, got %s and %s.", typeof(Func), typeof(ErrorDepth)), 2) end ParseTypeTable(Types, 3 + ErrorDepth) end ErrorDepth = math.max(0, ErrorDepth) return function(...) local Got = {...} VerifyTableTypes(Got, Types, 3 + ErrorDepth) return Func(...) end end
local ClassMeta = {} function ClassMeta:__Call(...) return self:New(...) end function ClassMeta:__index(Key) if ClassMeta[Key] then return ClassMeta[Key] elseif self.__Constructors[Key] then return function(...) local Object = setmetatable({}, self.__Methods) self.__Constructors[Key](Object, ...) return Object end end end ClassMeta.__newindex = TypeProtect("Class", "string|number", "function", 0, function(self, Index, Function) if not self.__PromiseState.Types then error("Attempted to add a method/constructor without making a promise first.", 3) elseif Index ~= self.__PromiseState.PromiseName then error("Attempted to add a method/constructor with a name that doesn't match the last given promise.", 3) end local ProtectArgs = self.__PromiseState.Types table.insert(ProtectArgs, 0) table.insert(ProtectArgs, Function) local Protected = TypeProtect(unpack(ProtectArgs)) local PromiseType = self.__PromiseState.PromiseType if PromiseType == ":" then self.__Methods[Index] = Protected elseif PromiseType == "+" then if not self.__Constructors.__Default then self.__Constructors.__Default = Protected end self.__Constructors[Index] = Protected end self.__PromiseState.PromiseType = nil self.__PromiseState.PromiseName = nil self.__PromiseState.Types = nil end) ClassMeta.AddPromise = TypeProtect("Class", "string", "string...", 0, function(self, PromiseName, ...) local PromiseType = PromiseName:sub(1, 1) if PromiseType ~= ":" and PromiseType ~= "+" then error("Please use + for promising a constructor and : for promising a method as a prefix before the promised name.\nExamples: \":MyMethod\" \"+fromHSV\"", 3) end self.__PromiseState.PromiseType = PromiseType self.__PromiseState.PromiseName = PromiseName:sub(2, -1) self.__PromiseState.Types = {...} end) function ClassMeta:__call(...) return self.__Default(...) end Class = TypeProtect("string", 0, function(Type) local Class = { __Type = "Class", __Methods = { __Type = Type }, __Constructors = {}, __PromiseState = {} } Class.__Methods.__index = Class.__Methods return setmetatable(Class, ClassMeta) end)
Col3 = Class("Col3") Col3:AddPromise("+New", "Col3", "number?", "number?", "number?") function Col3:New(R, G, B) self.R = R or 0 self.G = G or 0 self.B = B or 0 end -- from https://www.rapidtables.com/convert/color/hsv-to-rgb.html Col3:AddPromise("+FromHSV", "Col3", "number?", "number?", "number?") function Col3:FromHSV(H, S, V) H = (H % 1) * 360 S = math.clamp(S, 0, 1) V = math.clamp(V, 0, 1) local C = V * S local X = C * (1 - math.abs(((H / 60) % 2) - 1)) local M = V - C if H < 60 then self.R = C self.G = X self.B = 0 elseif H < 120 then self.R = X self.G = C self.B = 0 elseif H < 180 then self.R = 0 self.G = C self.B = X elseif H < 240 then self.R = 0 self.G = X self.B = C elseif H < 300 then self.R = X self.G = 0 self.B = C else self.R = C self.G = 0 self.B = X end self.R = self.R + M self.G = self.G + M self.B = self.B + M end -- from https://www.rapidtables.com/convert/color/rgb-to-hsv.html Col3:AddPromise(":ToHSV", "Col3") function Col3:ToHSV() local R, G, B = self.R, self.G, self.B local Cmax = math.max(R, G, B) local Cmin = math.min(R, G, B) local Delta = Cmax - Cmin local H if Delta == 0 then H = 0 elseif Cmax == R then H = 60 * (((G - B) / Delta) % 6) elseif Cmax == G then H = 60 * (((B - R) / Delta + 2) % 6) elseif Cmax == B then H = 60 * (((R - G) / Delta + 4) % 6) end local S if Cmax == 0 then S = 0 else S = Delta / Cmax end V = Cmax return H / 360, S, V end Col3:AddPromise(":__tostring", "Col3") function Col3:__tostring() return string.format("Col3(%d, %d, %d)", self.R, self.G, self.B) end
Editor Settings
Theme
Key bindings
Full width
Lines