From 7febf72fab42a6a4d06046ec7b0f6e882fd515e3 Mon Sep 17 00:00:00 2001 From: Demo <76854027+RobloxianDemo@users.noreply.github.com> Date: Sat, 11 Nov 2023 02:54:07 -0500 Subject: [PATCH 01/29] Feature: Add `Luau` language. --- lib/linguist/languages.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index 88c425287f..73d61a485f 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -34,7 +34,6 @@ # test changes in `test/test_blob.rb`. # # Please keep this list alphabetized. Capitalization comes before lowercase. ---- 1C Enterprise: type: programming color: "#814CCC" @@ -3879,6 +3878,17 @@ Lua: interpreters: - lua language_id: 213 +Luau: + type: programming + tm_scope: source.luau + ace_mode: lua + codemirror_mode: lua + codemirror_mime_type: text/x-lua + color: "#14a5ff" + extensions: + - ".luau" + interpreters: + - lua M: type: programming aliases: From 912f57cd0d58464c25204cd20e3e26883aa6bc7e Mon Sep 17 00:00:00 2001 From: Demo <76854027+RobloxianDemo@users.noreply.github.com> Date: Sat, 11 Nov 2023 02:56:53 -0500 Subject: [PATCH 02/29] Featire: Add `Luau` code samples. --- samples/Luau/signal.luau | 37 +++++++++ samples/Luau/vector.luau | 170 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 samples/Luau/signal.luau create mode 100644 samples/Luau/vector.luau diff --git a/samples/Luau/signal.luau b/samples/Luau/signal.luau new file mode 100644 index 0000000000..fcd2631f4f --- /dev/null +++ b/samples/Luau/signal.luau @@ -0,0 +1,37 @@ +--[[ +Simple signal/slot implementation +]] +local signal_mt = { + __index = { + register = table.insert + } +} +function signal_mt.__index:emit(... --[[ Comment in params ]]) + for _: number, slot: any in ipairs(self) do + slot(self, ...) + end +end +local function create_signal(): typeof(signal_mt) + return setmetatable({}, signal_mt) +end + +-- Signal test +local signal: typeof(signal_mt) = create_signal() +signal:register(function(_signal, ...) + print(...) +end) +signal:emit('Answer to Life, the Universe, and Everything:', 42) + +-- Output test +local testNumber: number = 10 + +print(`this is line, and "testNumber" equals to: {testNumber}.`) + +--[==[ [=[ [[ +Nested ]] +multi-line ]=] +comment ]==] +[==[ Nested +[=[ multi-line +[[ string +]] ]=] ]==] \ No newline at end of file diff --git a/samples/Luau/vector.luau b/samples/Luau/vector.luau new file mode 100644 index 0000000000..017290529a --- /dev/null +++ b/samples/Luau/vector.luau @@ -0,0 +1,170 @@ +--// Vector +--// Written by Demo (R0BL0XIAN_D3M0) +--// [https://www.roblox.com/users/289025524/profile] +--// 11/09/2023 + +--// Types +type TVector = Vector2 | Vector3 + +--[=[ + @class Vector + + A collection of very useful vector-related functions. +]=] +local Vector = {} + +--// Functions + +--[=[ + @within Vector + + @param vector Vector2 -- The `Vector2` coordinate. + + @return Vector3 -- Return the newly created `Vector3`. + + Create a `Vector3` from a `Vector2` within the XY plane. +]=] +function Vector.FromVector2XY(vector: Vector2): Vector3 + return (Vector3.new(vector.X, vector.Y, 0)) +end + +--[=[ + @within Vector + + @param vector Vector2 -- The `Vector2` coordinate. + + @return Vector3 -- Return the newly created `Vector3`. + + Create a `Vector3` from a `Vector2` within the XZ plane, unlike `FromVector2XY`. +]=] +function Vector.FromVector2XZ(vector: Vector2): Vector3 + return (Vector3.new(vector.X, 0, vector.Y)) +end + +--[=[ + @within Vector + + @param vector Vector2 -- The initial `Vector2` coordinate. + + @param _vector Vector2 -- The secondary `Vector2` coordinate. + + @return number -- Return the computed angle in a numerical form or nil. + + Compute the angle between two vectors in radians. +]=] +function Vector.RetrieveAngleRadian(vector: Vector2, _vector: Vector2): number? + if vector.Magnitude == 0 then + return nil + end + + return (math.acos(vector:Dot(_vector))) +end + +--[=[ + @within Vector + + @param vector Vector2 -- The initial `Vector2` coordinate. + + @param _vector Vector2 -- The secondary `Vector2` coordinate. + + @return number -- Return the computed angle in a numerical form. + + Compute the angle between two vectors. +]=] +function Vector.AngleBetweenVectors(vector: Vector2, _vector: Vector2): number + local newVector: Vector2 = (_vector.Magnitude * vector) + local _newVector: Vector2 = (vector.Magnitude * _vector) + + return (2 * (math.atan2((_newVector - newVector).Magnitude, (newVector + _newVector).Magnitude))) +end + +--[=[ + @within Vector + + @param vector Vector3 -- The original `Vector3` coordinate. + + @param amount number -- The primary amount. + + @return Vector3 -- Return the rounded `Vector3`. + + Round the specified `Vector3` to the nearest number. +]=] +function Vector.Round(vector: Vector3, amount: number): Vector3 + return ( + Vector3.new( + ((math.round(vector.X / amount)) * amount), + ((math.round(vector.Y / amount)) * amount), + ((math.round(vector.Z / amount)) * amount) + ) + ) +end + +--[=[ + @within Vector + + @param vector TVector -- The vector coordinate (`Vector2` or `Vector3`). + + @param maxMagnitude number -- The maximum magnitude. + + @return number -- Return the clamped magnitude. + + Clamp the magnitude of a vector so it is only a certain length. +]=] +function Vector.ClampMagnitude(vector: TVector, maxMagnitude: number): number + return ((vector.Magnitude > maxMagnitude) and (vector.Unit * maxMagnitude) or vector) +end + +--[=[ + @within Vector + + @param vector TVector -- The initial vector coordinate (`Vector2` or `Vector3`). + + @param _vector TVector -- The secondary vector coordinate (`Vector2` or `Vector3`). + + @return number -- Return the radianed angle. + + Finds the angle in radians between two vectors. +]=] +function Vector.AngleBetween(vector: TVector, _vector: TVector): number + return (math.acos(math.clamp(vector.Unit:Dot(_vector.Unit), -1, 1))) +end + +--[=[ + @within Vector + + @param vector TVector -- The initial vector coordinate (`Vector2` or `Vector3`). + + @param _vector TVector -- The secondary vector coordinate (`Vector2` or `Vector3`). + + @param axisVector Vector -- The axis vector coordinate (`Vector` or `Vector3`). + + @return number -- Return the radianed angle. + + Finds the angle in radians between two vectors and returns a signed value. +]=] +function Vector.AngleBetweenSigned(vector: TVector, _vector: TVector, axisVector: TVector): number + local angle: number = Vector.AngleBetween(vector, _vector) + + return (angle * (math.sign(axisVector:Dot(vector:Cross(_vector))))) +end + +--[=[ + @within Vector + + @return Vector3 -- Return the random `Vector3` coordinate. + + Return a random unit vector (could be used for equal distribution around a sphere). +]=] +function Vector.RetrieveRandomUnitVector(): Vector3 + local randomX: number = (2 * ((math.random()) - 0.5)) + local randomY: number = (6.2831853071796 * (math.random())) + local randomZ: number = ((1 - (randomX * randomX)) ^ 0.5) + + local X: number = randomX + local Y: number = (randomZ * (math.cos(randomZ))) + local Z: number = (randomZ * (math.sin(randomY))) + + return (Vector3.new(X, Y, Z)) +end + +return Vector \ No newline at end of file From 81be62246d27c12605999098230535f995dd2588 Mon Sep 17 00:00:00 2001 From: Demo <76854027+RobloxianDemo@users.noreply.github.com> Date: Sat, 11 Nov 2023 02:58:11 -0500 Subject: [PATCH 03/29] Feature: Add the grammar. --- .gitmodules | 3 + grammars.yml | 2 + vendor/grammars/luau.tmLanguage | 1 + .../git_submodule/luau.tmLanguage.dep.yml | 56 +++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 160000 vendor/grammars/luau.tmLanguage create mode 100644 vendor/licenses/git_submodule/luau.tmLanguage.dep.yml diff --git a/.gitmodules b/.gitmodules index e14f0bf03f..addd3ea5b8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -848,6 +848,9 @@ [submodule "vendor/grammars/lua.tmbundle"] path = vendor/grammars/lua.tmbundle url = https://github.com/textmate/lua.tmbundle +[submodule "vendor/grammars/luau.tmLanguage"] + path = vendor/grammars/luau.tmLanguage + url = https://github.com/JohnnyMorganz/Luau.tmLanguage.git [submodule "vendor/grammars/m3"] path = vendor/grammars/m3 url = https://github.com/newgrammars/m3 diff --git a/grammars.yml b/grammars.yml index cf9da731c7..4904457a7d 100644 --- a/grammars.yml +++ b/grammars.yml @@ -809,6 +809,8 @@ vendor/grammars/logtalk.tmbundle: - source.logtalk vendor/grammars/lua.tmbundle: - source.lua +vendor/grammars/luau.tmLanguage: +- source.luau vendor/grammars/m3: - source.modula-3 vendor/grammars/make.tmbundle: diff --git a/vendor/grammars/luau.tmLanguage b/vendor/grammars/luau.tmLanguage new file mode 160000 index 0000000000..9f320add6d --- /dev/null +++ b/vendor/grammars/luau.tmLanguage @@ -0,0 +1 @@ +Subproject commit 9f320add6dd099f99897a03c5e2ce3ca5f176d7f diff --git a/vendor/licenses/git_submodule/luau.tmLanguage.dep.yml b/vendor/licenses/git_submodule/luau.tmLanguage.dep.yml new file mode 100644 index 0000000000..83c1503249 --- /dev/null +++ b/vendor/licenses/git_submodule/luau.tmLanguage.dep.yml @@ -0,0 +1,56 @@ +name: Luau.tmLanguag +version: 9f320add6dd099f99897a03c5e2ce3ca5f176d7f +type: git_submodule +homepage: https://github.com/JohnnyMorganz/Luau.tmLanguage.git +license: mit +licenses: +- sources: LICENSE.md + text: |- + Copyright (c) JohnnyMorganz + All rights reserved. + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + === + + Copyright (c) Microsoft Corporation + All rights reserved. + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +notices: [] From 77339ab23c4d36b238e94c9fc437dbcb7f542a64 Mon Sep 17 00:00:00 2001 From: Demo <76854027+RobloxianDemo@users.noreply.github.com> Date: Sat, 11 Nov 2023 03:00:48 -0500 Subject: [PATCH 04/29] Feature: Add `Luau` to grammar index. --- vendor/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/vendor/README.md b/vendor/README.md index 67f1d452b0..1c73c47dc4 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -313,6 +313,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **LookML:** [atom/language-yaml](https://github.com/atom/language-yaml) - **LoomScript:** [ambethia/Sublime-Loom](https://github.com/ambethia/Sublime-Loom) - **Lua:** [textmate/lua.tmbundle](https://github.com/textmate/lua.tmbundle) +- **Luau:** [JohnnyMorganz/Luau.tmLanguage](https://github.com/JohnnyMorganz/Luau.tmLanguage) - **M4:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **M4Sugar:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **MATLAB:** [mathworks/MATLAB-Language-grammar](https://github.com/mathworks/MATLAB-Language-grammar) From 428f5d21f1f95205d50e9e8f73aa009c4c078640 Mon Sep 17 00:00:00 2001 From: Demo <76854027+RobloxianDemo@users.noreply.github.com> Date: Sat, 11 Nov 2023 11:53:51 -0500 Subject: [PATCH 05/29] Fix: Add comment block and change the interpreter. --- lib/linguist/languages.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index 73d61a485f..7439e844d4 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -34,6 +34,7 @@ # test changes in `test/test_blob.rb`. # # Please keep this list alphabetized. Capitalization comes before lowercase. +--- 1C Enterprise: type: programming color: "#814CCC" @@ -3888,7 +3889,7 @@ Luau: extensions: - ".luau" interpreters: - - lua + - luau M: type: programming aliases: From 1eced9338d0b22c54f12150ce0c4a5fc84ddeb8a Mon Sep 17 00:00:00 2001 From: Demo <76854027+RobloxianDemo@users.noreply.github.com> Date: Sat, 11 Nov 2023 21:45:01 -0500 Subject: [PATCH 06/29] Feature: Add new samples in place of the old ones. --- samples/Luau/createProcessor.luau | 22 +++ samples/Luau/createSignal.luau | 37 ++++ samples/Luau/equals.luau | 17 ++ samples/Luau/isEmpty.luau | 21 ++ samples/Luau/none.luau | 21 ++ samples/Luau/replaceMacros.luau | 311 ++++++++++++++++++++++++++++++ samples/Luau/signal.luau | 37 ---- samples/Luau/timeStepper.luau | 36 ++++ samples/Luau/toSet.luau | 22 +++ samples/Luau/vector.luau | 170 ---------------- 10 files changed, 487 insertions(+), 207 deletions(-) create mode 100644 samples/Luau/createProcessor.luau create mode 100644 samples/Luau/createSignal.luau create mode 100644 samples/Luau/equals.luau create mode 100644 samples/Luau/isEmpty.luau create mode 100644 samples/Luau/none.luau create mode 100644 samples/Luau/replaceMacros.luau delete mode 100644 samples/Luau/signal.luau create mode 100644 samples/Luau/timeStepper.luau create mode 100644 samples/Luau/toSet.luau delete mode 100644 samples/Luau/vector.luau diff --git a/samples/Luau/createProcessor.luau b/samples/Luau/createProcessor.luau new file mode 100644 index 0000000000..d55aba076e --- /dev/null +++ b/samples/Luau/createProcessor.luau @@ -0,0 +1,22 @@ +--// Authored by @sinlerdev (https://github.com/sinlerdev) +--// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/createProcessor.luau) +--// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) + +--!strict +local function createProcessor(valueChecker: (a: any, b: any) -> boolean, cleaner: (taskItem: any) -> ()) + return function(old: T, new: T, isDestroying: boolean) + if isDestroying then + cleaner(old) + return false + end + + if valueChecker(old, new) then + return false + end + + cleaner(old) + return true + end +end + +return createProcessor diff --git a/samples/Luau/createSignal.luau b/samples/Luau/createSignal.luau new file mode 100644 index 0000000000..5ffd461fff --- /dev/null +++ b/samples/Luau/createSignal.luau @@ -0,0 +1,37 @@ +--// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) +--// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/createSignal.luau) +--// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) + +--!strict + +local function createSignal() + local listeners = {} + local signal = {} + + function signal:Connect(listener) + listeners[listener] = true + + local connection = { + Connected = true, + } + + function connection.Disconnect() + connection.Connected = false + listeners[listener] = nil + end + connection.disconnect = connection.Disconnect + + return connection + end + signal.connect = signal.Connect + + local function fire(...) + for listener, _ in pairs(listeners) do + spawn(listener, ...) + end + end + + return signal, fire +end + +return createSignal diff --git a/samples/Luau/equals.luau b/samples/Luau/equals.luau new file mode 100644 index 0000000000..6b259e0359 --- /dev/null +++ b/samples/Luau/equals.luau @@ -0,0 +1,17 @@ +--// Authored by @sinlerdev (https://github.com/sinlerdev) +--// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/equals.luau) +--// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) + +--!strict + +local function equals(a: any, b: any): boolean + -- INFO: Vinum doesn't officially support immutability currently- so, we just assume + -- that every new table entry is not equal. See issue 26 + if type(a) == "table" then + return false + end + + return a == b +end + +return equals diff --git a/samples/Luau/isEmpty.luau b/samples/Luau/isEmpty.luau new file mode 100644 index 0000000000..585c387271 --- /dev/null +++ b/samples/Luau/isEmpty.luau @@ -0,0 +1,21 @@ +--// Authored by @benbrimeyer (https://github.com/benbrimeyer) +--// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/isEmpty.luau) +--// Licensed under the MIT License (hhttps://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) + +--!strict +--[=[ + Returns true if the collection is empty. + + ```lua + Freeze.isEmpty({}) + -- true + ``` + + @within Freeze + @function isEmpty + @return boolean +]=] + +return function(collection) + return next(collection) == nil +end diff --git a/samples/Luau/none.luau b/samples/Luau/none.luau new file mode 100644 index 0000000000..9aa5f93929 --- /dev/null +++ b/samples/Luau/none.luau @@ -0,0 +1,21 @@ +--// Authored by @benbrimeyer (https://github.com/benbrimeyer) +--// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/None.luau) +--// Licensed under the MIT License (hhttps://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) + +--[=[ + @prop None None + @within Freeze + + Since lua tables cannot distinguish between values not being present and a value of nil, + `Freeze.None` exists to represent values that should be interpreted as `nil`. + + This is useful when removing values with functions such as [`Freeze.Dictionary.merge`](../api/Dictionary#merge). +]=] + +local None = newproxy(true) + +getmetatable(None).__tostring = function() + return "Freeze.None" +end + +return None diff --git a/samples/Luau/replaceMacros.luau b/samples/Luau/replaceMacros.luau new file mode 100644 index 0000000000..11daa0b368 --- /dev/null +++ b/samples/Luau/replaceMacros.luau @@ -0,0 +1,311 @@ +--// Authored by @grilme99 (https://github.com/grilme99) +--// Fetched from (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/scripts/replaceMacros.luau) +--// Licensed under the MIT License (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/LICENSE-Brooke) + +-- upstream: https://github.com/dead/typeflex/blob/422cb26/tools/repalce_macros.py + +local function YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) + local ret = [[ +local function YGNodeStyleSet##name(node: YGNode, paramName: type) + if node:getStyle().instanceName ~= paramName then + local style: YGStyle = node:getStyle() + style.instanceName = paramName + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##name = YGNodeStyleSet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL(type, name, paramName, instanceName) + local ret = [[ +local function YGNodeStyleSet##name(node: YGNode, paramName: type) + local value: YGValue = YGValue.new( + YGFloatSanitize(paramName), + if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point + ) + + if + (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) + or node:getStyle().instanceName.unit ~= value.unit + then + local style: YGStyle = node:getStyle() + style.instanceName = value + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##name = YGNodeStyleSet##name + +local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) + local value: YGValue = YGValue.new( + YGFloatSanitize(paramName), + if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent + ) + + if + (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) + or node:getStyle().instanceName.unit ~= value.unit + then + local style: YGStyle = node:getStyle() + style.instanceName = value + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL(type, name, paramName, instanceName) + local ret = [[ +local function YGNodeStyleSet##name(node: YGNode, paramName: type) + local value: YGValue = YGValue.new( + YGFloatSanitize(paramName), + if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point + ) + + if + (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) + or node:getStyle().instanceName.unit ~= value.unit + then + local style: YGStyle = node:getStyle() + style.instanceName = value + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##name = YGNodeStyleSet##name + +local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) + if + node:getStyle().instanceName.value ~= YGFloatSanitize(paramName) + or node:getStyle().instanceName.unit ~= YGUnit.Percent + then + local style: YGStyle = node:getStyle() + style.instanceName.value = YGFloatSanitize(paramName) + style.instanceName.unit = if YGFloatIsUndefined(paramName) then YGUnit.Auto else YGUnit.Percent + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent + +local function YGNodeStyleSet##nameAuto(node: YGNode) + if node:getStyle().instanceName.unit ~= YGUnit.Auto then + local style: YGStyle = node:getStyle() + style.instanceName.value = 0 + style.instanceName.unit = YGUnit.Auto + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_PROPERTY_IMPL(type, name, paramName, instanceName) + local ret = YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) + ret ..= [[ +local function YGNodeStyleGet##name(node: YGNode): type + return node:getStyle().instanceName +end +exports.YGNodeStyleGet##name = YGNodeStyleGet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) + local ret = [[ +local function YGNodeStyleSet##name(node: YGNode, edge: YGEdge, paramName: number) + local value: YGValue = YGValue.new( + YGFloatSanitize(paramName), + if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point + ) + + if + (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) + or node:getStyle().instanceName[edge].unit ~= value.unit + then + local style: YGStyle = node:getStyle() + style.instanceName[edge] = value + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##name = YGNodeStyleSet##name + +local function YGNodeStyleSet##namePercent(node: YGNode, edge: YGEdge, paramName: number) + local value: YGValue = YGValue.new( + YGFloatSanitize(paramName), + if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent + ) + + if + (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) + or node:getStyle().instanceName[edge].unit ~= value.unit + then + local style: YGStyle = node:getStyle() + style.instanceName[edge] = value + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent + +local function YGNodeStyleGet##name(node: YGNode, edge: YGEdge): type + local value: YGValue = node:getStyle().instanceName[edge] + if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then + value.value = YGUndefined + end + + return value +end +exports.YGNodeStyleGet##name = YGNodeStyleGet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL(type, name, instanceName) + local ret = [[ +local function YGNodeStyleSet##nameAuto(node: YGNode, edge: YGEdge) + if node:getStyle().instanceName[edge].unit ~= YGUnit.Auto then + local style: YGStyle = node:getStyle() + style.instanceName[edge].value = 0 + style.instanceName[edge].unit = YGUnit.Auto + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL(type, name, paramName, instanceName) + local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL("number", name, paramName, instanceName) + ret ..= [[ +local function YGNodeStyleGet##name(node: YGNode): type + local value: YGValue = node:getStyle().instanceName + if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then + value.value = YGUndefined + end + return value +end +exports.YGNodeStyleGet##name = YGNodeStyleGet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) + local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL("number", name, paramName, instanceName) + ret ..= [[ +local function YGNodeStyleGet##name(node: YGNode): type + local value: YGValue = node:getStyle().instanceName + if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then + value.value = YGUndefined + end + return value +end +exports.YGNodeStyleGet##name = YGNodeStyleGet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_LAYOUT_PROPERTY_IMPL(type, name, instanceName) + local ret = [[ +local function YGNodeLayoutGet##name(node: YGNode): type + return node:getLayout().instanceName +end +exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) +end + +local function YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(type, name, instanceName) + local ret = [[ +local function YGNodeLayoutGet##name(node: YGNode, edge: YGEdge): type + -- YGAssertWithNode(node, edge <= YGEdge.End, "Cannot get layout properties of multi-edge shorthands") + + if edge == YGEdge.Start then + if node:getLayout().direction == YGDirection.RTL then + return node:getLayout().instanceName[YGEdge.Right] + else + return node:getLayout().instanceName[YGEdge.Left] + end + end + + if edge == YGEdge.End then + if node:getLayout().direction == YGDirection.RTL then + return node:getLayout().instanceName[YGEdge.Left] + else + return node:getLayout().instanceName[YGEdge.Right] + end + end + + return node:getLayout().instanceName[edge] +end +exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) +end + +local cod = "" + +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDirection", "Direction", "direction", "direction") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGFlexDirection", "FlexDirection", "flexDirection", "flexDirection") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGJustify", "JustifyContent", "justifyContent", "justifyContent") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignContent", "alignContent", "alignContent") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignItems", "alignItems", "alignItems") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignSelf", "alignSelf", "alignSelf") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGPositionType", "PositionType", "positionType", "positionType") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGWrap", "FlexWrap", "flexWrap", "flexWrap") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGOverflow", "Overflow", "overflow", "overflow") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDisplay", "Display", "display", "display") +cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Position", "position", "position") +cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Margin", "margin", "margin") +cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Padding", "padding", "padding") +cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Margin", "margin") +cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Width", "width", "dimensions[YGDimension.Width]") +cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Height", "height", "dimensions[YGDimension.Height]") +cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinWidth", "minWidth", "minDimensions[YGDimension.Width]") +cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinHeight", "minHeight", "minDimensions[YGDimension.Height]") +cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxWidth", "maxWidth", "maxDimensions[YGDimension.Width]") +cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxHeight", "maxHeight", "maxDimensions[YGDimension.Height]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Left", "position[YGEdge.Left]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Top", "position[YGEdge.Top]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Right", "position[YGEdge.Right]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Bottom", "position[YGEdge.Bottom]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Width", "dimensions[YGDimension.Width]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Height", "dimensions[YGDimension.Height]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("YGDirection", "Direction", "direction") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("boolean", "HadOverflow", "hadOverflow") +cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Margin", "margin") +cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Border", "border") +cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Padding", "padding") + +print(cod) \ No newline at end of file diff --git a/samples/Luau/signal.luau b/samples/Luau/signal.luau deleted file mode 100644 index fcd2631f4f..0000000000 --- a/samples/Luau/signal.luau +++ /dev/null @@ -1,37 +0,0 @@ ---[[ -Simple signal/slot implementation -]] -local signal_mt = { - __index = { - register = table.insert - } -} -function signal_mt.__index:emit(... --[[ Comment in params ]]) - for _: number, slot: any in ipairs(self) do - slot(self, ...) - end -end -local function create_signal(): typeof(signal_mt) - return setmetatable({}, signal_mt) -end - --- Signal test -local signal: typeof(signal_mt) = create_signal() -signal:register(function(_signal, ...) - print(...) -end) -signal:emit('Answer to Life, the Universe, and Everything:', 42) - --- Output test -local testNumber: number = 10 - -print(`this is line, and "testNumber" equals to: {testNumber}.`) - ---[==[ [=[ [[ -Nested ]] -multi-line ]=] -comment ]==] -[==[ Nested -[=[ multi-line -[[ string -]] ]=] ]==] \ No newline at end of file diff --git a/samples/Luau/timeStepper.luau b/samples/Luau/timeStepper.luau new file mode 100644 index 0000000000..038a614451 --- /dev/null +++ b/samples/Luau/timeStepper.luau @@ -0,0 +1,36 @@ +--// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) +--// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/TimeStepper.luau) +--// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) + +--!strict + +--[[ + +A time stepper is responsible for stepping systems in a deterministic order at a set interval. + +]] + +local TimeStepper = {} +TimeStepper.__index = TimeStepper + +function TimeStepper.new(interval: number, systems) + local self = setmetatable({ + _systems = systems, + _interval = interval, + }, TimeStepper) + + return self +end + +function TimeStepper:start() + coroutine.resume(coroutine.create(function() + while true do + local timeStep, _ = wait(self._interval) + for _, system in ipairs(self._systems) do + system:step(timeStep) + end + end + end)) +end + +return TimeStepper diff --git a/samples/Luau/toSet.luau b/samples/Luau/toSet.luau new file mode 100644 index 0000000000..079bcc854d --- /dev/null +++ b/samples/Luau/toSet.luau @@ -0,0 +1,22 @@ +--// Authored by @benbrimeyer (https://github.com/benbrimeyer) +--// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/List/toSet.luau) +--// Licensed under the MIT License (hhttps://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) + +--!strict + +--[=[ + Returns a Set from the given List. + + @within List + @function toSet + @ignore +]=] +return function(list: { Value }): { [Value]: boolean } + local set = {} + + for _, v in list do + set[v] = true + end + + return set +end diff --git a/samples/Luau/vector.luau b/samples/Luau/vector.luau deleted file mode 100644 index 017290529a..0000000000 --- a/samples/Luau/vector.luau +++ /dev/null @@ -1,170 +0,0 @@ ---// Vector ---// Written by Demo (R0BL0XIAN_D3M0) ---// [https://www.roblox.com/users/289025524/profile] ---// 11/09/2023 - ---// Types -type TVector = Vector2 | Vector3 - ---[=[ - @class Vector - - A collection of very useful vector-related functions. -]=] -local Vector = {} - ---// Functions - ---[=[ - @within Vector - - @param vector Vector2 -- The `Vector2` coordinate. - - @return Vector3 -- Return the newly created `Vector3`. - - Create a `Vector3` from a `Vector2` within the XY plane. -]=] -function Vector.FromVector2XY(vector: Vector2): Vector3 - return (Vector3.new(vector.X, vector.Y, 0)) -end - ---[=[ - @within Vector - - @param vector Vector2 -- The `Vector2` coordinate. - - @return Vector3 -- Return the newly created `Vector3`. - - Create a `Vector3` from a `Vector2` within the XZ plane, unlike `FromVector2XY`. -]=] -function Vector.FromVector2XZ(vector: Vector2): Vector3 - return (Vector3.new(vector.X, 0, vector.Y)) -end - ---[=[ - @within Vector - - @param vector Vector2 -- The initial `Vector2` coordinate. - - @param _vector Vector2 -- The secondary `Vector2` coordinate. - - @return number -- Return the computed angle in a numerical form or nil. - - Compute the angle between two vectors in radians. -]=] -function Vector.RetrieveAngleRadian(vector: Vector2, _vector: Vector2): number? - if vector.Magnitude == 0 then - return nil - end - - return (math.acos(vector:Dot(_vector))) -end - ---[=[ - @within Vector - - @param vector Vector2 -- The initial `Vector2` coordinate. - - @param _vector Vector2 -- The secondary `Vector2` coordinate. - - @return number -- Return the computed angle in a numerical form. - - Compute the angle between two vectors. -]=] -function Vector.AngleBetweenVectors(vector: Vector2, _vector: Vector2): number - local newVector: Vector2 = (_vector.Magnitude * vector) - local _newVector: Vector2 = (vector.Magnitude * _vector) - - return (2 * (math.atan2((_newVector - newVector).Magnitude, (newVector + _newVector).Magnitude))) -end - ---[=[ - @within Vector - - @param vector Vector3 -- The original `Vector3` coordinate. - - @param amount number -- The primary amount. - - @return Vector3 -- Return the rounded `Vector3`. - - Round the specified `Vector3` to the nearest number. -]=] -function Vector.Round(vector: Vector3, amount: number): Vector3 - return ( - Vector3.new( - ((math.round(vector.X / amount)) * amount), - ((math.round(vector.Y / amount)) * amount), - ((math.round(vector.Z / amount)) * amount) - ) - ) -end - ---[=[ - @within Vector - - @param vector TVector -- The vector coordinate (`Vector2` or `Vector3`). - - @param maxMagnitude number -- The maximum magnitude. - - @return number -- Return the clamped magnitude. - - Clamp the magnitude of a vector so it is only a certain length. -]=] -function Vector.ClampMagnitude(vector: TVector, maxMagnitude: number): number - return ((vector.Magnitude > maxMagnitude) and (vector.Unit * maxMagnitude) or vector) -end - ---[=[ - @within Vector - - @param vector TVector -- The initial vector coordinate (`Vector2` or `Vector3`). - - @param _vector TVector -- The secondary vector coordinate (`Vector2` or `Vector3`). - - @return number -- Return the radianed angle. - - Finds the angle in radians between two vectors. -]=] -function Vector.AngleBetween(vector: TVector, _vector: TVector): number - return (math.acos(math.clamp(vector.Unit:Dot(_vector.Unit), -1, 1))) -end - ---[=[ - @within Vector - - @param vector TVector -- The initial vector coordinate (`Vector2` or `Vector3`). - - @param _vector TVector -- The secondary vector coordinate (`Vector2` or `Vector3`). - - @param axisVector Vector -- The axis vector coordinate (`Vector` or `Vector3`). - - @return number -- Return the radianed angle. - - Finds the angle in radians between two vectors and returns a signed value. -]=] -function Vector.AngleBetweenSigned(vector: TVector, _vector: TVector, axisVector: TVector): number - local angle: number = Vector.AngleBetween(vector, _vector) - - return (angle * (math.sign(axisVector:Dot(vector:Cross(_vector))))) -end - ---[=[ - @within Vector - - @return Vector3 -- Return the random `Vector3` coordinate. - - Return a random unit vector (could be used for equal distribution around a sphere). -]=] -function Vector.RetrieveRandomUnitVector(): Vector3 - local randomX: number = (2 * ((math.random()) - 0.5)) - local randomY: number = (6.2831853071796 * (math.random())) - local randomZ: number = ((1 - (randomX * randomX)) ^ 0.5) - - local X: number = randomX - local Y: number = (randomZ * (math.cos(randomZ))) - local Z: number = (randomZ * (math.sin(randomY))) - - return (Vector3.new(X, Y, Z)) -end - -return Vector \ No newline at end of file From c0dd82e91adb6048f708c899c4a64d0dfbe02890 Mon Sep 17 00:00:00 2001 From: Demo <76854027+RobloxianDemo@users.noreply.github.com> Date: Sat, 11 Nov 2023 21:52:34 -0500 Subject: [PATCH 07/29] Patch: Slight typo in the header. --- samples/Luau/isEmpty.luau | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/Luau/isEmpty.luau b/samples/Luau/isEmpty.luau index 585c387271..c90e654801 100644 --- a/samples/Luau/isEmpty.luau +++ b/samples/Luau/isEmpty.luau @@ -1,6 +1,6 @@ --// Authored by @benbrimeyer (https://github.com/benbrimeyer) --// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/isEmpty.luau) ---// Licensed under the MIT License (hhttps://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) +--// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) --!strict --[=[ From a3a42863084ecf8caa77f84be17d09c669af75c1 Mon Sep 17 00:00:00 2001 From: Demo <76854027+RobloxianDemo@users.noreply.github.com> Date: Sat, 11 Nov 2023 21:53:59 -0500 Subject: [PATCH 08/29] Patch: Header typos again, I'm such a genius. --- samples/Luau/none.luau | 2 +- samples/Luau/toSet.luau | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/Luau/none.luau b/samples/Luau/none.luau index 9aa5f93929..118a75d8ce 100644 --- a/samples/Luau/none.luau +++ b/samples/Luau/none.luau @@ -1,6 +1,6 @@ --// Authored by @benbrimeyer (https://github.com/benbrimeyer) --// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/None.luau) ---// Licensed under the MIT License (hhttps://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) +--// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) --[=[ @prop None None diff --git a/samples/Luau/toSet.luau b/samples/Luau/toSet.luau index 079bcc854d..7d18f99ed2 100644 --- a/samples/Luau/toSet.luau +++ b/samples/Luau/toSet.luau @@ -1,6 +1,6 @@ --// Authored by @benbrimeyer (https://github.com/benbrimeyer) --// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/List/toSet.luau) ---// Licensed under the MIT License (hhttps://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) +--// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) --!strict From ba342d6f93708df835cd9e78f7f91d6e118e71cb Mon Sep 17 00:00:00 2001 From: Demo <76854027+RobloxianDemo@users.noreply.github.com> Date: Sun, 12 Nov 2023 11:46:50 -0500 Subject: [PATCH 09/29] Patch: Introduce the languageId. --- lib/linguist/languages.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index 7439e844d4..5007977038 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -3890,6 +3890,7 @@ Luau: - ".luau" interpreters: - luau + language_id: 365050359 M: type: programming aliases: From 7463c5f98a44d12712e159b3fd40d17da2d357bc Mon Sep 17 00:00:00 2001 From: Demo <76854027+RobloxianDemo@users.noreply.github.com> Date: Sun, 12 Nov 2023 19:01:23 +0000 Subject: [PATCH 10/29] Patch: Resolve grammar conflict. --- .gitmodules | 4 ++-- vendor/grammars/{luau.tmLanguage => Luau.tmLanguage} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename vendor/grammars/{luau.tmLanguage => Luau.tmLanguage} (100%) diff --git a/.gitmodules b/.gitmodules index addd3ea5b8..a87e943d26 100644 --- a/.gitmodules +++ b/.gitmodules @@ -848,8 +848,8 @@ [submodule "vendor/grammars/lua.tmbundle"] path = vendor/grammars/lua.tmbundle url = https://github.com/textmate/lua.tmbundle -[submodule "vendor/grammars/luau.tmLanguage"] - path = vendor/grammars/luau.tmLanguage +[submodule "vendor/grammars/Luau.tmLanguage"] + path = vendor/grammars/Luau.tmLanguage url = https://github.com/JohnnyMorganz/Luau.tmLanguage.git [submodule "vendor/grammars/m3"] path = vendor/grammars/m3 diff --git a/vendor/grammars/luau.tmLanguage b/vendor/grammars/Luau.tmLanguage similarity index 100% rename from vendor/grammars/luau.tmLanguage rename to vendor/grammars/Luau.tmLanguage From f55cbc55e1144a174c9a79636556bec387e52dd6 Mon Sep 17 00:00:00 2001 From: Demo <76854027+RobloxianDemo@users.noreply.github.com> Date: Sun, 12 Nov 2023 19:04:24 +0000 Subject: [PATCH 11/29] Patch: Resolve grammar conflict. --- grammars.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grammars.yml b/grammars.yml index 4904457a7d..bd00c0d773 100644 --- a/grammars.yml +++ b/grammars.yml @@ -809,7 +809,7 @@ vendor/grammars/logtalk.tmbundle: - source.logtalk vendor/grammars/lua.tmbundle: - source.lua -vendor/grammars/luau.tmLanguage: +vendor/grammars/Luau.tmLanguage: - source.luau vendor/grammars/m3: - source.modula-3 From b99f319d48d18189871a8ef76dc7f3ad60723006 Mon Sep 17 00:00:00 2001 From: Demo <76854027+RobloxianDemo@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:59:23 -0500 Subject: [PATCH 12/29] Patch: Update the hex value. --- lib/linguist/languages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index 5007977038..c389b44f73 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -3885,7 +3885,7 @@ Luau: ace_mode: lua codemirror_mode: lua codemirror_mime_type: text/x-lua - color: "#14a5ff" + color: "#479df9" extensions: - ".luau" interpreters: From 92bed6ff79b75156515a7d3d2328a7fb17403495 Mon Sep 17 00:00:00 2001 From: Demo <76854027+RobloxianDemo@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:08:14 -0500 Subject: [PATCH 13/29] Patch: Update the hex value. --- lib/linguist/languages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index c389b44f73..bdb2451133 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -3885,7 +3885,7 @@ Luau: ace_mode: lua codemirror_mode: lua codemirror_mime_type: text/x-lua - color: "#479df9" + color: "#00A2FF" extensions: - ".luau" interpreters: From 3ae7360c5511ad6ad7156afc480f940433167e73 Mon Sep 17 00:00:00 2001 From: Colin Seymour Date: Wed, 6 Dec 2023 12:13:30 +0000 Subject: [PATCH 14/29] Sort --- .gitmodules | 6 +++--- grammars.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index a87e943d26..43cad251d2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -61,6 +61,9 @@ [submodule "vendor/grammars/LiveScript.tmbundle"] path = vendor/grammars/LiveScript.tmbundle url = https://github.com/paulmillr/LiveScript.tmbundle +[submodule "vendor/grammars/Luau.tmLanguage"] + path = vendor/grammars/Luau.tmLanguage + url = https://github.com/JohnnyMorganz/Luau.tmLanguage.git [submodule "vendor/grammars/MATLAB-Language-grammar"] path = vendor/grammars/MATLAB-Language-grammar url = https://github.com/mathworks/MATLAB-Language-grammar @@ -848,9 +851,6 @@ [submodule "vendor/grammars/lua.tmbundle"] path = vendor/grammars/lua.tmbundle url = https://github.com/textmate/lua.tmbundle -[submodule "vendor/grammars/Luau.tmLanguage"] - path = vendor/grammars/Luau.tmLanguage - url = https://github.com/JohnnyMorganz/Luau.tmLanguage.git [submodule "vendor/grammars/m3"] path = vendor/grammars/m3 url = https://github.com/newgrammars/m3 diff --git a/grammars.yml b/grammars.yml index bd00c0d773..917ab6f584 100644 --- a/grammars.yml +++ b/grammars.yml @@ -50,6 +50,8 @@ vendor/grammars/Ligo-grammar: - source.religo vendor/grammars/LiveScript.tmbundle: - source.livescript +vendor/grammars/Luau.tmLanguage: +- source.luau vendor/grammars/MATLAB-Language-grammar: - source.matlab vendor/grammars/MQL5-sublime: @@ -809,8 +811,6 @@ vendor/grammars/logtalk.tmbundle: - source.logtalk vendor/grammars/lua.tmbundle: - source.lua -vendor/grammars/Luau.tmLanguage: -- source.luau vendor/grammars/m3: - source.modula-3 vendor/grammars/make.tmbundle: From 5f21201c94e239a4c81379f00e6e9cc4704aef4a Mon Sep 17 00:00:00 2001 From: Demo Date: Wed, 3 Apr 2024 11:21:32 +0000 Subject: [PATCH 15/29] Patch: Update the submodule to the latest commit. --- vendor/grammars/Luau.tmLanguage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/grammars/Luau.tmLanguage b/vendor/grammars/Luau.tmLanguage index 9f320add6d..bc4170802a 160000 --- a/vendor/grammars/Luau.tmLanguage +++ b/vendor/grammars/Luau.tmLanguage @@ -1 +1 @@ -Subproject commit 9f320add6dd099f99897a03c5e2ce3ca5f176d7f +Subproject commit bc4170802aab182127f2b6b80b0e1164f50d78c1 From 405f7e49f8da703fd47158a4ca7b1d03f3022030 Mon Sep 17 00:00:00 2001 From: Demo Date: Wed, 3 Apr 2024 17:36:16 +0000 Subject: [PATCH 16/29] Patch: Resolve merge conflicts. --- .gitmodules | 2 +- grammars.yml | 3 - vendor/README.md | 3 +- ...nguage.dep.yml => Luau.tmLanguage.dep.yml} | 9 +- .../git_submodule/vscode-vba-json.dep.yml | 383 ------------------ 5 files changed, 7 insertions(+), 393 deletions(-) rename vendor/licenses/git_submodule/{luau.tmLanguage.dep.yml => Luau.tmLanguage.dep.yml} (96%) delete mode 100644 vendor/licenses/git_submodule/vscode-vba-json.dep.yml diff --git a/.gitmodules b/.gitmodules index 43cad251d2..60c07ac6e6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -850,7 +850,7 @@ url = https://github.com/textmate/logtalk.tmbundle [submodule "vendor/grammars/lua.tmbundle"] path = vendor/grammars/lua.tmbundle - url = https://github.com/textmate/lua.tmbundle + url = https://github.com/LuaLS/lua.tmbundle.git [submodule "vendor/grammars/m3"] path = vendor/grammars/m3 url = https://github.com/newgrammars/m3 diff --git a/grammars.yml b/grammars.yml index 917ab6f584..92fc155976 100644 --- a/grammars.yml +++ b/grammars.yml @@ -1155,9 +1155,6 @@ vendor/grammars/vscode-singularity: - source.singularity vendor/grammars/vscode-slice: - source.slice -vendor/grammars/vscode-vba-json: -- source.vba -- source.wwb vendor/grammars/vscode-vlang: - source.v vendor/grammars/vscode-wit: diff --git a/vendor/README.md b/vendor/README.md index 1c73c47dc4..f0c48a7a33 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -312,7 +312,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Logtalk:** [textmate/logtalk.tmbundle](https://github.com/textmate/logtalk.tmbundle) - **LookML:** [atom/language-yaml](https://github.com/atom/language-yaml) - **LoomScript:** [ambethia/Sublime-Loom](https://github.com/ambethia/Sublime-Loom) -- **Lua:** [textmate/lua.tmbundle](https://github.com/textmate/lua.tmbundle) +- **Lua:** [LuaLS/lua.tmbundle](https://github.com/LuaLS/lua.tmbundle) - **Luau:** [JohnnyMorganz/Luau.tmLanguage](https://github.com/JohnnyMorganz/Luau.tmLanguage) - **M4:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **M4Sugar:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) @@ -567,7 +567,6 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **UnrealScript:** [textmate/java.tmbundle](https://github.com/textmate/java.tmbundle) - **UrWeb:** [gwalborn/UrWeb-Language-Definition](https://github.com/gwalborn/UrWeb-Language-Definition) - **V:** [0x9ef/vscode-vlang](https://github.com/0x9ef/vscode-vlang) -- **VBA:** [tiabeast/vscode-vba-json](https://github.com/tiabeast/vscode-vba-json) - **VBScript:** [peters-ben-0007/VBDotNetSyntax](https://github.com/peters-ben-0007/VBDotNetSyntax) - **VCL:** [brandonwamboldt/sublime-varnish](https://github.com/brandonwamboldt/sublime-varnish) - **VHDL:** [textmate/vhdl.tmbundle](https://github.com/textmate/vhdl.tmbundle) diff --git a/vendor/licenses/git_submodule/luau.tmLanguage.dep.yml b/vendor/licenses/git_submodule/Luau.tmLanguage.dep.yml similarity index 96% rename from vendor/licenses/git_submodule/luau.tmLanguage.dep.yml rename to vendor/licenses/git_submodule/Luau.tmLanguage.dep.yml index 83c1503249..dbf914eb5f 100644 --- a/vendor/licenses/git_submodule/luau.tmLanguage.dep.yml +++ b/vendor/licenses/git_submodule/Luau.tmLanguage.dep.yml @@ -1,11 +1,12 @@ -name: Luau.tmLanguag -version: 9f320add6dd099f99897a03c5e2ce3ca5f176d7f +--- +name: Luau.tmLanguage +version: bc4170802aab182127f2b6b80b0e1164f50d78c1 type: git_submodule homepage: https://github.com/JohnnyMorganz/Luau.tmLanguage.git -license: mit +license: other licenses: - sources: LICENSE.md - text: |- + text: | Copyright (c) JohnnyMorganz All rights reserved. diff --git a/vendor/licenses/git_submodule/vscode-vba-json.dep.yml b/vendor/licenses/git_submodule/vscode-vba-json.dep.yml deleted file mode 100644 index f15284308a..0000000000 --- a/vendor/licenses/git_submodule/vscode-vba-json.dep.yml +++ /dev/null @@ -1,383 +0,0 @@ ---- -name: vscode-vba-json -version: 4df0db50f52c8e4d48f590c9a517ced204e8dce9 -type: git_submodule -homepage: https://github.com/tiabeast/vscode-vba-json.git -license: mpl-2.0 -licenses: -- sources: LICENSE.txt - text: | - Mozilla Public License Version 2.0 - ================================== - - 1. Definitions - -------------- - - 1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - - 1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - - 1.3. "Contribution" - means Covered Software of a particular Contributor. - - 1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - - 1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - - 1.6. "Executable Form" - means any form of the work other than Source Code Form. - - 1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - - 1.8. "License" - means this document. - - 1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - - 1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - - 1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - - 1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - - 1.13. "Source Code Form" - means the form of the work preferred for making modifications. - - 1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - - 2. License Grants and Conditions - -------------------------------- - - 2.1. Grants - - Each Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - (a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - - (b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - - 2.2. Effective Date - - The licenses granted in Section 2.1 with respect to any Contribution - become effective for each Contribution on the date the Contributor first - distributes such Contribution. - - 2.3. Limitations on Grant Scope - - The licenses granted in this Section 2 are the only rights granted under - this License. No additional rights or licenses will be implied from the - distribution or licensing of Covered Software under this License. - Notwithstanding Section 2.1(b) above, no patent license is granted by a - Contributor: - - (a) for any code that a Contributor has removed from Covered Software; - or - - (b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - - (c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - - This License does not grant any rights in the trademarks, service marks, - or logos of any Contributor (except as may be necessary to comply with - the notice requirements in Section 3.4). - - 2.4. Subsequent Licenses - - No Contributor makes additional grants as a result of Your choice to - distribute the Covered Software under a subsequent version of this - License (see Section 10.2) or under the terms of a Secondary License (if - permitted under the terms of Section 3.3). - - 2.5. Representation - - Each Contributor represents that the Contributor believes its - Contributions are its original creation(s) or it has sufficient rights - to grant the rights to its Contributions conveyed by this License. - - 2.6. Fair Use - - This License is not intended to limit any rights You have under - applicable copyright doctrines of fair use, fair dealing, or other - equivalents. - - 2.7. Conditions - - Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted - in Section 2.1. - - 3. Responsibilities - ------------------- - - 3.1. Distribution of Source Form - - All distribution of Covered Software in Source Code Form, including any - Modifications that You create or to which You contribute, must be under - the terms of this License. You must inform recipients that the Source - Code Form of the Covered Software is governed by the terms of this - License, and how they can obtain a copy of this License. You may not - attempt to alter or restrict the recipients' rights in the Source Code - Form. - - 3.2. Distribution of Executable Form - - If You distribute Covered Software in Executable Form then: - - (a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - - (b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - - 3.3. Distribution of a Larger Work - - You may create and distribute a Larger Work under terms of Your choice, - provided that You also comply with the requirements of this License for - the Covered Software. If the Larger Work is a combination of Covered - Software with a work governed by one or more Secondary Licenses, and the - Covered Software is not Incompatible With Secondary Licenses, this - License permits You to additionally distribute such Covered Software - under the terms of such Secondary License(s), so that the recipient of - the Larger Work may, at their option, further distribute the Covered - Software under the terms of either this License or such Secondary - License(s). - - 3.4. Notices - - You may not remove or alter the substance of any license notices - (including copyright notices, patent notices, disclaimers of warranty, - or limitations of liability) contained within the Source Code Form of - the Covered Software, except that You may alter any license notices to - the extent required to remedy known factual inaccuracies. - - 3.5. Application of Additional Terms - - You may choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of Covered - Software. However, You may do so only on Your own behalf, and not on - behalf of any Contributor. You must make it absolutely clear that any - such warranty, support, indemnity, or liability obligation is offered by - You alone, and You hereby agree to indemnify every Contributor for any - liability incurred by such Contributor as a result of warranty, support, - indemnity or liability terms You offer. You may include additional - disclaimers of warranty and limitations of liability specific to any - jurisdiction. - - 4. Inability to Comply Due to Statute or Regulation - --------------------------------------------------- - - If it is impossible for You to comply with any of the terms of this - License with respect to some or all of the Covered Software due to - statute, judicial order, or regulation then You must: (a) comply with - the terms of this License to the maximum extent possible; and (b) - describe the limitations and the code they affect. Such description must - be placed in a text file included with all distributions of the Covered - Software under this License. Except to the extent prohibited by statute - or regulation, such description must be sufficiently detailed for a - recipient of ordinary skill to be able to understand it. - - 5. Termination - -------------- - - 5.1. The rights granted under this License will terminate automatically - if You fail to comply with any of its terms. However, if You become - compliant, then the rights granted under this License from a particular - Contributor are reinstated (a) provisionally, unless and until such - Contributor explicitly and finally terminates Your grants, and (b) on an - ongoing basis, if such Contributor fails to notify You of the - non-compliance by some reasonable means prior to 60 days after You have - come back into compliance. Moreover, Your grants from a particular - Contributor are reinstated on an ongoing basis if such Contributor - notifies You of the non-compliance by some reasonable means, this is the - first time You have received notice of non-compliance with this License - from such Contributor, and You become compliant prior to 30 days after - Your receipt of the notice. - - 5.2. If You initiate litigation against any entity by asserting a patent - infringement claim (excluding declaratory judgment actions, - counter-claims, and cross-claims) alleging that a Contributor Version - directly or indirectly infringes any patent, then the rights granted to - You by any and all Contributors for the Covered Software under Section - 2.1 of this License shall terminate. - - 5.3. In the event of termination under Sections 5.1 or 5.2 above, all - end user license agreements (excluding distributors and resellers) which - have been validly granted by You or Your distributors under this License - prior to termination shall survive termination. - - ************************************************************************ - * * - * 6. Disclaimer of Warranty * - * ------------------------- * - * * - * Covered Software is provided under this License on an "as is" * - * basis, without warranty of any kind, either expressed, implied, or * - * statutory, including, without limitation, warranties that the * - * Covered Software is free of defects, merchantable, fit for a * - * particular purpose or non-infringing. The entire risk as to the * - * quality and performance of the Covered Software is with You. * - * Should any Covered Software prove defective in any respect, You * - * (not any Contributor) assume the cost of any necessary servicing, * - * repair, or correction. This disclaimer of warranty constitutes an * - * essential part of this License. No use of any Covered Software is * - * authorized under this License except under this disclaimer. * - * * - ************************************************************************ - - ************************************************************************ - * * - * 7. Limitation of Liability * - * -------------------------- * - * * - * Under no circumstances and under no legal theory, whether tort * - * (including negligence), contract, or otherwise, shall any * - * Contributor, or anyone who distributes Covered Software as * - * permitted above, be liable to You for any direct, indirect, * - * special, incidental, or consequential damages of any character * - * including, without limitation, damages for lost profits, loss of * - * goodwill, work stoppage, computer failure or malfunction, or any * - * and all other commercial damages or losses, even if such party * - * shall have been informed of the possibility of such damages. This * - * limitation of liability shall not apply to liability for death or * - * personal injury resulting from such party's negligence to the * - * extent applicable law prohibits such limitation. Some * - * jurisdictions do not allow the exclusion or limitation of * - * incidental or consequential damages, so this exclusion and * - * limitation may not apply to You. * - * * - ************************************************************************ - - 8. Litigation - ------------- - - Any litigation relating to this License may be brought only in the - courts of a jurisdiction where the defendant maintains its principal - place of business and such litigation shall be governed by laws of that - jurisdiction, without reference to its conflict-of-law provisions. - Nothing in this Section shall prevent a party's ability to bring - cross-claims or counter-claims. - - 9. Miscellaneous - ---------------- - - This License represents the complete agreement concerning the subject - matter hereof. If any provision of this License is held to be - unenforceable, such provision shall be reformed only to the extent - necessary to make it enforceable. Any law or regulation which provides - that the language of a contract shall be construed against the drafter - shall not be used to construe this License against a Contributor. - - 10. Versions of the License - --------------------------- - - 10.1. New Versions - - Mozilla Foundation is the license steward. Except as provided in Section - 10.3, no one other than the license steward has the right to modify or - publish new versions of this License. Each version will be given a - distinguishing version number. - - 10.2. Effect of New Versions - - You may distribute the Covered Software under the terms of the version - of the License under which You originally received the Covered Software, - or under the terms of any subsequent version published by the license - steward. - - 10.3. Modified Versions - - If you create software not governed by this License, and you want to - create a new license for such software, you may create and use a - modified version of this License if you rename the license and remove - any references to the name of the license steward (except to note that - such modified license differs from this License). - - 10.4. Distributing Source Code Form that is Incompatible With Secondary - Licenses - - If You choose to distribute Source Code Form that is Incompatible With - Secondary Licenses under the terms of this version of the License, the - notice described in Exhibit B of this License must be attached. - - Exhibit A - Source Code Form License Notice - ------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - - If it is not possible or desirable to put the notice in a particular - file, then You may include the notice in a location (such as a LICENSE - file in a relevant directory) where a recipient would be likely to look - for such a notice. - - You may add additional accurate notices of copyright ownership. - - Exhibit B - "Incompatible With Secondary Licenses" Notice - --------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. -notices: [] From f0db0755569050a8b306ba7351ce7587eca740c0 Mon Sep 17 00:00:00 2001 From: Demo Date: Wed, 3 Apr 2024 17:42:17 +0000 Subject: [PATCH 17/29] Patch: Resolve further conflicts. --- grammars.yml | 69 +++++++++++++++++++++++++++++++++++++++++------- vendor/README.md | 33 +++++++++++++++++------ 2 files changed, 85 insertions(+), 17 deletions(-) diff --git a/grammars.yml b/grammars.yml index 92fc155976..f9d10fd5a2 100644 --- a/grammars.yml +++ b/grammars.yml @@ -32,6 +32,9 @@ vendor/grammars/Elm/Syntaxes: - text.html.mediawiki.elm-documentation vendor/grammars/FreeMarker.tmbundle: - text.html.ftl +vendor/grammars/GeneroFgl.tmbundle: +- source.genero-4gl +- source.genero-per vendor/grammars/Handlebars: - text.html.handlebars vendor/grammars/IDL-Syntax: @@ -269,9 +272,6 @@ vendor/grammars/atom-miniyaml: vendor/grammars/atom-salt: - source.python.salt - source.yaml.salt -vendor/grammars/atomic-dreams: -- source.dm -- source.dmf vendor/grammars/ats: - source.ats vendor/grammars/avro.tmLanguage: @@ -315,6 +315,9 @@ vendor/grammars/clarity.tmbundle: vendor/grammars/cmake.tmbundle: - source.cache.cmake - source.cmake +vendor/grammars/common-lisp-tmlanguage: +- markdown.commonlisp.codeblock +- source.commonlisp vendor/grammars/conllu-linguist-grammar: - text.conllu vendor/grammars/cool-tmbundle: @@ -347,6 +350,8 @@ vendor/grammars/desktop.tmbundle: - source.desktop vendor/grammars/diff.tmbundle: - source.diff +vendor/grammars/dm-syntax: +- source.dm vendor/grammars/dylan.tmbundle: - source.dylan - source.lid @@ -357,6 +362,11 @@ vendor/grammars/ec.tmbundle: - source.c.ec vendor/grammars/ecl-tmLanguage: - source.ecl +vendor/grammars/edge-vscode: +- text.html.edge +vendor/grammars/edgedb-editor-plugin: +- inline.edgeql +- source.edgeql vendor/grammars/eiffel.tmbundle: - source.eiffel vendor/grammars/ejs-tmbundle: @@ -390,9 +400,6 @@ vendor/grammars/gemfile-lock-tmlanguage: - source.gemfile-lock vendor/grammars/gemini-vscode: - source.gemini -vendor/grammars/genero.tmbundle: -- source.genero -- source.genero-forms vendor/grammars/gettext.tmbundle: - source.po vendor/grammars/gnuplot-tmbundle: @@ -409,12 +416,13 @@ vendor/grammars/gradle.tmbundle: - source.groovy.gradle vendor/grammars/graphiql: - inline.graphql +- inline.graphql.markdown.codeblock - inline.graphql.php - inline.graphql.python +- inline.graphql.rb - inline.graphql.re - inline.graphql.res - inline.graphql.scala -- markdown.graphql.codeblock - source.graphql vendor/grammars/graphviz.tmbundle: - source.dot @@ -433,6 +441,8 @@ vendor/grammars/holyc.tmbundle: - source.hc vendor/grammars/hoon-grammar: - source.hoon +vendor/grammars/ide-tools: +- source.toit vendor/grammars/idl.tmbundle: - source.idl - source.idl-dlm @@ -490,6 +500,8 @@ vendor/grammars/language-basic: - source.basic vendor/grammars/language-batchfile: - source.batchfile +vendor/grammars/language-bh: +- source.bh vendor/grammars/language-blade: - source.gfm.blade - text.html.php.blade @@ -764,6 +776,8 @@ vendor/grammars/language-supercollider: vendor/grammars/language-texinfo: - text.info - text.texinfo +vendor/grammars/language-ti-basic: +- source.8xp vendor/grammars/language-toc-wow: - source.toc vendor/grammars/language-tsql: @@ -834,6 +848,8 @@ vendor/grammars/mint-vscode: - source.mint vendor/grammars/mlir-grammar: - source.mlir +vendor/grammars/mojo-syntax: +- source.mojo vendor/grammars/monkey: - source.monkey vendor/grammars/moonscript-tmbundle: @@ -891,6 +907,9 @@ vendor/grammars/portugol-grammar: - source.portugol vendor/grammars/powershell: - source.powershell +vendor/grammars/praatvscode: +- source.praat +- source.textgrid vendor/grammars/processing.tmbundle: - source.processing vendor/grammars/python-django.tmbundle: @@ -914,6 +933,8 @@ vendor/grammars/rez.tmbundle: - source.rez vendor/grammars/riot-syntax-highlight: - text.html.riot +vendor/grammars/roc-vscode-unofficial: +- source.roc vendor/grammars/ruby-slim.tmbundle: - text.slim vendor/grammars/rust-syntax: @@ -932,6 +953,8 @@ vendor/grammars/selinux-policy-languages: vendor/grammars/shaders-tmLanguage: - source.hlsl - source.shaderlab +vendor/grammars/slint-tmLanguage: +- source.slint vendor/grammars/smali-sublime: - source.smali vendor/grammars/smalltalk-tmbundle: @@ -939,7 +962,11 @@ vendor/grammars/smalltalk-tmbundle: vendor/grammars/smithy-vscode: - source.smithy vendor/grammars/sourcepawn-vscode: +- source.amxxpawn - source.sourcepawn +- sp-jsdoc.injection +- text.valve-cfg +- text.valve-kv vendor/grammars/sql.tmbundle: - source.sql vendor/grammars/squirrel-language: @@ -1032,8 +1059,12 @@ vendor/grammars/sway-vscode-plugin: - source.sway vendor/grammars/sweave.tmbundle: - text.tex.latex.sweave -vendor/grammars/swift.tmbundle: +vendor/grammars/swift-tmlanguage: - source.swift +vendor/grammars/syntax: +- source.hcl +- source.hcl.terraform +- source.sentinel vendor/grammars/tcl.tmbundle: - source.tcl - text.html.tcl @@ -1059,6 +1090,13 @@ vendor/grammars/verilog.tmbundle: - source.verilog vendor/grammars/vhdl: - source.vhdl +vendor/grammars/vsc-ember-syntax: +- inline.hbs +- inline.template +- markdown.glimmer.codeblock +- source.gjs +- source.gts +- text.html.ember-handlebars vendor/grammars/vsc-fennel: - source.fnl vendor/grammars/vscode-TalonScript: @@ -1066,6 +1104,8 @@ vendor/grammars/vscode-TalonScript: - source.talon vendor/grammars/vscode-antlers-language-server: - text.html.statamic +vendor/grammars/vscode-bitbake: +- source.bb vendor/grammars/vscode-brightscript-language: - source.brs vendor/grammars/vscode-cadence: @@ -1092,7 +1132,6 @@ vendor/grammars/vscode-gleam: vendor/grammars/vscode-go: - go.mod - go.sum -- govulncheck vendor/grammars/vscode-hack: - markdown.hack.codeblock - source.hack @@ -1121,12 +1160,18 @@ vendor/grammars/vscode-lean: - markdown.lean.codeblock - source.lean - source.lean.markdown +vendor/grammars/vscode-lean4: +- markdown.lean4.codeblock +- source.lean4 +- source.lean4.markdown vendor/grammars/vscode-monkey-c: - source.mc vendor/grammars/vscode-motoko: - source.did - source.mo vendor/grammars/vscode-move-syntax: +- markdown.move.codeblock +- mdx.LANGUAGE.codeblock - source.move vendor/grammars/vscode-opa: - source.rego @@ -1147,6 +1192,8 @@ vendor/grammars/vscode-procfile: vendor/grammars/vscode-proto3: - markdown.codeblock.proto - source.proto +vendor/grammars/vscode-python: +- source.pip-requirements vendor/grammars/vscode-rbs-syntax: - source.rbs vendor/grammars/vscode-scala-syntax: @@ -1154,7 +1201,11 @@ vendor/grammars/vscode-scala-syntax: vendor/grammars/vscode-singularity: - source.singularity vendor/grammars/vscode-slice: +- source.ice - source.slice +vendor/grammars/vscode-vba: +- source.vba +- source.wwb vendor/grammars/vscode-vlang: - source.v vendor/grammars/vscode-wit: diff --git a/vendor/README.md b/vendor/README.md index f0c48a7a33..81a7a77cea 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -57,11 +57,12 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Bicep:** [azure/bicep](https://github.com/azure/bicep) - **Bikeshed:** [tabatkins/bikeshed](https://github.com/tabatkins/bikeshed) - **Bison:** [Alhadis/language-grammars](https://github.com/Alhadis/language-grammars) +- **BitBake:** [yoctoproject/vscode-bitbake](https://github.com/yoctoproject/vscode-bitbake) - **Blade:** [jawee/language-blade](https://github.com/jawee/language-blade) - **BlitzBasic:** [textmate/blitzmax.tmbundle](https://github.com/textmate/blitzmax.tmbundle) - **BlitzMax:** [textmate/blitzmax.tmbundle](https://github.com/textmate/blitzmax.tmbundle) - **Bluespec:** [thotypous/sublime-bsv](https://github.com/thotypous/sublime-bsv) -- **Bluespec BH:** [atom-haskell/language-haskell](https://github.com/atom-haskell/language-haskell) +- **Bluespec BH:** [B-Lang-org/language-bh](https://github.com/B-Lang-org/language-bh) - **Boo:** [drslump/sublime-boo](https://github.com/drslump/sublime-boo) - **Boogie:** [boogie-org/boogie-vscode](https://github.com/boogie-org/boogie-vscode) - **Brainfuck:** [Drako/SublimeBrainfuck](https://github.com/Drako/SublimeBrainfuck) @@ -108,7 +109,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **CoffeeScript:** [atom/language-coffee-script](https://github.com/atom/language-coffee-script) - **ColdFusion:** [SublimeText/ColdFusion](https://github.com/SublimeText/ColdFusion) - **ColdFusion CFC:** [SublimeText/ColdFusion](https://github.com/SublimeText/ColdFusion) -- **Common Lisp:** [textmate/lisp.tmbundle](https://github.com/textmate/lisp.tmbundle) +- **Common Lisp:** [qingpeng9802/common-lisp-tmlanguage](https://github.com/qingpeng9802/common-lisp-tmlanguage) - **Common Workflow Language:** [manabuishii/language-cwl](https://github.com/manabuishii/language-cwl) - **Component Pascal:** [textmate/pascal.tmbundle](https://github.com/textmate/pascal.tmbundle) - **Cool:** [anunayk/cool-tmbundle](https://github.com/anunayk/cool-tmbundle) @@ -128,7 +129,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **D:** [textmate/d.tmbundle](https://github.com/textmate/d.tmbundle) - **D-ObjDump:** [nanoant/assembly.tmbundle](https://github.com/nanoant/assembly.tmbundle) - **D2:** [terrastruct/d2-vscode](https://github.com/terrastruct/d2-vscode) -- **DM:** [PJB3005/atomic-dreams](https://github.com/PJB3005/atomic-dreams) +- **DM:** [spacestation13/dm-syntax](https://github.com/spacestation13/dm-syntax) - **DNS Zone:** [sixty4k/st2-zonefile](https://github.com/sixty4k/st2-zonefile) - **DTrace:** [textmate/c.tmbundle](https://github.com/textmate/c.tmbundle) - **Dafny:** [DafnyVSCode/Dafny-VSCode](https://github.com/DafnyVSCode/Dafny-VSCode) @@ -152,6 +153,8 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Easybuild:** [MagicStack/MagicPython](https://github.com/MagicStack/MagicPython) - **Ecere Projects:** [Nixinova/NovaGrammars](https://github.com/Nixinova/NovaGrammars) - **Ecmarkup:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) +- **Edge:** [edge-js/edge-vscode](https://github.com/edge-js/edge-vscode) +- **EdgeQL:** [edgedb/edgedb-editor-plugin](https://github.com/edgedb/edgedb-editor-plugin) - **EditorConfig:** [sindresorhus/atom-editorconfig](https://github.com/sindresorhus/atom-editorconfig) - **Edje Data Collection:** [textmate/c.tmbundle](https://github.com/textmate/c.tmbundle) - **Eiffel:** [textmate/eiffel.tmbundle](https://github.com/textmate/eiffel.tmbundle) @@ -190,8 +193,8 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Game Maker Language:** [textmate/c.tmbundle](https://github.com/textmate/c.tmbundle) - **Gemfile.lock:** [hmarr/gemfile-lock-tmlanguage](https://github.com/hmarr/gemfile-lock-tmlanguage) - **Gemini:** [printfn/gemini-vscode](https://github.com/printfn/gemini-vscode) -- **Genero:** [alienriver49/genero.tmbundle](https://github.com/alienriver49/genero.tmbundle) -- **Genero Forms:** [alienriver49/genero.tmbundle](https://github.com/alienriver49/genero.tmbundle) +- **Genero 4gl:** [FourjsGenero/GeneroFgl.tmbundle](https://github.com/FourjsGenero/GeneroFgl.tmbundle) +- **Genero per:** [FourjsGenero/GeneroFgl.tmbundle](https://github.com/FourjsGenero/GeneroFgl.tmbundle) - **Genshi:** [genshi.edgewall.org/query](https://genshi.edgewall.org/query) - **Gentoo Ebuild:** [atom/language-shellscript](https://github.com/atom/language-shellscript) - **Gentoo Eclass:** [atom/language-shellscript](https://github.com/atom/language-shellscript) @@ -202,6 +205,8 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Git Config:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **Git Revision List:** [Nixinova/NovaGrammars](https://github.com/Nixinova/NovaGrammars) - **Gleam:** [gleam-lang/tree-sitter-gleam](https://github.com/gleam-lang/tree-sitter-gleam) 🐌 +- **Glimmer JS:** [lifeart/vsc-ember-syntax](https://github.com/lifeart/vsc-ember-syntax) +- **Glimmer TS:** [lifeart/vsc-ember-syntax](https://github.com/lifeart/vsc-ember-syntax) - **Glyph:** [textmate/tcl.tmbundle](https://github.com/textmate/tcl.tmbundle) - **Glyph Bitmap Distribution Format:** [Alhadis/language-fontforge](https://github.com/Alhadis/language-fontforge) - **Gnuplot:** [mattfoster/gnuplot-tmbundle](https://github.com/mattfoster/gnuplot-tmbundle) @@ -300,6 +305,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Lasso:** [bfad/Sublime-Lasso](https://github.com/bfad/Sublime-Lasso) - **Latte:** [textmate/php-smarty.tmbundle](https://github.com/textmate/php-smarty.tmbundle) - **Lean:** [leanprover/vscode-lean](https://github.com/leanprover/vscode-lean) +- **Lean 4:** [leanprover/vscode-lean4](https://github.com/leanprover/vscode-lean4) - **Less:** [atom/language-less](https://github.com/atom/language-less) - **Lex:** [Alhadis/language-grammars](https://github.com/Alhadis/language-grammars) - **LigoLANG:** [pewulfman/Ligo-grammar](https://github.com/pewulfman/Ligo-grammar) @@ -343,6 +349,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Modelica:** [BorisChumichev/modelicaSublimeTextPackage](https://github.com/BorisChumichev/modelicaSublimeTextPackage) - **Modula-2:** [harogaston/Sublime-Modula-2](https://github.com/harogaston/Sublime-Modula-2) - **Modula-3:** [newgrammars/m3](https://github.com/newgrammars/m3) +- **Mojo:** [modularml/mojo-syntax](https://github.com/modularml/mojo-syntax) - **Monkey:** [gingerbeardman/monkey.tmbundle](https://github.com/gingerbeardman/monkey.tmbundle) - **Monkey C:** [ghisguth/vscode-monkey-c](https://github.com/ghisguth/vscode-monkey-c) - **MoonScript:** [leafo/moonscript-tmbundle](https://github.com/leafo/moonscript-tmbundle) @@ -378,6 +385,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **OASv3-json:** [Nixinova/NovaGrammars](https://github.com/Nixinova/NovaGrammars) - **OASv3-yaml:** [atom/language-yaml](https://github.com/atom/language-yaml) - **OCaml:** [textmate/ocaml.tmbundle](https://github.com/textmate/ocaml.tmbundle) +- **Oberon:** [harogaston/Sublime-Modula-2](https://github.com/harogaston/Sublime-Modula-2) - **ObjDump:** [nanoant/assembly.tmbundle](https://github.com/nanoant/assembly.tmbundle) - **Object Data Instance Notation:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **ObjectScript:** [intersystems-community/ObjectScript.tmBundle](https://github.com/intersystems-community/ObjectScript.tmBundle) @@ -387,7 +395,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Odin:** [odin-lang/sublime-odin](https://github.com/odin-lang/sublime-odin) - **Opa:** [mads379/opa.tmbundle](https://github.com/mads379/opa.tmbundle) - **Opal:** [artifactz/sublime-opal](https://github.com/artifactz/sublime-opal) -- **Open Policy Agent:** [tsandall/vscode-opa](https://github.com/tsandall/vscode-opa) +- **Open Policy Agent:** [open-policy-agent/vscode-opa](https://github.com/open-policy-agent/vscode-opa) - **OpenCL:** [textmate/c.tmbundle](https://github.com/textmate/c.tmbundle) - **OpenEdge ABL:** [chriscamicas/abl-tmlanguage](https://github.com/chriscamicas/abl-tmlanguage) - **OpenQASM:** [tareqdandachi/language-qasm](https://github.com/tareqdandachi/language-qasm) @@ -416,6 +424,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **PicoLisp:** [textmate/lisp.tmbundle](https://github.com/textmate/lisp.tmbundle) - **PigLatin:** [goblindegook/sublime-text-pig-latin](https://github.com/goblindegook/sublime-text-pig-latin) - **Pike:** [hww3/pike-textmate](https://github.com/hww3/pike-textmate) +- **Pip Requirements:** [microsoft/vscode-python](https://github.com/microsoft/vscode-python) - **PlantUML:** [qjebbs/vscode-plantuml](https://github.com/qjebbs/vscode-plantuml) - **Pod 6:** [perl6/atom-language-perl6](https://github.com/perl6/atom-language-perl6) - **PogoScript:** [featurist/PogoScript.tmbundle](https://github.com/featurist/PogoScript.tmbundle) @@ -425,6 +434,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **PostCSS:** [hudochenkov/Syntax-highlighting-for-PostCSS](https://github.com/hudochenkov/Syntax-highlighting-for-PostCSS) - **PostScript:** [Alhadis/Atom-PostScript](https://github.com/Alhadis/Atom-PostScript) - **PowerShell:** [PowerShell/EditorSyntax](https://github.com/PowerShell/EditorSyntax) +- **Praat:** [orhunulusahin/praatvscode](https://github.com/orhunulusahin/praatvscode) - **Prisma:** [prisma/vscode-prisma](https://github.com/prisma/vscode-prisma) - **Processing:** [textmate/processing.tmbundle](https://github.com/textmate/processing.tmbundle) - **Procfile:** [benspaulding/vscode-procfile](https://github.com/benspaulding/vscode-procfile) @@ -474,6 +484,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Ring:** [MahmoudFayed/atom-language-ring](https://github.com/MahmoudFayed/atom-language-ring) - **Riot:** [riot/syntax-highlight](https://github.com/riot/syntax-highlight) - **RobotFramework:** [shellderp/sublime-robot-plugin](https://github.com/shellderp/sublime-robot-plugin) +- **Roc:** [ivan-demchenko/roc-vscode-unofficial](https://github.com/ivan-demchenko/roc-vscode-unofficial) - **Roff:** [Alhadis/language-roff](https://github.com/Alhadis/language-roff) - **Roff Manpage:** [Alhadis/language-roff](https://github.com/Alhadis/language-roff) - **Rouge:** [atom/language-clojure](https://github.com/atom/language-clojure) @@ -513,6 +524,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Slash:** [slash-lang/Slash.tmbundle](https://github.com/slash-lang/Slash.tmbundle) - **Slice:** [zeroc-ice/vscode-slice](https://github.com/zeroc-ice/vscode-slice) - **Slim:** [slim-template/ruby-slim.tmbundle](https://github.com/slim-template/ruby-slim.tmbundle) +- **Slint:** [slint-ui/slint-tmLanguage](https://github.com/slint-ui/slint-tmLanguage) - **SmPL:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **Smali:** [ShaneWilton/sublime-smali](https://github.com/ShaneWilton/sublime-smali) - **Smalltalk:** [tomas-stefano/smalltalk-tmbundle](https://github.com/tomas-stefano/smalltalk-tmbundle) @@ -521,7 +533,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Snakemake:** [MagicStack/MagicPython](https://github.com/MagicStack/MagicPython) - **Solidity:** [davidhq/SublimeEthereum](https://github.com/davidhq/SublimeEthereum) - **Soong:** [flimberger/android-system-tools](https://github.com/flimberger/android-system-tools) -- **SourcePawn:** [Dreae/sourcepawn-vscode](https://github.com/Dreae/sourcepawn-vscode) +- **SourcePawn:** [Sarrus1/sourcepawn-vscode](https://github.com/Sarrus1/sourcepawn-vscode) - **Spline Font Database:** [Alhadis/language-fontforge](https://github.com/Alhadis/language-fontforge) - **Squirrel:** [mathewmariani/squirrel-language](https://github.com/mathewmariani/squirrel-language) - **Stan:** [stan-dev/atom-language-stan](https://github.com/stan-dev/atom-language-stan) @@ -536,8 +548,9 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **Svelte:** [sebastinez/svelte-atom](https://github.com/sebastinez/svelte-atom) - **Sway:** [FuelLabs/sway-vscode-plugin](https://github.com/FuelLabs/sway-vscode-plugin) - **Sweave:** [textmate/sweave.tmbundle](https://github.com/textmate/sweave.tmbundle) -- **Swift:** [textmate/swift.tmbundle](https://github.com/textmate/swift.tmbundle) +- **Swift:** [alex-pinkus/tree-sitter-swift](https://github.com/alex-pinkus/tree-sitter-swift) 🐌 - **SystemVerilog:** [TheClams/SystemVerilog](https://github.com/TheClams/SystemVerilog) +- **TI Program:** [TIny-Hacker/language-ti-basic](https://github.com/TIny-Hacker/language-ti-basic) - **TL-Verilog:** [adamint/tlv-vscode](https://github.com/adamint/tlv-vscode) - **TLA:** [tlaplus-community/tree-sitter-tlaplus](https://github.com/tlaplus-community/tree-sitter-tlaplus) 🐌 - **TOML:** [textmate/toml.tmbundle](https://github.com/textmate/toml.tmbundle) @@ -551,9 +564,12 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **TeX:** [textmate/latex.tmbundle](https://github.com/textmate/latex.tmbundle) - **Tea:** [pferruggiaro/sublime-tea](https://github.com/pferruggiaro/sublime-tea) - **Terra:** [pyk/sublime-terra](https://github.com/pyk/sublime-terra) +- **Terraform Template:** [hashicorp/syntax](https://github.com/hashicorp/syntax) - **Texinfo:** [Alhadis/language-texinfo](https://github.com/Alhadis/language-texinfo) +- **TextGrid:** [orhunulusahin/praatvscode](https://github.com/orhunulusahin/praatvscode) - **TextMate Properties:** [textmate/textmate.tmbundle](https://github.com/textmate/textmate.tmbundle) - **Thrift:** [textmate/thrift.tmbundle](https://github.com/textmate/thrift.tmbundle) +- **Toit:** [toitware/ide-tools](https://github.com/toitware/ide-tools) - **Turing:** [Alhadis/language-turing](https://github.com/Alhadis/language-turing) - **Turtle:** [peta/turtle.tmbundle](https://github.com/peta/turtle.tmbundle) - **Twig:** [Anomareh/PHP-Twig.tmbundle](https://github.com/Anomareh/PHP-Twig.tmbundle) @@ -567,6 +583,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **UnrealScript:** [textmate/java.tmbundle](https://github.com/textmate/java.tmbundle) - **UrWeb:** [gwalborn/UrWeb-Language-Definition](https://github.com/gwalborn/UrWeb-Language-Definition) - **V:** [0x9ef/vscode-vlang](https://github.com/0x9ef/vscode-vlang) +- **VBA:** [serkonda7/vscode-vba](https://github.com/serkonda7/vscode-vba) - **VBScript:** [peters-ben-0007/VBDotNetSyntax](https://github.com/peters-ben-0007/VBDotNetSyntax) - **VCL:** [brandonwamboldt/sublime-varnish](https://github.com/brandonwamboldt/sublime-varnish) - **VHDL:** [textmate/vhdl.tmbundle](https://github.com/textmate/vhdl.tmbundle) From 3588fea0473fcc03be18b8d7c0052890b988e4a2 Mon Sep 17 00:00:00 2001 From: Demo Date: Thu, 4 Apr 2024 16:51:05 +0000 Subject: [PATCH 18/29] Patch: Remove conflict. --- vendor/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/vendor/README.md b/vendor/README.md index 81a7a77cea..2dd99fc849 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -319,7 +319,6 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **LookML:** [atom/language-yaml](https://github.com/atom/language-yaml) - **LoomScript:** [ambethia/Sublime-Loom](https://github.com/ambethia/Sublime-Loom) - **Lua:** [LuaLS/lua.tmbundle](https://github.com/LuaLS/lua.tmbundle) -- **Luau:** [JohnnyMorganz/Luau.tmLanguage](https://github.com/JohnnyMorganz/Luau.tmLanguage) - **M4:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **M4Sugar:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **MATLAB:** [mathworks/MATLAB-Language-grammar](https://github.com/mathworks/MATLAB-Language-grammar) From f5eaf45e6de506b4c12555ef0617f168fc0bf383 Mon Sep 17 00:00:00 2001 From: Demo Date: Thu, 4 Apr 2024 16:53:04 +0000 Subject: [PATCH 19/29] Patch: Reintroduce Luau into grammar index. --- vendor/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/vendor/README.md b/vendor/README.md index 2dd99fc849..81a7a77cea 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -319,6 +319,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **LookML:** [atom/language-yaml](https://github.com/atom/language-yaml) - **LoomScript:** [ambethia/Sublime-Loom](https://github.com/ambethia/Sublime-Loom) - **Lua:** [LuaLS/lua.tmbundle](https://github.com/LuaLS/lua.tmbundle) +- **Luau:** [JohnnyMorganz/Luau.tmLanguage](https://github.com/JohnnyMorganz/Luau.tmLanguage) - **M4:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **M4Sugar:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **MATLAB:** [mathworks/MATLAB-Language-grammar](https://github.com/mathworks/MATLAB-Language-grammar) From 11736986224df7a29c39ac9cb0840406a7f32236 Mon Sep 17 00:00:00 2001 From: Demo Date: Thu, 4 Apr 2024 17:22:04 +0000 Subject: [PATCH 20/29] Revert "Patch: Reintroduce Luau into grammar index." This reverts commit f5eaf45e6de506b4c12555ef0617f168fc0bf383. --- vendor/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/vendor/README.md b/vendor/README.md index 81a7a77cea..2dd99fc849 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -319,7 +319,6 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **LookML:** [atom/language-yaml](https://github.com/atom/language-yaml) - **LoomScript:** [ambethia/Sublime-Loom](https://github.com/ambethia/Sublime-Loom) - **Lua:** [LuaLS/lua.tmbundle](https://github.com/LuaLS/lua.tmbundle) -- **Luau:** [JohnnyMorganz/Luau.tmLanguage](https://github.com/JohnnyMorganz/Luau.tmLanguage) - **M4:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **M4Sugar:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **MATLAB:** [mathworks/MATLAB-Language-grammar](https://github.com/mathworks/MATLAB-Language-grammar) From 4d4b3ebd978e1aa43cb965a4f16d2d4f541583a7 Mon Sep 17 00:00:00 2001 From: Demo Date: Thu, 4 Apr 2024 18:41:12 +0000 Subject: [PATCH 21/29] Patch: Retry resolving the conflict issue. --- vendor/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/vendor/README.md b/vendor/README.md index 2dd99fc849..81a7a77cea 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -319,6 +319,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **LookML:** [atom/language-yaml](https://github.com/atom/language-yaml) - **LoomScript:** [ambethia/Sublime-Loom](https://github.com/ambethia/Sublime-Loom) - **Lua:** [LuaLS/lua.tmbundle](https://github.com/LuaLS/lua.tmbundle) +- **Luau:** [JohnnyMorganz/Luau.tmLanguage](https://github.com/JohnnyMorganz/Luau.tmLanguage) - **M4:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **M4Sugar:** [Alhadis/language-etc](https://github.com/Alhadis/language-etc) - **MATLAB:** [mathworks/MATLAB-Language-grammar](https://github.com/mathworks/MATLAB-Language-grammar) From afcdb27292f20aedf050883891dd618834209941 Mon Sep 17 00:00:00 2001 From: Colin Seymour Date: Fri, 5 Apr 2024 08:13:39 +0100 Subject: [PATCH 22/29] Update vendor/licenses/git_submodule/Luau.tmLanguage.dep.yml --- vendor/licenses/git_submodule/Luau.tmLanguage.dep.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/licenses/git_submodule/Luau.tmLanguage.dep.yml b/vendor/licenses/git_submodule/Luau.tmLanguage.dep.yml index dbf914eb5f..5de924bb05 100644 --- a/vendor/licenses/git_submodule/Luau.tmLanguage.dep.yml +++ b/vendor/licenses/git_submodule/Luau.tmLanguage.dep.yml @@ -3,7 +3,7 @@ name: Luau.tmLanguage version: bc4170802aab182127f2b6b80b0e1164f50d78c1 type: git_submodule homepage: https://github.com/JohnnyMorganz/Luau.tmLanguage.git -license: other +license: mit licenses: - sources: LICENSE.md text: | From d0053792c2016da0f8ea560a2202476e7e6c97c4 Mon Sep 17 00:00:00 2001 From: Demo Date: Fri, 5 Apr 2024 16:06:09 +0000 Subject: [PATCH 23/29] Enhancement: Update old samples and their sources. --- samples/Luau/Promise.luau | 2243 +++++++++++++++++ samples/Luau/ReactRobloxHostConfig.luau | 1366 ++++++++++ samples/Luau/Replicator.luau | 546 ++++ samples/Luau/createProcessor.luau | 22 - samples/Luau/createSignal.luau | 37 - samples/Luau/equals.luau | 17 - samples/Luau/isEmpty.luau | 21 - samples/Luau/none.luau | 21 - samples/Luau/pathToRegexp.luau | 854 +++++++ samples/Luau/replaceMacros.luau | 311 --- samples/Luau/roblox.luau | 512 ++++ samples/Luau/timeStepper.luau | 36 - samples/Luau/toSet.luau | 22 - vendor/CodeMirror | 2 +- vendor/grammars/Handlebars | 2 +- vendor/grammars/MATLAB-Language-grammar | 2 +- vendor/grammars/Nasal.tmbundle | 2 +- vendor/grammars/NimLime | 2 +- vendor/grammars/Terraform.tmLanguage | 2 +- vendor/grammars/TypeScript-TmLanguage | 2 +- vendor/grammars/VscodeAdblockSyntax | 2 +- vendor/grammars/abap-cds-grammar | 2 +- vendor/grammars/abl-tmlanguage | 2 +- vendor/grammars/aidl-language | 2 +- vendor/grammars/astro | 2 +- vendor/grammars/atom-language-julia | 2 +- vendor/grammars/atom-language-perl6 | 2 +- vendor/grammars/atomic-dreams | 1 + vendor/grammars/bicep | 2 +- vendor/grammars/bikeshed | 2 +- vendor/grammars/cds-textmate-grammar | 2 +- vendor/grammars/csharp-tmLanguage | 2 +- vendor/grammars/d.tmbundle | 2 +- vendor/grammars/d2-vscode | 2 +- vendor/grammars/dart-syntax-highlight | 2 +- vendor/grammars/denizenscript-grammar | 2 +- vendor/grammars/earthfile-grammar | 2 +- vendor/grammars/elixir-tmbundle | 2 +- vendor/grammars/elvish | 2 +- vendor/grammars/gemini-vscode | 2 +- vendor/grammars/genero.tmbundle | 1 + vendor/grammars/godot-vscode-plugin | 2 +- vendor/grammars/graphiql | 2 +- vendor/grammars/ionide-fsgrammar | 2 +- vendor/grammars/language-csound | 2 +- vendor/grammars/language-etc | 2 +- vendor/grammars/language-kotlin | 2 +- vendor/grammars/language-subtitles | 2 +- vendor/grammars/language-viml | 2 +- vendor/grammars/latex.tmbundle | 2 +- vendor/grammars/lua.tmbundle | 2 +- vendor/grammars/markdown-tm-language | 2 +- vendor/grammars/mint-vscode | 2 +- vendor/grammars/nu-grammar | 2 +- vendor/grammars/qsharp-compiler | 2 +- vendor/grammars/rescript-vscode | 2 +- vendor/grammars/rust-syntax | 2 +- vendor/grammars/sas.tmbundle | 2 +- vendor/grammars/selinux-policy-languages | 2 +- vendor/grammars/smithy-vscode | 2 +- vendor/grammars/sourcepawn-vscode | 2 +- vendor/grammars/sublime-odin | 2 +- vendor/grammars/sublime-pony | 2 +- vendor/grammars/sublime-q | 2 +- vendor/grammars/sway-vscode-plugin | 2 +- vendor/grammars/swift.tmbundle | 1 + .../grammars/vscode-antlers-language-server | 2 +- vendor/grammars/vscode-brightscript-language | 2 +- vendor/grammars/vscode-cadence | 2 +- vendor/grammars/vscode-codeql | 2 +- vendor/grammars/vscode-fluent | 2 +- vendor/grammars/vscode-go | 2 +- vendor/grammars/vscode-hack | 2 +- vendor/grammars/vscode-ibmi-languages | 2 +- vendor/grammars/vscode-jest | 2 +- vendor/grammars/vscode-lean | 2 +- vendor/grammars/vscode-motoko | 2 +- vendor/grammars/vscode-move-syntax | 2 +- vendor/grammars/vscode-opa | 2 +- vendor/grammars/vscode-pddl | 2 +- vendor/grammars/vscode-prisma | 2 +- vendor/grammars/vscode-procfile | 2 +- vendor/grammars/vscode-scala-syntax | 2 +- vendor/grammars/vscode-slice | 2 +- vendor/grammars/vscode_cobol | 2 +- vendor/grammars/wgsl-analyzer | 2 +- 86 files changed, 5594 insertions(+), 557 deletions(-) create mode 100644 samples/Luau/Promise.luau create mode 100644 samples/Luau/ReactRobloxHostConfig.luau create mode 100644 samples/Luau/Replicator.luau delete mode 100644 samples/Luau/createProcessor.luau delete mode 100644 samples/Luau/createSignal.luau delete mode 100644 samples/Luau/equals.luau delete mode 100644 samples/Luau/isEmpty.luau delete mode 100644 samples/Luau/none.luau create mode 100644 samples/Luau/pathToRegexp.luau delete mode 100644 samples/Luau/replaceMacros.luau create mode 100644 samples/Luau/roblox.luau delete mode 100644 samples/Luau/timeStepper.luau delete mode 100644 samples/Luau/toSet.luau create mode 160000 vendor/grammars/atomic-dreams create mode 160000 vendor/grammars/genero.tmbundle create mode 160000 vendor/grammars/swift.tmbundle diff --git a/samples/Luau/Promise.luau b/samples/Luau/Promise.luau new file mode 100644 index 0000000000..fb7c265b43 --- /dev/null +++ b/samples/Luau/Promise.luau @@ -0,0 +1,2243 @@ +--// Coauthored by @ArxkDev (https://github.com/arxkdev) and @Evaera (https://github.com/evaera) +--// Fetched from (https://github.com/arxkdev/Leaderboard/blob/main/src/Leaderboard/Promise.luau) +--// Licensed under the MIT License (https://github.com/arxkdev/Leaderboard/blob/main/LICENSE) + +--!strict +--[[ + An implementation of Promises similar to Promise/A+. +]] + +export type Status = "Started" | "Resolved" | "Rejected" | "Cancelled" + +export type Promise = { + andThen: ( + self: Promise, + successHandler: (...any) -> ...any, + failureHandler: ((...any) -> ...any)? + ) -> Promise, + andThenCall: (self: Promise, callback: (TArgs...) -> ...any, TArgs...) -> any, + andThenReturn: (self: Promise, ...any) -> Promise, + + await: (self: Promise) -> (boolean, ...any), + awaitStatus: (self: Promise) -> (Status, ...any), + + cancel: (self: Promise) -> (), + catch: (self: Promise, failureHandler: (...any) -> ...any) -> Promise, + expect: (self: Promise) -> ...any, + + finally: (self: Promise, finallyHandler: (status: Status) -> ...any) -> Promise, + finallyCall: (self: Promise, callback: (TArgs...) -> ...any, TArgs...) -> Promise, + finallyReturn: (self: Promise, ...any) -> Promise, + + getStatus: (self: Promise) -> Status, + now: (self: Promise, rejectionValue: any?) -> Promise, + tap: (self: Promise, tapHandler: (...any) -> ...any) -> Promise, + timeout: (self: Promise, seconds: number, rejectionValue: any?) -> Promise, +} + +export type TypedPromise = { + andThen: ( + self: TypedPromise, + successHandler: (result: T) -> ...any, + failureHandler: ((...any) -> ...any)? + ) -> TypedPromise, + andThenReturn: (self: TypedPromise, ...any) -> TypedPromise, + + await: (self: TypedPromise) -> (boolean, ...T), + awaitStatus: (self: TypedPromise) -> (Status, ...T), + + cancel: (self: TypedPromise) -> (), + catch: (self: TypedPromise, failureHandler: (...any) -> ...any) -> TypedPromise, + expect: (self: TypedPromise) -> ...any, + finally: (self: TypedPromise, finallyHandler: (status: Status) -> ...any) -> TypedPromise, + + finallyReturn: (self: TypedPromise, ...any) -> TypedPromise, + finallyCall: (self: TypedPromise, callback: (TArgs...) -> ...any, TArgs...) -> TypedPromise, + + getStatus: (self: TypedPromise) -> Status, + now: (self: TypedPromise, rejectionValue: any?) -> TypedPromise, + tap: (self: TypedPromise, tapHandler: (...any) -> ...any) -> TypedPromise, + timeout: (self: TypedPromise, seconds: number, rejectionValue: any?) -> TypedPromise, +} + +type Signal = { + Connect: (self: Signal, callback: (T...) -> ...any) -> SignalConnection, +} + +type SignalConnection = { + Disconnect: (self: SignalConnection) -> ...any, + [any]: any, +} + +local ERROR_NON_PROMISE_IN_LIST = "Non-promise value passed into %s at index %s" +local ERROR_NON_LIST = "Please pass a list of promises to %s" +local ERROR_NON_FUNCTION = "Please pass a handler function to %s!" +local MODE_KEY_METATABLE = { __mode = "k" } + +local function isCallable(value) + if type(value) == "function" then + return true + end + + if type(value) == "table" then + local metatable = getmetatable(value) + if metatable and type(rawget(metatable, "__call")) == "function" then + return true + end + end + + return false +end + +--[[ + Creates an enum dictionary with some metamethods to prevent common mistakes. +]] +local function makeEnum(enumName, members) + local enum = {} + + for _, memberName in ipairs(members) do + enum[memberName] = memberName + end + + return setmetatable(enum, { + __index = function(_, k) + error(string.format("%s is not in %s!", k, enumName), 2) + end, + __newindex = function() + error(string.format("Creating new members in %s is not allowed!", enumName), 2) + end, + }) +end + +--[=[ + An object to represent runtime errors that occur during execution. + Promises that experience an error like this will be rejected with + an instance of this object. + + @class Error +]=] +local Error +do + Error = { + Kind = makeEnum("Promise.Error.Kind", { + "ExecutionError", + "AlreadyCancelled", + "NotResolvedInTime", + "TimedOut", + }), + } + Error.__index = Error + + function Error.new(options, parent) + options = options or {} + return setmetatable({ + error = tostring(options.error) or "[This error has no error text.]", + trace = options.trace, + context = options.context, + kind = options.kind, + parent = parent, + createdTick = os.clock(), + createdTrace = debug.traceback(), + }, Error) + end + + function Error.is(anything) + if type(anything) == "table" then + local metatable = getmetatable(anything) + + if type(metatable) == "table" then + return rawget(anything, "error") ~= nil and type(rawget(metatable, "extend")) == "function" + end + end + + return false + end + + function Error.isKind(anything, kind) + assert(kind ~= nil, "Argument #2 to Promise.Error.isKind must not be nil") + + return Error.is(anything) and anything.kind == kind + end + + function Error:extend(options) + options = options or {} + + options.kind = options.kind or self.kind + + return Error.new(options, self) + end + + function Error:getErrorChain() + local runtimeErrors = { self } + + while runtimeErrors[#runtimeErrors].parent do + table.insert(runtimeErrors, runtimeErrors[#runtimeErrors].parent) + end + + return runtimeErrors + end + + function Error:__tostring() + local errorStrings = { + string.format("-- Promise.Error(%s) --", self.kind or "?"), + } + + for _, runtimeError in ipairs(self:getErrorChain()) do + table.insert( + errorStrings, + table.concat({ + runtimeError.trace or runtimeError.error, + runtimeError.context, + }, "\n") + ) + end + + return table.concat(errorStrings, "\n") + end +end + +--[[ + Packs a number of arguments into a table and returns its length. + + Used to cajole varargs without dropping sparse values. +]] +local function pack(...) + return select("#", ...), { ... } +end + +--[[ + Returns first value (success), and packs all following values. +]] +local function packResult(success, ...) + return success, select("#", ...), { ... } +end + +local function makeErrorHandler(traceback) + assert(traceback ~= nil, "traceback is nil") + + return function(err) + -- If the error object is already a table, forward it directly. + -- Should we extend the error here and add our own trace? + + if type(err) == "table" then + return err + end + + return Error.new({ + error = err, + kind = Error.Kind.ExecutionError, + trace = debug.traceback(tostring(err), 2), + context = "Promise created at:\n\n" .. traceback, + }) + end +end + +--[[ + Calls a Promise executor with error handling. +]] +local function runExecutor(traceback, callback, ...) + return packResult(xpcall(callback, makeErrorHandler(traceback), ...)) +end + +--[[ + Creates a function that invokes a callback with correct error handling and + resolution mechanisms. +]] +local function createAdvancer(traceback, callback, resolve, reject) + return function(...) + local ok, resultLength, result = runExecutor(traceback, callback, ...) + + if ok then + resolve(unpack(result, 1, resultLength)) + else + reject(result[1]) + end + end +end + +local function isEmpty(t) + return next(t) == nil +end + +--[=[ + An enum value used to represent the Promise's status. + @interface Status + @tag enum + @within Promise + .Started "Started" -- The Promise is executing, and not settled yet. + .Resolved "Resolved" -- The Promise finished successfully. + .Rejected "Rejected" -- The Promise was rejected. + .Cancelled "Cancelled" -- The Promise was cancelled before it finished. +]=] +--[=[ + @prop Status Status + @within Promise + @readonly + @tag enums + A table containing all members of the `Status` enum, e.g., `Promise.Status.Resolved`. +]=] +--[=[ + A Promise is an object that represents a value that will exist in the future, but doesn't right now. + Promises allow you to then attach callbacks that can run once the value becomes available (known as *resolving*), + or if an error has occurred (known as *rejecting*). + + @class Promise + @__index prototype +]=] +local Promise = { + Error = Error, + Status = makeEnum("Promise.Status", { "Started", "Resolved", "Rejected", "Cancelled" }), + _getTime = os.clock, + _timeEvent = game:GetService("RunService").Heartbeat, + _unhandledRejectionCallbacks = {}, +} +Promise.prototype = {} +Promise.__index = Promise.prototype + +function Promise._new(traceback, callback, parent) + if parent ~= nil and not Promise.is(parent) then + error("Argument #2 to Promise.new must be a promise or nil", 2) + end + + local self = { + -- The executor thread. + _thread = nil, + + -- Used to locate where a promise was created + _source = traceback, + + _status = Promise.Status.Started, + + -- A table containing a list of all results, whether success or failure. + -- Only valid if _status is set to something besides Started + _values = nil, + + -- Lua doesn't like sparse arrays very much, so we explicitly store the + -- length of _values to handle middle nils. + _valuesLength = -1, + + -- Tracks if this Promise has no error observers.. + _unhandledRejection = true, + + -- Queues representing functions we should invoke when we update! + _queuedResolve = {}, + _queuedReject = {}, + _queuedFinally = {}, + + -- The function to run when/if this promise is cancelled. + _cancellationHook = nil, + + -- The "parent" of this promise in a promise chain. Required for + -- cancellation propagation upstream. + _parent = parent, + + -- Consumers are Promises that have chained onto this one. + -- We track them for cancellation propagation downstream. + _consumers = setmetatable({}, MODE_KEY_METATABLE), + } + + if parent and parent._status == Promise.Status.Started then + parent._consumers[self] = true + end + + setmetatable(self, Promise) + + local function resolve(...) + self:_resolve(...) + end + + local function reject(...) + self:_reject(...) + end + + local function onCancel(cancellationHook) + if cancellationHook then + if self._status == Promise.Status.Cancelled then + cancellationHook() + else + self._cancellationHook = cancellationHook + end + end + + return self._status == Promise.Status.Cancelled + end + + self._thread = coroutine.create(function() + local ok, _, result = runExecutor(self._source, callback, resolve, reject, onCancel) + + if not ok then + reject(result[1]) + end + end) + + task.spawn(self._thread) + + return self +end + +--[=[ + Construct a new Promise that will be resolved or rejected with the given callbacks. + + If you `resolve` with a Promise, it will be chained onto. + + You can safely yield within the executor function and it will not block the creating thread. + + ```lua + local myFunction() + return Promise.new(function(resolve, reject, onCancel) + wait(1) + resolve("Hello world!") + end) + end + + myFunction():andThen(print) + ``` + + You do not need to use `pcall` within a Promise. Errors that occur during execution will be caught and turned into a rejection automatically. If `error()` is called with a table, that table will be the rejection value. Otherwise, string errors will be converted into `Promise.Error(Promise.Error.Kind.ExecutionError)` objects for tracking debug information. + + You may register an optional cancellation hook by using the `onCancel` argument: + + * This should be used to abort any ongoing operations leading up to the promise being settled. + * Call the `onCancel` function with a function callback as its only argument to set a hook which will in turn be called when/if the promise is cancelled. + * `onCancel` returns `true` if the Promise was already cancelled when you called `onCancel`. + * Calling `onCancel` with no argument will not override a previously set cancellation hook, but it will still return `true` if the Promise is currently cancelled. + * You can set the cancellation hook at any time before resolving. + * When a promise is cancelled, calls to `resolve` or `reject` will be ignored, regardless of if you set a cancellation hook or not. + + :::caution + If the Promise is cancelled, the `executor` thread is closed with `coroutine.close` after the cancellation hook is called. + + You must perform any cleanup code in the cancellation hook: any time your executor yields, it **may never resume**. + ::: + + @param executor (resolve: (...: any) -> (), reject: (...: any) -> (), onCancel: (abortHandler?: () -> ()) -> boolean) -> () + @return Promise +]=] +function Promise.new(executor) + return Promise._new(debug.traceback(nil, 2), executor) +end + +function Promise:__tostring() + return string.format("Promise(%s)", self._status) +end + +--[=[ + The same as [Promise.new](/api/Promise#new), except execution begins after the next `Heartbeat` event. + + This is a spiritual replacement for `spawn`, but it does not suffer from the same [issues](https://eryn.io/gist/3db84579866c099cdd5bb2ff37947cec) as `spawn`. + + ```lua + local function waitForChild(instance, childName, timeout) + return Promise.defer(function(resolve, reject) + local child = instance:WaitForChild(childName, timeout) + + ;(child and resolve or reject)(child) + end) + end + ``` + + @param executor (resolve: (...: any) -> (), reject: (...: any) -> (), onCancel: (abortHandler?: () -> ()) -> boolean) -> () + @return Promise +]=] +function Promise.defer(executor) + local traceback = debug.traceback(nil, 2) + local promise + promise = Promise._new(traceback, function(resolve, reject, onCancel) + local connection + connection = Promise._timeEvent:Connect(function() + connection:Disconnect() + local ok, _, result = runExecutor(traceback, executor, resolve, reject, onCancel) + + if not ok then + reject(result[1]) + end + end) + end) + + return promise +end + +-- Backwards compatibility +Promise.async = Promise.defer + +--[=[ + Creates an immediately resolved Promise with the given value. + + ```lua + -- Example using Promise.resolve to deliver cached values: + function getSomething(name) + if cache[name] then + return Promise.resolve(cache[name]) + else + return Promise.new(function(resolve, reject) + local thing = getTheThing() + cache[name] = thing + + resolve(thing) + end) + end + end + ``` + + @param ... any + @return Promise<...any> +]=] +function Promise.resolve(...) + local length, values = pack(...) + return Promise._new(debug.traceback(nil, 2), function(resolve) + resolve(unpack(values, 1, length)) + end) +end + +--[=[ + Creates an immediately rejected Promise with the given value. + + :::caution + Something needs to consume this rejection (i.e. `:catch()` it), otherwise it will emit an unhandled Promise rejection warning on the next frame. Thus, you should not create and store rejected Promises for later use. Only create them on-demand as needed. + ::: + + @param ... any + @return Promise<...any> +]=] +function Promise.reject(...) + local length, values = pack(...) + return Promise._new(debug.traceback(nil, 2), function(_, reject) + reject(unpack(values, 1, length)) + end) +end + +--[[ + Runs a non-promise-returning function as a Promise with the + given arguments. +]] +function Promise._try(traceback, callback, ...) + local valuesLength, values = pack(...) + + return Promise._new(traceback, function(resolve) + resolve(callback(unpack(values, 1, valuesLength))) + end) +end + +--[=[ + Begins a Promise chain, calling a function and returning a Promise resolving with its return value. If the function errors, the returned Promise will be rejected with the error. You can safely yield within the Promise.try callback. + + :::info + `Promise.try` is similar to [Promise.promisify](#promisify), except the callback is invoked immediately instead of returning a new function. + ::: + + ```lua + Promise.try(function() + return math.random(1, 2) == 1 and "ok" or error("Oh an error!") + end) + :andThen(function(text) + print(text) + end) + :catch(function(err) + warn("Something went wrong") + end) + ``` + + @param callback (...: T...) -> ...any + @param ... T... -- Additional arguments passed to `callback` + @return Promise +]=] +function Promise.try(callback, ...) + return Promise._try(debug.traceback(nil, 2), callback, ...) +end + +--[[ + Returns a new promise that: + * is resolved when all input promises resolve + * is rejected if ANY input promises reject +]] +function Promise._all(traceback, promises, amount) + if type(promises) ~= "table" then + error(string.format(ERROR_NON_LIST, "Promise.all"), 3) + end + + -- We need to check that each value is a promise here so that we can produce + -- a proper error rather than a rejected promise with our error. + for i, promise in pairs(promises) do + if not Promise.is(promise) then + error(string.format(ERROR_NON_PROMISE_IN_LIST, "Promise.all", tostring(i)), 3) + end + end + + -- If there are no values then return an already resolved promise. + if #promises == 0 or amount == 0 then + return Promise.resolve({}) + end + + return Promise._new(traceback, function(resolve, reject, onCancel) + -- An array to contain our resolved values from the given promises. + local resolvedValues = {} + local newPromises = {} + + -- Keep a count of resolved promises because just checking the resolved + -- values length wouldn't account for promises that resolve with nil. + local resolvedCount = 0 + local rejectedCount = 0 + local done = false + + local function cancel() + for _, promise in ipairs(newPromises) do + promise:cancel() + end + end + + -- Called when a single value is resolved and resolves if all are done. + local function resolveOne(i, ...) + if done then + return + end + + resolvedCount = resolvedCount + 1 + + if amount == nil then + resolvedValues[i] = ... + else + resolvedValues[resolvedCount] = ... + end + + if resolvedCount >= (amount or #promises) then + done = true + resolve(resolvedValues) + cancel() + end + end + + onCancel(cancel) + + -- We can assume the values inside `promises` are all promises since we + -- checked above. + for i, promise in ipairs(promises) do + newPromises[i] = promise:andThen(function(...) + resolveOne(i, ...) + end, function(...) + rejectedCount = rejectedCount + 1 + + if amount == nil or #promises - rejectedCount < amount then + cancel() + done = true + + reject(...) + end + end) + end + + if done then + cancel() + end + end) +end + +--[=[ + Accepts an array of Promises and returns a new promise that: + * is resolved after all input promises resolve. + * is rejected if *any* input promises reject. + + :::info + Only the first return value from each promise will be present in the resulting array. + ::: + + After any input Promise rejects, all other input Promises that are still pending will be cancelled if they have no other consumers. + + ```lua + local promises = { + returnsAPromise("example 1"), + returnsAPromise("example 2"), + returnsAPromise("example 3"), + } + + return Promise.all(promises) + ``` + + @param promises {Promise} + @return Promise<{T}> +]=] +function Promise.all(promises) + return Promise._all(debug.traceback(nil, 2), promises) +end + +--[=[ + Folds an array of values or promises into a single value. The array is traversed sequentially. + + The reducer function can return a promise or value directly. Each iteration receives the resolved value from the previous, and the first receives your defined initial value. + + The folding will stop at the first rejection encountered. + ```lua + local basket = {"blueberry", "melon", "pear", "melon"} + Promise.fold(basket, function(cost, fruit) + if fruit == "blueberry" then + return cost -- blueberries are free! + else + -- call a function that returns a promise with the fruit price + return fetchPrice(fruit):andThen(function(fruitCost) + return cost + fruitCost + end) + end + end, 0) + ``` + + @since v3.1.0 + @param list {T | Promise} + @param reducer (accumulator: U, value: T, index: number) -> U | Promise + @param initialValue U +]=] +function Promise.fold(list, reducer, initialValue) + assert(type(list) == "table", "Bad argument #1 to Promise.fold: must be a table") + assert(isCallable(reducer), "Bad argument #2 to Promise.fold: must be a function") + + local accumulator = Promise.resolve(initialValue) + return Promise.each(list, function(resolvedElement, i) + accumulator = accumulator:andThen(function(previousValueResolved) + return reducer(previousValueResolved, resolvedElement, i) + end) + end):andThen(function() + return accumulator + end) +end + +--[=[ + Accepts an array of Promises and returns a Promise that is resolved as soon as `count` Promises are resolved from the input array. The resolved array values are in the order that the Promises resolved in. When this Promise resolves, all other pending Promises are cancelled if they have no other consumers. + + `count` 0 results in an empty array. The resultant array will never have more than `count` elements. + + ```lua + local promises = { + returnsAPromise("example 1"), + returnsAPromise("example 2"), + returnsAPromise("example 3"), + } + + return Promise.some(promises, 2) -- Only resolves with first 2 promises to resolve + ``` + + @param promises {Promise} + @param count number + @return Promise<{T}> +]=] +function Promise.some(promises, count) + assert(type(count) == "number", "Bad argument #2 to Promise.some: must be a number") + + return Promise._all(debug.traceback(nil, 2), promises, count) +end + +--[=[ + Accepts an array of Promises and returns a Promise that is resolved as soon as *any* of the input Promises resolves. It will reject only if *all* input Promises reject. As soon as one Promises resolves, all other pending Promises are cancelled if they have no other consumers. + + Resolves directly with the value of the first resolved Promise. This is essentially [[Promise.some]] with `1` count, except the Promise resolves with the value directly instead of an array with one element. + + ```lua + local promises = { + returnsAPromise("example 1"), + returnsAPromise("example 2"), + returnsAPromise("example 3"), + } + + return Promise.any(promises) -- Resolves with first value to resolve (only rejects if all 3 rejected) + ``` + + @param promises {Promise} + @return Promise +]=] +function Promise.any(promises) + return Promise._all(debug.traceback(nil, 2), promises, 1):andThen(function(values) + return values[1] + end) +end + +--[=[ + Accepts an array of Promises and returns a new Promise that resolves with an array of in-place Statuses when all input Promises have settled. This is equivalent to mapping `promise:finally` over the array of Promises. + + ```lua + local promises = { + returnsAPromise("example 1"), + returnsAPromise("example 2"), + returnsAPromise("example 3"), + } + + return Promise.allSettled(promises) + ``` + + @param promises {Promise} + @return Promise<{Status}> +]=] +function Promise.allSettled(promises) + if type(promises) ~= "table" then + error(string.format(ERROR_NON_LIST, "Promise.allSettled"), 2) + end + + -- We need to check that each value is a promise here so that we can produce + -- a proper error rather than a rejected promise with our error. + for i, promise in pairs(promises) do + if not Promise.is(promise) then + error(string.format(ERROR_NON_PROMISE_IN_LIST, "Promise.allSettled", tostring(i)), 2) + end + end + + -- If there are no values then return an already resolved promise. + if #promises == 0 then + return Promise.resolve({}) + end + + return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel) + -- An array to contain our resolved values from the given promises. + local fates = {} + local newPromises = {} + + -- Keep a count of resolved promises because just checking the resolved + -- values length wouldn't account for promises that resolve with nil. + local finishedCount = 0 + + -- Called when a single value is resolved and resolves if all are done. + local function resolveOne(i, ...) + finishedCount = finishedCount + 1 + + fates[i] = ... + + if finishedCount >= #promises then + resolve(fates) + end + end + + onCancel(function() + for _, promise in ipairs(newPromises) do + promise:cancel() + end + end) + + -- We can assume the values inside `promises` are all promises since we + -- checked above. + for i, promise in ipairs(promises) do + newPromises[i] = promise:finally(function(...) + resolveOne(i, ...) + end) + end + end) +end + +--[=[ + Accepts an array of Promises and returns a new promise that is resolved or rejected as soon as any Promise in the array resolves or rejects. + + :::warning + If the first Promise to settle from the array settles with a rejection, the resulting Promise from `race` will reject. + + If you instead want to tolerate rejections, and only care about at least one Promise resolving, you should use [Promise.any](#any) or [Promise.some](#some) instead. + ::: + + All other Promises that don't win the race will be cancelled if they have no other consumers. + + ```lua + local promises = { + returnsAPromise("example 1"), + returnsAPromise("example 2"), + returnsAPromise("example 3"), + } + + return Promise.race(promises) -- Only returns 1st value to resolve or reject + ``` + + @param promises {Promise} + @return Promise +]=] +function Promise.race(promises) + assert(type(promises) == "table", string.format(ERROR_NON_LIST, "Promise.race")) + + for i, promise in pairs(promises) do + assert(Promise.is(promise), string.format(ERROR_NON_PROMISE_IN_LIST, "Promise.race", tostring(i))) + end + + return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel) + local newPromises = {} + local finished = false + + local function cancel() + for _, promise in ipairs(newPromises) do + promise:cancel() + end + end + + local function finalize(callback) + return function(...) + cancel() + finished = true + return callback(...) + end + end + + if onCancel(finalize(reject)) then + return + end + + for i, promise in ipairs(promises) do + newPromises[i] = promise:andThen(finalize(resolve), finalize(reject)) + end + + if finished then + cancel() + end + end) +end + +--[=[ + Iterates serially over the given an array of values, calling the predicate callback on each value before continuing. + + If the predicate returns a Promise, we wait for that Promise to resolve before moving on to the next item + in the array. + + :::info + `Promise.each` is similar to `Promise.all`, except the Promises are ran in order instead of all at once. + + But because Promises are eager, by the time they are created, they're already running. Thus, we need a way to defer creation of each Promise until a later time. + + The predicate function exists as a way for us to operate on our data instead of creating a new closure for each Promise. If you would prefer, you can pass in an array of functions, and in the predicate, call the function and return its return value. + ::: + + ```lua + Promise.each({ + "foo", + "bar", + "baz", + "qux" + }, function(value, index) + return Promise.delay(1):andThen(function() + print(("%d) Got %s!"):format(index, value)) + end) + end) + + --[[ + (1 second passes) + > 1) Got foo! + (1 second passes) + > 2) Got bar! + (1 second passes) + > 3) Got baz! + (1 second passes) + > 4) Got qux! + ]] + ``` + + If the Promise a predicate returns rejects, the Promise from `Promise.each` is also rejected with the same value. + + If the array of values contains a Promise, when we get to that point in the list, we wait for the Promise to resolve before calling the predicate with the value. + + If a Promise in the array of values is already Rejected when `Promise.each` is called, `Promise.each` rejects with that value immediately (the predicate callback will never be called even once). If a Promise in the list is already Cancelled when `Promise.each` is called, `Promise.each` rejects with `Promise.Error(Promise.Error.Kind.AlreadyCancelled`). If a Promise in the array of values is Started at first, but later rejects, `Promise.each` will reject with that value and iteration will not continue once iteration encounters that value. + + Returns a Promise containing an array of the returned/resolved values from the predicate for each item in the array of values. + + If this Promise returned from `Promise.each` rejects or is cancelled for any reason, the following are true: + - Iteration will not continue. + - Any Promises within the array of values will now be cancelled if they have no other consumers. + - The Promise returned from the currently active predicate will be cancelled if it hasn't resolved yet. + + @since 3.0.0 + @param list {T | Promise} + @param predicate (value: T, index: number) -> U | Promise + @return Promise<{U}> +]=] +function Promise.each(list, predicate) + assert(type(list) == "table", string.format(ERROR_NON_LIST, "Promise.each")) + assert(isCallable(predicate), string.format(ERROR_NON_FUNCTION, "Promise.each")) + + return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel) + local results = {} + local promisesToCancel = {} + + local cancelled = false + + local function cancel() + for _, promiseToCancel in ipairs(promisesToCancel) do + promiseToCancel:cancel() + end + end + + onCancel(function() + cancelled = true + + cancel() + end) + + -- We need to preprocess the list of values and look for Promises. + -- If we find some, we must register our andThen calls now, so that those Promises have a consumer + -- from us registered. If we don't do this, those Promises might get cancelled by something else + -- before we get to them in the series because it's not possible to tell that we plan to use it + -- unless we indicate it here. + + local preprocessedList = {} + + for index, value in ipairs(list) do + if Promise.is(value) then + if value:getStatus() == Promise.Status.Cancelled then + cancel() + return reject(Error.new({ + error = "Promise is cancelled", + kind = Error.Kind.AlreadyCancelled, + context = string.format( + "The Promise that was part of the array at index %d passed into Promise.each was already cancelled when Promise.each began.\n\nThat Promise was created at:\n\n%s", + index, + value._source + ), + })) + elseif value:getStatus() == Promise.Status.Rejected then + cancel() + return reject(select(2, value:await())) + end + + -- Chain a new Promise from this one so we only cancel ours + local ourPromise = value:andThen(function(...) + return ... + end) + + table.insert(promisesToCancel, ourPromise) + preprocessedList[index] = ourPromise + else + preprocessedList[index] = value + end + end + + for index, value in ipairs(preprocessedList) do + if Promise.is(value) then + local success + success, value = value:await() + + if not success then + cancel() + return reject(value) + end + end + + if cancelled then + return + end + + local predicatePromise = Promise.resolve(predicate(value, index)) + + table.insert(promisesToCancel, predicatePromise) + + local success, result = predicatePromise:await() + + if not success then + cancel() + return reject(result) + end + + results[index] = result + end + + resolve(results) + end) +end + +--[=[ + Checks whether the given object is a Promise via duck typing. This only checks if the object is a table and has an `andThen` method. + + @param object any + @return boolean -- `true` if the given `object` is a Promise. +]=] +function Promise.is(object) + if type(object) ~= "table" then + return false + end + + local objectMetatable = getmetatable(object) + + if objectMetatable == Promise then + -- The Promise came from this library. + return true + elseif objectMetatable == nil then + -- No metatable, but we should still chain onto tables with andThen methods + return isCallable(object.andThen) + elseif + type(objectMetatable) == "table" + and type(rawget(objectMetatable, "__index")) == "table" + and isCallable(rawget(rawget(objectMetatable, "__index"), "andThen")) + then + -- Maybe this came from a different or older Promise library. + return true + end + + return false +end + +--[=[ + Wraps a function that yields into one that returns a Promise. + + Any errors that occur while executing the function will be turned into rejections. + + :::info + `Promise.promisify` is similar to [Promise.try](#try), except the callback is returned as a callable function instead of being invoked immediately. + ::: + + ```lua + local sleep = Promise.promisify(wait) + + sleep(1):andThen(print) + ``` + + ```lua + local isPlayerInGroup = Promise.promisify(function(player, groupId) + return player:IsInGroup(groupId) + end) + ``` + + @param callback (...: any) -> ...any + @return (...: any) -> Promise +]=] +function Promise.promisify(callback) + return function(...) + return Promise._try(debug.traceback(nil, 2), callback, ...) + end +end + +--[=[ + Returns a Promise that resolves after `seconds` seconds have passed. The Promise resolves with the actual amount of time that was waited. + + This function is **not** a wrapper around `wait`. `Promise.delay` uses a custom scheduler which provides more accurate timing. As an optimization, cancelling this Promise instantly removes the task from the scheduler. + + :::warning + Passing `NaN`, infinity, or a number less than 1/60 is equivalent to passing 1/60. + ::: + + ```lua + Promise.delay(5):andThenCall(print, "This prints after 5 seconds") + ``` + + @function delay + @within Promise + @param seconds number + @return Promise +]=] +do + -- uses a sorted doubly linked list (queue) to achieve O(1) remove operations and O(n) for insert + + -- the initial node in the linked list + local first + local connection + + function Promise.delay(seconds) + assert(type(seconds) == "number", "Bad argument #1 to Promise.delay, must be a number.") + -- If seconds is -INF, INF, NaN, or less than 1 / 60, assume seconds is 1 / 60. + -- This mirrors the behavior of wait() + if not (seconds >= 1 / 60) or seconds == math.huge then + seconds = 1 / 60 + end + + return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel) + local startTime = Promise._getTime() + local endTime = startTime + seconds + + local node = { + resolve = resolve, + startTime = startTime, + endTime = endTime, + } + + if connection == nil then -- first is nil when connection is nil + first = node + connection = Promise._timeEvent:Connect(function() + local threadStart = Promise._getTime() + + while first ~= nil and first.endTime < threadStart do + local current = first + first = current.next + + if first == nil then + connection:Disconnect() + connection = nil + else + first.previous = nil + end + + current.resolve(Promise._getTime() - current.startTime) + end + end) + else -- first is non-nil + if first.endTime < endTime then -- if `node` should be placed after `first` + -- we will insert `node` between `current` and `next` + -- (i.e. after `current` if `next` is nil) + local current = first + local next = current.next + + while next ~= nil and next.endTime < endTime do + current = next + next = current.next + end + + -- `current` must be non-nil, but `next` could be `nil` (i.e. last item in list) + current.next = node + node.previous = current + + if next ~= nil then + node.next = next + next.previous = node + end + else + -- set `node` to `first` + node.next = first + first.previous = node + first = node + end + end + + onCancel(function() + -- remove node from queue + local next = node.next + + if first == node then + if next == nil then -- if `node` is the first and last + connection:Disconnect() + connection = nil + else -- if `node` is `first` and not the last + next.previous = nil + end + first = next + else + local previous = node.previous + -- since `node` is not `first`, then we know `previous` is non-nil + previous.next = next + + if next ~= nil then + next.previous = previous + end + end + end) + end) + end +end + +--[=[ + Returns a new Promise that resolves if the chained Promise resolves within `seconds` seconds, or rejects if execution time exceeds `seconds`. The chained Promise will be cancelled if the timeout is reached. + + Rejects with `rejectionValue` if it is non-nil. If a `rejectionValue` is not given, it will reject with a `Promise.Error(Promise.Error.Kind.TimedOut)`. This can be checked with [[Error.isKind]]. + + ```lua + getSomething():timeout(5):andThen(function(something) + -- got something and it only took at max 5 seconds + end):catch(function(e) + -- Either getting something failed or the time was exceeded. + + if Promise.Error.isKind(e, Promise.Error.Kind.TimedOut) then + warn("Operation timed out!") + else + warn("Operation encountered an error!") + end + end) + ``` + + Sugar for: + + ```lua + Promise.race({ + Promise.delay(seconds):andThen(function() + return Promise.reject( + rejectionValue == nil + and Promise.Error.new({ kind = Promise.Error.Kind.TimedOut }) + or rejectionValue + ) + end), + promise + }) + ``` + + @param seconds number + @param rejectionValue? any -- The value to reject with if the timeout is reached + @return Promise +]=] +function Promise.prototype:timeout(seconds, rejectionValue) + local traceback = debug.traceback(nil, 2) + + return Promise.race({ + Promise.delay(seconds):andThen(function() + return Promise.reject(rejectionValue == nil and Error.new({ + kind = Error.Kind.TimedOut, + error = "Timed out", + context = string.format( + "Timeout of %d seconds exceeded.\n:timeout() called at:\n\n%s", + seconds, + traceback + ), + }) or rejectionValue) + end), + self, + }) +end + +--[=[ + Returns the current Promise status. + + @return Status +]=] +function Promise.prototype:getStatus() + return self._status +end + +--[[ + Creates a new promise that receives the result of this promise. + + The given callbacks are invoked depending on that result. +]] +function Promise.prototype:_andThen(traceback, successHandler, failureHandler) + self._unhandledRejection = false + + -- If we are already cancelled, we return a cancelled Promise + if self._status == Promise.Status.Cancelled then + local promise = Promise.new(function() end) + promise:cancel() + + return promise + end + + -- Create a new promise to follow this part of the chain + return Promise._new(traceback, function(resolve, reject, onCancel) + -- Our default callbacks just pass values onto the next promise. + -- This lets success and failure cascade correctly! + + local successCallback = resolve + if successHandler then + successCallback = createAdvancer(traceback, successHandler, resolve, reject) + end + + local failureCallback = reject + if failureHandler then + failureCallback = createAdvancer(traceback, failureHandler, resolve, reject) + end + + if self._status == Promise.Status.Started then + -- If we haven't resolved yet, put ourselves into the queue + table.insert(self._queuedResolve, successCallback) + table.insert(self._queuedReject, failureCallback) + + onCancel(function() + -- These are guaranteed to exist because the cancellation handler is guaranteed to only + -- be called at most once + if self._status == Promise.Status.Started then + table.remove(self._queuedResolve, table.find(self._queuedResolve, successCallback)) + table.remove(self._queuedReject, table.find(self._queuedReject, failureCallback)) + end + end) + elseif self._status == Promise.Status.Resolved then + -- This promise has already resolved! Trigger success immediately. + successCallback(unpack(self._values, 1, self._valuesLength)) + elseif self._status == Promise.Status.Rejected then + -- This promise died a terrible death! Trigger failure immediately. + failureCallback(unpack(self._values, 1, self._valuesLength)) + end + end, self) +end + +--[=[ + Chains onto an existing Promise and returns a new Promise. + + :::warning + Within the failure handler, you should never assume that the rejection value is a string. Some rejections within the Promise library are represented by [[Error]] objects. If you want to treat it as a string for debugging, you should call `tostring` on it first. + ::: + + You can return a Promise from the success or failure handler and it will be chained onto. + + Calling `andThen` on a cancelled Promise returns a cancelled Promise. + + :::tip + If the Promise returned by `andThen` is cancelled, `successHandler` and `failureHandler` will not run. + + To run code no matter what, use [Promise:finally]. + ::: + + @param successHandler (...: any) -> ...any + @param failureHandler? (...: any) -> ...any + @return Promise<...any> +]=] +function Promise.prototype:andThen(successHandler, failureHandler) + assert(successHandler == nil or isCallable(successHandler), string.format(ERROR_NON_FUNCTION, "Promise:andThen")) + assert(failureHandler == nil or isCallable(failureHandler), string.format(ERROR_NON_FUNCTION, "Promise:andThen")) + + return self:_andThen(debug.traceback(nil, 2), successHandler, failureHandler) +end + +--[=[ + Shorthand for `Promise:andThen(nil, failureHandler)`. + + Returns a Promise that resolves if the `failureHandler` worked without encountering an additional error. + + :::warning + Within the failure handler, you should never assume that the rejection value is a string. Some rejections within the Promise library are represented by [[Error]] objects. If you want to treat it as a string for debugging, you should call `tostring` on it first. + ::: + + Calling `catch` on a cancelled Promise returns a cancelled Promise. + + :::tip + If the Promise returned by `catch` is cancelled, `failureHandler` will not run. + + To run code no matter what, use [Promise:finally]. + ::: + + @param failureHandler (...: any) -> ...any + @return Promise<...any> +]=] +function Promise.prototype:catch(failureHandler) + assert(failureHandler == nil or isCallable(failureHandler), string.format(ERROR_NON_FUNCTION, "Promise:catch")) + return self:_andThen(debug.traceback(nil, 2), nil, failureHandler) +end + +--[=[ + Similar to [Promise.andThen](#andThen), except the return value is the same as the value passed to the handler. In other words, you can insert a `:tap` into a Promise chain without affecting the value that downstream Promises receive. + + ```lua + getTheValue() + :tap(print) + :andThen(function(theValue) + print("Got", theValue, "even though print returns nil!") + end) + ``` + + If you return a Promise from the tap handler callback, its value will be discarded but `tap` will still wait until it resolves before passing the original value through. + + @param tapHandler (...: any) -> ...any + @return Promise<...any> +]=] +function Promise.prototype:tap(tapHandler) + assert(isCallable(tapHandler), string.format(ERROR_NON_FUNCTION, "Promise:tap")) + return self:_andThen(debug.traceback(nil, 2), function(...) + local callbackReturn = tapHandler(...) + + if Promise.is(callbackReturn) then + local length, values = pack(...) + return callbackReturn:andThen(function() + return unpack(values, 1, length) + end) + end + + return ... + end) +end + +--[=[ + Attaches an `andThen` handler to this Promise that calls the given callback with the predefined arguments. The resolved value is discarded. + + ```lua + promise:andThenCall(someFunction, "some", "arguments") + ``` + + This is sugar for + + ```lua + promise:andThen(function() + return someFunction("some", "arguments") + end) + ``` + + @param callback (...: any) -> any + @param ...? any -- Additional arguments which will be passed to `callback` + @return Promise +]=] +function Promise.prototype:andThenCall(callback, ...) + assert(isCallable(callback), string.format(ERROR_NON_FUNCTION, "Promise:andThenCall")) + local length, values = pack(...) + return self:_andThen(debug.traceback(nil, 2), function() + return callback(unpack(values, 1, length)) + end) +end + +--[=[ + Attaches an `andThen` handler to this Promise that discards the resolved value and returns the given value from it. + + ```lua + promise:andThenReturn("some", "values") + ``` + + This is sugar for + + ```lua + promise:andThen(function() + return "some", "values" + end) + ``` + + :::caution + Promises are eager, so if you pass a Promise to `andThenReturn`, it will begin executing before `andThenReturn` is reached in the chain. Likewise, if you pass a Promise created from [[Promise.reject]] into `andThenReturn`, it's possible that this will trigger the unhandled rejection warning. If you need to return a Promise, it's usually best practice to use [[Promise.andThen]]. + ::: + + @param ... any -- Values to return from the function + @return Promise +]=] +function Promise.prototype:andThenReturn(...) + local length, values = pack(...) + return self:_andThen(debug.traceback(nil, 2), function() + return unpack(values, 1, length) + end) +end + +--[=[ + Cancels this promise, preventing the promise from resolving or rejecting. Does not do anything if the promise is already settled. + + Cancellations will propagate upwards and downwards through chained promises. + + Promises will only be cancelled if all of their consumers are also cancelled. This is to say that if you call `andThen` twice on the same promise, and you cancel only one of the child promises, it will not cancel the parent promise until the other child promise is also cancelled. + + ```lua + promise:cancel() + ``` +]=] +function Promise.prototype:cancel() + if self._status ~= Promise.Status.Started then + return + end + + self._status = Promise.Status.Cancelled + + if self._cancellationHook then + self._cancellationHook() + end + + coroutine.close(self._thread) + + if self._parent then + self._parent:_consumerCancelled(self) + end + + for child in pairs(self._consumers) do + child:cancel() + end + + self:_finalize() +end + +--[[ + Used to decrease the number of consumers by 1, and if there are no more, + cancel this promise. +]] +function Promise.prototype:_consumerCancelled(consumer) + if self._status ~= Promise.Status.Started then + return + end + + self._consumers[consumer] = nil + + if next(self._consumers) == nil then + self:cancel() + end +end + +--[[ + Used to set a handler for when the promise resolves, rejects, or is + cancelled. +]] +function Promise.prototype:_finally(traceback, finallyHandler) + self._unhandledRejection = false + + local promise = Promise._new(traceback, function(resolve, reject, onCancel) + local handlerPromise + + onCancel(function() + -- The finally Promise is not a proper consumer of self. We don't care about the resolved value. + -- All we care about is running at the end. Therefore, if self has no other consumers, it's safe to + -- cancel. We don't need to hold out cancelling just because there's a finally handler. + self:_consumerCancelled(self) + + if handlerPromise then + handlerPromise:cancel() + end + end) + + local finallyCallback = resolve + if finallyHandler then + finallyCallback = function(...) + local callbackReturn = finallyHandler(...) + + if Promise.is(callbackReturn) then + handlerPromise = callbackReturn + + callbackReturn + :finally(function(status) + if status ~= Promise.Status.Rejected then + resolve(self) + end + end) + :catch(function(...) + reject(...) + end) + else + resolve(self) + end + end + end + + if self._status == Promise.Status.Started then + -- The promise is not settled, so queue this. + table.insert(self._queuedFinally, finallyCallback) + else + -- The promise already settled or was cancelled, run the callback now. + finallyCallback(self._status) + end + end) + + return promise +end + +--[=[ + Set a handler that will be called regardless of the promise's fate. The handler is called when the promise is + resolved, rejected, *or* cancelled. + + Returns a new Promise that: + - resolves with the same values that this Promise resolves with. + - rejects with the same values that this Promise rejects with. + - is cancelled if this Promise is cancelled. + + If the value you return from the handler is a Promise: + - We wait for the Promise to resolve, but we ultimately discard the resolved value. + - If the returned Promise rejects, the Promise returned from `finally` will reject with the rejected value from the + *returned* promise. + - If the `finally` Promise is cancelled, and you returned a Promise from the handler, we cancel that Promise too. + + Otherwise, the return value from the `finally` handler is entirely discarded. + + :::note Cancellation + As of Promise v4, `Promise:finally` does not count as a consumer of the parent Promise for cancellation purposes. + This means that if all of a Promise's consumers are cancelled and the only remaining callbacks are finally handlers, + the Promise is cancelled and the finally callbacks run then and there. + + Cancellation still propagates through the `finally` Promise though: if you cancel the `finally` Promise, it can cancel + its parent Promise if it had no other consumers. Likewise, if the parent Promise is cancelled, the `finally` Promise + will also be cancelled. + ::: + + ```lua + local thing = createSomething() + + doSomethingWith(thing) + :andThen(function() + print("It worked!") + -- do something.. + end) + :catch(function() + warn("Oh no it failed!") + end) + :finally(function() + -- either way, destroy thing + + thing:Destroy() + end) + + ``` + + @param finallyHandler (status: Status) -> ...any + @return Promise<...any> +]=] +function Promise.prototype:finally(finallyHandler) + assert(finallyHandler == nil or isCallable(finallyHandler), string.format(ERROR_NON_FUNCTION, "Promise:finally")) + return self:_finally(debug.traceback(nil, 2), finallyHandler) +end + +--[=[ + Same as `andThenCall`, except for `finally`. + + Attaches a `finally` handler to this Promise that calls the given callback with the predefined arguments. + + @param callback (...: any) -> any + @param ...? any -- Additional arguments which will be passed to `callback` + @return Promise +]=] +function Promise.prototype:finallyCall(callback, ...) + assert(isCallable(callback), string.format(ERROR_NON_FUNCTION, "Promise:finallyCall")) + local length, values = pack(...) + return self:_finally(debug.traceback(nil, 2), function() + return callback(unpack(values, 1, length)) + end) +end + +--[=[ + Attaches a `finally` handler to this Promise that discards the resolved value and returns the given value from it. + + ```lua + promise:finallyReturn("some", "values") + ``` + + This is sugar for + + ```lua + promise:finally(function() + return "some", "values" + end) + ``` + + @param ... any -- Values to return from the function + @return Promise +]=] +function Promise.prototype:finallyReturn(...) + local length, values = pack(...) + return self:_finally(debug.traceback(nil, 2), function() + return unpack(values, 1, length) + end) +end + +--[=[ + Yields the current thread until the given Promise completes. Returns the Promise's status, followed by the values that the promise resolved or rejected with. + + @yields + @return Status -- The Status representing the fate of the Promise + @return ...any -- The values the Promise resolved or rejected with. +]=] +function Promise.prototype:awaitStatus() + self._unhandledRejection = false + + if self._status == Promise.Status.Started then + local thread = coroutine.running() + + self + :finally(function() + task.spawn(thread) + end) + -- The finally promise can propagate rejections, so we attach a catch handler to prevent the unhandled + -- rejection warning from appearing + :catch( + function() end + ) + + coroutine.yield() + end + + if self._status == Promise.Status.Resolved then + return self._status, unpack(self._values, 1, self._valuesLength) + elseif self._status == Promise.Status.Rejected then + return self._status, unpack(self._values, 1, self._valuesLength) + end + + return self._status +end + +local function awaitHelper(status, ...) + return status == Promise.Status.Resolved, ... +end + +--[=[ + Yields the current thread until the given Promise completes. Returns true if the Promise resolved, followed by the values that the promise resolved or rejected with. + + :::caution + If the Promise gets cancelled, this function will return `false`, which is indistinguishable from a rejection. If you need to differentiate, you should use [[Promise.awaitStatus]] instead. + ::: + + ```lua + local worked, value = getTheValue():await() + + if worked then + print("got", value) + else + warn("it failed") + end + ``` + + @yields + @return boolean -- `true` if the Promise successfully resolved + @return ...any -- The values the Promise resolved or rejected with. +]=] +function Promise.prototype:await() + return awaitHelper(self:awaitStatus()) +end + +local function expectHelper(status, ...) + if status ~= Promise.Status.Resolved then + error((...) == nil and "Expected Promise rejected with no value." or (...), 3) + end + + return ... +end + +--[=[ + Yields the current thread until the given Promise completes. Returns the values that the promise resolved with. + + ```lua + local worked = pcall(function() + print("got", getTheValue():expect()) + end) + + if not worked then + warn("it failed") + end + ``` + + This is essentially sugar for: + + ```lua + select(2, assert(promise:await())) + ``` + + **Errors** if the Promise rejects or gets cancelled. + + @error any -- Errors with the rejection value if this Promise rejects or gets cancelled. + @yields + @return ...any -- The values the Promise resolved with. +]=] +function Promise.prototype:expect() + return expectHelper(self:awaitStatus()) +end + +-- Backwards compatibility +Promise.prototype.awaitValue = Promise.prototype.expect + +--[[ + Intended for use in tests. + + Similar to await(), but instead of yielding if the promise is unresolved, + _unwrap will throw. This indicates an assumption that a promise has + resolved. +]] +function Promise.prototype:_unwrap() + if self._status == Promise.Status.Started then + error("Promise has not resolved or rejected.", 2) + end + + local success = self._status == Promise.Status.Resolved + + return success, unpack(self._values, 1, self._valuesLength) +end + +function Promise.prototype:_resolve(...) + if self._status ~= Promise.Status.Started then + if Promise.is((...)) then + (...):_consumerCancelled(self) + end + return + end + + -- If the resolved value was a Promise, we chain onto it! + if Promise.is((...)) then + -- Without this warning, arguments sometimes mysteriously disappear + if select("#", ...) > 1 then + local message = string.format( + "When returning a Promise from andThen, extra arguments are " .. "discarded! See:\n\n%s", + self._source + ) + warn(message) + end + + local chainedPromise = ... + + local promise = chainedPromise:andThen(function(...) + self:_resolve(...) + end, function(...) + local maybeRuntimeError = chainedPromise._values[1] + + -- Backwards compatibility < v2 + if chainedPromise._error then + maybeRuntimeError = Error.new({ + error = chainedPromise._error, + kind = Error.Kind.ExecutionError, + context = "[No stack trace available as this Promise originated from an older version of the Promise library (< v2)]", + }) + end + + if Error.isKind(maybeRuntimeError, Error.Kind.ExecutionError) then + return self:_reject(maybeRuntimeError:extend({ + error = "This Promise was chained to a Promise that errored.", + trace = "", + context = string.format( + "The Promise at:\n\n%s\n...Rejected because it was chained to the following Promise, which encountered an error:\n", + self._source + ), + })) + end + + self:_reject(...) + end) + + if promise._status == Promise.Status.Cancelled then + self:cancel() + elseif promise._status == Promise.Status.Started then + -- Adopt ourselves into promise for cancellation propagation. + self._parent = promise + promise._consumers[self] = true + end + + return + end + + self._status = Promise.Status.Resolved + self._valuesLength, self._values = pack(...) + + -- We assume that these callbacks will not throw errors. + for _, callback in ipairs(self._queuedResolve) do + coroutine.wrap(callback)(...) + end + + self:_finalize() +end + +function Promise.prototype:_reject(...) + if self._status ~= Promise.Status.Started then + return + end + + self._status = Promise.Status.Rejected + self._valuesLength, self._values = pack(...) + + -- If there are any rejection handlers, call those! + if not isEmpty(self._queuedReject) then + -- We assume that these callbacks will not throw errors. + for _, callback in ipairs(self._queuedReject) do + coroutine.wrap(callback)(...) + end + else + -- At this point, no one was able to observe the error. + -- An error handler might still be attached if the error occurred + -- synchronously. We'll wait one tick, and if there are still no + -- observers, then we should put a message in the console. + + local err = tostring((...)) + + coroutine.wrap(function() + Promise._timeEvent:Wait() + + -- Someone observed the error, hooray! + if not self._unhandledRejection then + return + end + + -- Build a reasonable message + local message = string.format("Unhandled Promise rejection:\n\n%s\n\n%s", err, self._source) + + for _, callback in ipairs(Promise._unhandledRejectionCallbacks) do + task.spawn(callback, self, unpack(self._values, 1, self._valuesLength)) + end + + if Promise.TEST then + -- Don't spam output when we're running tests. + return + end + + warn(message) + end)() + end + + self:_finalize() +end + +--[[ + Calls any :finally handlers. We need this to be a separate method and + queue because we must call all of the finally callbacks upon a success, + failure, *and* cancellation. +]] +function Promise.prototype:_finalize() + for _, callback in ipairs(self._queuedFinally) do + -- Purposefully not passing values to callbacks here, as it could be the + -- resolved values, or rejected errors. If the developer needs the values, + -- they should use :andThen or :catch explicitly. + coroutine.wrap(callback)(self._status) + end + + self._queuedFinally = nil + self._queuedReject = nil + self._queuedResolve = nil + + -- Clear references to other Promises to allow gc + if not Promise.TEST then + self._parent = nil + self._consumers = nil + end + + task.defer(coroutine.close, self._thread) +end + +--[=[ + Chains a Promise from this one that is resolved if this Promise is already resolved, and rejected if it is not resolved at the time of calling `:now()`. This can be used to ensure your `andThen` handler occurs on the same frame as the root Promise execution. + + ```lua + doSomething() + :now() + :andThen(function(value) + print("Got", value, "synchronously.") + end) + ``` + + If this Promise is still running, Rejected, or Cancelled, the Promise returned from `:now()` will reject with the `rejectionValue` if passed, otherwise with a `Promise.Error(Promise.Error.Kind.NotResolvedInTime)`. This can be checked with [[Error.isKind]]. + + @param rejectionValue? any -- The value to reject with if the Promise isn't resolved + @return Promise +]=] +function Promise.prototype:now(rejectionValue) + local traceback = debug.traceback(nil, 2) + if self._status == Promise.Status.Resolved then + return self:_andThen(traceback, function(...) + return ... + end) + else + return Promise.reject(rejectionValue == nil and Error.new({ + kind = Error.Kind.NotResolvedInTime, + error = "This Promise was not resolved in time for :now()", + context = ":now() was called at:\n\n" .. traceback, + }) or rejectionValue) + end +end + +--[=[ + Repeatedly calls a Promise-returning function up to `times` number of times, until the returned Promise resolves. + + If the amount of retries is exceeded, the function will return the latest rejected Promise. + + ```lua + local function canFail(a, b, c) + return Promise.new(function(resolve, reject) + -- do something that can fail + + local failed, thing = doSomethingThatCanFail(a, b, c) + + if failed then + reject("it failed") + else + resolve(thing) + end + end) + end + + local MAX_RETRIES = 10 + local value = Promise.retry(canFail, MAX_RETRIES, "foo", "bar", "baz") -- args to send to canFail + ``` + + @since 3.0.0 + @param callback (...: P) -> Promise + @param times number + @param ...? P + @return Promise +]=] +function Promise.retry(callback, times, ...) + assert(isCallable(callback), "Parameter #1 to Promise.retry must be a function") + assert(type(times) == "number", "Parameter #2 to Promise.retry must be a number") + + local args, length = { ... }, select("#", ...) + + return Promise.resolve(callback(...)):catch(function(...) + if times > 0 then + return Promise.retry(callback, times - 1, unpack(args, 1, length)) + else + return Promise.reject(...) + end + end) +end + +--[=[ + Repeatedly calls a Promise-returning function up to `times` number of times, waiting `seconds` seconds between each + retry, until the returned Promise resolves. + + If the amount of retries is exceeded, the function will return the latest rejected Promise. + + @since v3.2.0 + @param callback (...: P) -> Promise + @param times number + @param seconds number + @param ...? P + @return Promise +]=] +function Promise.retryWithDelay(callback, times, seconds, ...) + assert(isCallable(callback), "Parameter #1 to Promise.retryWithDelay must be a function") + assert(type(times) == "number", "Parameter #2 (times) to Promise.retryWithDelay must be a number") + assert(type(seconds) == "number", "Parameter #3 (seconds) to Promise.retryWithDelay must be a number") + + local args, length = { ... }, select("#", ...) + + return Promise.resolve(callback(...)):catch(function(...) + if times > 0 then + Promise.delay(seconds):await() + + return Promise.retryWithDelay(callback, times - 1, seconds, unpack(args, 1, length)) + else + return Promise.reject(...) + end + end) +end + + +--[=[ + Repeatedly calls a Promise-returning function up to `times` number of times, with a variable delay between retries + based on the specified equation ("Linear", "Exponential", or "Quadratic"), until the returned Promise resolves. + + If the amount of retries is exceeded, the function will return the latest rejected Promise. + + @param callback (...: P) -> Promise + @param times number + @param seconds number + @param equation string + @param ...? P + @return Promise +]=] +function Promise.retryWithVariableDelay(callback, times, seconds, equation, ...) + assert(isCallable(callback), "Parameter #1 to Promise.retryWithVariableDelay must be a function") + assert(type(times) == "number", "Parameter #2 (times) to Promise.retryWithVariableDelay must be a number") + assert(type(seconds) == "number", "Parameter #3 (seconds) to Promise.retryWithVariableDelay must be a number") + assert(type(equation) == "string", "Parameter #3 (seconds) to Promise.retryWithVariableDelay must be a string") + + local attempts = 0 + local args, length = { ... }, select("#", ...) + + local function attempt() + attempts += 1 + return Promise.resolve(callback(unpack(args, 1, length))):catch(function(...) + if attempts < times then + + local delaySeconds = seconds + if equation == "Exponential" then + delaySeconds = seconds * math.pow(2, attempts) + elseif equation == "Quadratic" then + delaySeconds = seconds * math.pow(attempts, 2) + end + Promise.delay(delaySeconds):await() + + return attempt() + else + warn("Return reject") + return Promise.reject(...) + end + end) + end + + return Promise.new(function(resolve, reject) + attempt():andThen(resolve):catch(reject) + end) +end + +--[=[ + Converts an event into a Promise which resolves the next time the event fires. + + The optional `predicate` callback, if passed, will receive the event arguments and should return `true` or `false`, based on if this fired event should resolve the Promise or not. If `true`, the Promise resolves. If `false`, nothing happens and the predicate will be rerun the next time the event fires. + + The Promise will resolve with the event arguments. + + :::tip + This function will work given any object with a `Connect` method. This includes all Roblox events. + ::: + + ```lua + -- Creates a Promise which only resolves when `somePart` is touched + -- by a part named `"Something specific"`. + return Promise.fromEvent(somePart.Touched, function(part) + return part.Name == "Something specific" + end) + ``` + + @since 3.0.0 + @param event Event -- Any object with a `Connect` method. This includes all Roblox events. + @param predicate? (...: P) -> boolean -- A function which determines if the Promise should resolve with the given value, or wait for the next event to check again. + @return Promise

+]=] +function Promise.fromEvent(event, predicate) + predicate = predicate or function() + return true + end + + return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel) + local connection + local shouldDisconnect = false + + local function disconnect() + connection:Disconnect() + connection = nil + end + + -- We use shouldDisconnect because if the callback given to Connect is called before + -- Connect returns, connection will still be nil. This happens with events that queue up + -- events when there's nothing connected, such as RemoteEvents + + connection = event:Connect(function(...) + local callbackValue = predicate(...) + + if callbackValue == true then + resolve(...) + + if connection then + disconnect() + else + shouldDisconnect = true + end + elseif type(callbackValue) ~= "boolean" then + error("Promise.fromEvent predicate should always return a boolean") + end + end) + + if shouldDisconnect and connection then + return disconnect() + end + + onCancel(disconnect) + end) +end + +--[=[ + Registers a callback that runs when an unhandled rejection happens. An unhandled rejection happens when a Promise + is rejected, and the rejection is not observed with `:catch`. + + The callback is called with the actual promise that rejected, followed by the rejection values. + + @since v3.2.0 + @param callback (promise: Promise, ...: any) -- A callback that runs when an unhandled rejection happens. + @return () -> () -- Function that unregisters the `callback` when called +]=] +function Promise.onUnhandledRejection(callback) + table.insert(Promise._unhandledRejectionCallbacks, callback) + + return function() + local index = table.find(Promise._unhandledRejectionCallbacks, callback) + + if index then + table.remove(Promise._unhandledRejectionCallbacks, index) + end + end +end + +return Promise :: { + Error: any, + + all: (promises: { TypedPromise }) -> TypedPromise<{ T }>, + allSettled: (promise: { TypedPromise }) -> TypedPromise<{ Status }>, + any: (promise: { TypedPromise }) -> TypedPromise, + defer: ( + executor: ( + resolve: (TReturn...) -> (), + reject: (...any) -> (), + onCancel: (abortHandler: (() -> ())?) -> boolean + ) -> () + ) -> TypedPromise, + delay: (seconds: number) -> TypedPromise, + each: ( + list: { T | TypedPromise }, + predicate: (value: T, index: number) -> TReturn | TypedPromise + ) -> TypedPromise<{ TReturn }>, + fold: ( + list: { T | TypedPromise }, + reducer: (accumulator: TReturn, value: T, index: number) -> TReturn | TypedPromise + ) -> TypedPromise, + fromEvent: ( + event: Signal, + predicate: ((TReturn...) -> boolean)? + ) -> TypedPromise, + is: (object: any) -> boolean, + new: ( + executor: ( + resolve: (TReturn...) -> (), + reject: (...any) -> (), + onCancel: (abortHandler: (() -> ())?) -> boolean + ) -> () + ) -> TypedPromise, + onUnhandledRejection: (callback: (promise: TypedPromise, ...any) -> ()) -> () -> (), + promisify: (callback: (TArgs...) -> TReturn...) -> (TArgs...) -> TypedPromise, + race: (promises: { TypedPromise }) -> TypedPromise, + reject: (...any) -> TypedPromise<...any>, + resolve: (TReturn...) -> TypedPromise, + retry: ( + callback: (TArgs...) -> TypedPromise, + times: number, + TArgs... + ) -> TypedPromise, + retryWithDelay: ( + callback: (TArgs...) -> TypedPromise, + times: number, + seconds: number, + TArgs... + ) -> TypedPromise, + retryWithVariableDelay: ( + callback: (TArgs...) -> TypedPromise, + times: number, + seconds: number, + equation: "Exponential" | "Quadratic", + TArgs... + ) -> TypedPromise, + some: (promise: { TypedPromise }, count: number) -> TypedPromise<{ T }>, + try: (callback: (TArgs...) -> TReturn..., TArgs...) -> TypedPromise, +} diff --git a/samples/Luau/ReactRobloxHostConfig.luau b/samples/Luau/ReactRobloxHostConfig.luau new file mode 100644 index 0000000000..53ca831b4f --- /dev/null +++ b/samples/Luau/ReactRobloxHostConfig.luau @@ -0,0 +1,1366 @@ +--// Authored by @JSDotLua (https://github.com/jsdotlua) +--// Fetched from (https://github.com/jsdotlua/react-lua/blob/main/modules/react-roblox/src/client/ReactRobloxHostConfig.lua) +--// Licensed under the MIT License (https://github.com/jsdotlua/react-lua/blob/main/LICENSE) + +--!strict +-- ROBLOX upstream: https://github.com/facebook/react/blob/8e5adfbd7e605bda9c5e96c10e015b3dc0df688e/packages/react-dom/src/client/ReactDOMHostConfig.js +-- ROBLOX upstream: https://github.com/facebook/react/blob/efd8f6442d1aa7c4566fe812cba03e7e83aaccc3/packages/react-native-renderer/src/ReactNativeHostConfig.js +--[[* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow +]] +-- FIXME (roblox): remove this when our unimplemented +local function unimplemented(message: string) + print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + print("UNIMPLEMENTED ERROR: " .. tostring(message)) + error("FIXME (roblox): " .. message .. " is unimplemented", 2) +end + +local CollectionService = game:GetService("CollectionService") +local LuauPolyfill = require("@pkg/@jsdotlua/luau-polyfill") +local inspect = LuauPolyfill.util.inspect +local console = require("@pkg/@jsdotlua/shared").console +local Object = LuauPolyfill.Object +local setTimeout = LuauPolyfill.setTimeout +local clearTimeout = LuauPolyfill.clearTimeout + +-- local type {DOMEventName} = require(Packages.../events/DOMEventNames') +-- local type {Fiber, FiberRoot} = require(Packages.react-reconciler/src/ReactInternalTypes') +-- local type { +-- BoundingRect, +-- IntersectionObserverOptions, +-- ObserveVisibleRectsCallback, +-- } = require(Packages.react-reconciler/src/ReactTestSelectors') +local ReactRobloxHostTypes = require("./ReactRobloxHostTypes.roblox.lua") +type RootType = ReactRobloxHostTypes.RootType +type Container = ReactRobloxHostTypes.Container +type HostInstance = ReactRobloxHostTypes.HostInstance +type SuspenseInstance = ReactRobloxHostTypes.SuspenseInstance +type TextInstance = ReactRobloxHostTypes.TextInstance +type Props = ReactRobloxHostTypes.Props +type Type = ReactRobloxHostTypes.Type +type HostContext = ReactRobloxHostTypes.HostContext + +-- local type {ReactScopeInstance} = require(Packages.shared/ReactTypes') +-- local type {ReactDOMFundamentalComponentInstance} = require(Packages.../shared/ReactDOMTypes') + +local ReactRobloxComponentTree = require("./ReactRobloxComponentTree") +local precacheFiberNode = ReactRobloxComponentTree.precacheFiberNode +local uncacheFiberNode = ReactRobloxComponentTree.uncacheFiberNode +local updateFiberProps = ReactRobloxComponentTree.updateFiberProps +-- local getClosestInstanceFromNode = ReactRobloxComponentTree.getClosestInstanceFromNode +-- local getFiberFromScopeInstance = ReactRobloxComponentTree.getFiberFromScopeInstance +-- local getInstanceFromNodeDOMTree = ReactRobloxComponentTree.getInstanceFromNode +-- local isContainerMarkedAsRoot = ReactRobloxComponentTree.isContainerMarkedAsRoot + +-- local {hasRole} = require(Packages../DOMAccessibilityRoles') +local ReactRobloxComponent = require("./ReactRobloxComponent") +-- local createElement = ReactRobloxComponent.createElement +-- local createTextNode = ReactRobloxComponent.createTextNode +local setInitialProperties = ReactRobloxComponent.setInitialProperties +local diffProperties = ReactRobloxComponent.diffProperties +local updateProperties = ReactRobloxComponent.updateProperties +local cleanupHostComponent = ReactRobloxComponent.cleanupHostComponent +-- local diffHydratedProperties = ReactRobloxComponent.diffHydratedProperties +-- local diffHydratedText = ReactRobloxComponent.diffHydratedText +-- local trapClickOnNonInteractiveElement = ReactRobloxComponent.trapClickOnNonInteractiveElement +-- local warnForUnmatchedText = ReactRobloxComponent.warnForUnmatchedText +-- local warnForDeletedHydratableElement = ReactRobloxComponent.warnForDeletedHydratableElement +-- local warnForDeletedHydratableText = ReactRobloxComponent.warnForDeletedHydratableText +-- local warnForInsertedHydratedElement = ReactRobloxComponent.warnForInsertedHydratedElement +-- local warnForInsertedHydratedText = ReactRobloxComponent.warnForInsertedHydratedText +-- local {getSelectionInformation, restoreSelection} = require(Packages../ReactInputSelection') +-- local setTextContent = require(Packages../setTextContent') +-- local {validateDOMNesting, updatedAncestorInfo} = require(Packages../validateDOMNesting') +-- local { +-- isEnabled as ReactBrowserEventEmitterIsEnabled, +-- setEnabled as ReactBrowserEventEmitterSetEnabled, +-- } = require(Packages.../events/ReactDOMEventListener') +-- local {getChildNamespace} = require(Packages.../shared/DOMNamespaces') +-- local { +-- ELEMENT_NODE, +-- TEXT_NODE, +-- COMMENT_NODE, +-- DOCUMENT_NODE, +-- DOCUMENT_FRAGMENT_NODE, +-- } = require(Packages.../shared/HTMLNodeType') +-- local dangerousStyleValue = require(Packages.../shared/dangerousStyleValue') + +-- local {REACT_OPAQUE_ID_TYPE} = require(Packages.shared/ReactSymbols') +-- local {retryIfBlockedOn} = require(Packages.../events/ReactDOMEventReplaying') + +local ReactFeatureFlags = require("@pkg/@jsdotlua/shared").ReactFeatureFlags +-- local enableSuspenseServerRenderer = ReactFeatureFlags.enableSuspenseServerRenderer +-- local enableFundamentalAPI = ReactFeatureFlags.enableFundamentalAPI +local enableCreateEventHandleAPI = ReactFeatureFlags.enableCreateEventHandleAPI +-- local enableScopeAPI = ReactFeatureFlags.enableScopeAPI +-- local enableEagerRootListeners = ReactFeatureFlags.enableEagerRootListeners + +-- local {HostComponent, HostText} = require(Packages.react-reconciler/src/ReactWorkTags') +-- local { +-- listenToReactEvent, +-- listenToAllSupportedEvents, +-- } = require(Packages.../events/DOMPluginEventSystem') + +type Array = { [number]: T } +type Object = { [any]: any } + +-- ROBLOX deviation: Moved to ReactRobloxHostTypes +-- export type Type = string; +-- export type Props = { +-- autoFocus: boolean?, +-- children: any, +-- disabled: boolean?, +-- hidden: boolean?, +-- suppressHydrationWarning: boolean?, +-- dangerouslySetInnerHTML: any, +-- style: { display: string, [any]: any }?, +-- bottom: number?, +-- left: number?, +-- right: number?, +-- top: number?, +-- -- ... +-- [any]: any, +-- }; +-- export type EventTargetChildElement = { +-- type: string, +-- props: nil | { +-- style?: { +-- position?: string, +-- zIndex?: number, +-- bottom?: string, +-- left?: string, +-- right?: string, +-- top?: string, +-- ... +-- }, +-- ... +-- }, +-- ... +-- end + +-- ROBLOX deviation: Moved to ReactRobloxHostTypes +-- export type SuspenseInstance = Comment & {_reactRetry?: () => void, ...} +-- export type HydratableInstance = Instance | TextInstance | SuspenseInstance + +-- ROBLOX deviation: Moved to ReactRobloxHostTypes +-- export type PublicInstance = Element | Text +-- type HostContextDev = { +-- namespace: string, +-- ancestorInfo: any, +-- -- ... +-- [any]: any, +-- } +-- type HostContextProd = string +-- export type HostContext = HostContextDev | HostContextProd + +-- export type UpdatePayload = Array +-- ROBLOX FIXME: cannot create type equal to void +-- export type ChildSet = void; -- Unused +-- export type TimeoutHandle = TimeoutID +-- export type NoTimeout = -1 +-- export type RendererInspectionConfig = $ReadOnly<{or}> + +-- export opaque type OpaqueIDType = +-- | string +-- | { +-- toString: () => string | void, +-- valueOf: () => string | void, +-- end + +-- type SelectionInformation = {| +-- focusedElem: nil | HTMLElement, +-- selectionRange: mixed, +-- |} + +-- local SUPPRESS_HYDRATION_WARNING +-- if __DEV__) +-- SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning' +-- end + +-- local SUSPENSE_START_DATA = '$' +-- local SUSPENSE_END_DATA = '/$' +-- local SUSPENSE_PENDING_START_DATA = '$?' +-- local SUSPENSE_FALLBACK_START_DATA = '$!' + +-- local STYLE = 'style' + +-- local eventsEnabled: boolean? = nil +-- local selectionInformation: nil | SelectionInformation = nil + +-- function shouldAutoFocusHostComponent(type: string, props: Props): boolean { +-- switch (type) +-- case 'button': +-- case 'input': +-- case 'select': +-- case 'textarea': +-- return !!props.autoFocus +-- end +-- return false +-- end + +-- ROBLOX deviation: Use GetDescendants rather than recursion +local function recursivelyUncacheFiberNode(node: HostInstance) + -- ROBLOX https://jira.rbx.com/browse/LUAFDN-713: Tables are somehow ending up + -- in this function that expects Instances. In that case, we won't be able to + -- iterate through its descendants. + if typeof(node :: any) ~= "Instance" then + return + end + + uncacheFiberNode(node) + + for _, child in node:GetDescendants() do + uncacheFiberNode(child) + end +end + +local exports: { [any]: any } = {} +Object.assign( + exports, + require("@pkg/@jsdotlua/shared").ReactFiberHostConfig.WithNoPersistence +) + +exports.getRootHostContext = function(rootContainerInstance: Container): HostContext + -- ROBLOX deviation: This is a lot of HTML-DOM specific logic; I'm not clear on + -- whether there'll be an equivalent of `namespaceURI` for our use cases, but + -- we may want to provide other kinds of context for host objects. + + -- For now, as a guess, we'll return the kind of instance we're attached to + return rootContainerInstance.ClassName + + -- local type + -- local namespace + -- local nodeType = rootContainerInstance.nodeType + -- switch (nodeType) + -- case DOCUMENT_NODE: + -- case DOCUMENT_FRAGMENT_NODE: { + -- type = nodeType == DOCUMENT_NODE ? '#document' : '#fragment' + -- local root = (rootContainerInstance: any).documentElement + -- namespace = root ? root.namespaceURI : getChildNamespace(null, '') + -- break + -- end + -- default: { + -- local container: any = + -- nodeType == COMMENT_NODE + -- ? rootContainerInstance.parentNode + -- : rootContainerInstance + -- local ownNamespace = container.namespaceURI or nil + -- type = container.tagName + -- namespace = getChildNamespace(ownNamespace, type) + -- break + -- end + -- end + -- if _G.__DEV__ then + -- local validatedTag = type.toLowerCase() + -- local ancestorInfo = updatedAncestorInfo(null, validatedTag) + -- return {namespace, ancestorInfo} + -- end + -- return namespace +end + +exports.getChildHostContext = function( + parentHostContext: HostContext, + type: string, + rootContainerInstance: Container +): HostContext + -- ROBLOX deviation: unclear on the purpose here just yet, might be fine to + -- just return parent's hostContext for now + return parentHostContext + -- if _G.__DEV__ then + -- local parentHostContextDev = ((parentHostContext: any): HostContextDev) + -- local namespace = getChildNamespace(parentHostContextDev.namespace, type) + -- local ancestorInfo = updatedAncestorInfo( + -- parentHostContextDev.ancestorInfo, + -- type, + -- ) + -- return {namespace, ancestorInfo} + -- end + -- local parentNamespace = ((parentHostContext: any): HostContextProd) + -- return getChildNamespace(parentNamespace, type) +end + +exports.getPublicInstance = function(instance: Instance): any + return instance +end + +exports.prepareForCommit = function(containerInfo: Container): Object? + -- eventsEnabled = ReactBrowserEventEmitterIsEnabled() + -- selectionInformation = getSelectionInformation() + local activeInstance = nil + if enableCreateEventHandleAPI then + unimplemented("enableCreateEventHandleAPI") + -- local focusedElem = selectionInformation.focusedElem + -- if focusedElem ~= nil then + -- activeInstance = getClosestInstanceFromNode(focusedElem) + -- end + end + -- ReactBrowserEventEmitterSetEnabled(false) + return activeInstance +end + +exports.beforeActiveInstanceBlur = function() + if enableCreateEventHandleAPI then + unimplemented("enableCreateEventHandleAPI") + -- ReactBrowserEventEmitterSetEnabled(true) + -- dispatchBeforeDetachedBlur((selectionInformation: any).focusedElem) + -- ReactBrowserEventEmitterSetEnabled(false) + end +end + +exports.afterActiveInstanceBlur = function() + if enableCreateEventHandleAPI then + unimplemented("enableCreateEventHandleAPI") + -- ReactBrowserEventEmitterSetEnabled(true) + -- dispatchAfterDetachedBlur((selectionInformation: any).focusedElem) + -- ReactBrowserEventEmitterSetEnabled(false) + end +end + +exports.resetAfterCommit = function(containerInfo: Container) + -- warn("Skip unimplemented: resetAfterCommit") + -- restoreSelection(selectionInformation) + -- ReactBrowserEventEmitterSetEnabled(eventsEnabled) + -- eventsEnabled = nil + -- selectionInformation = nil +end + +exports.createInstance = function( + type_: string, + props: Props, + rootContainerInstance: Container, + hostContext: HostContext, + internalInstanceHandle: Object +): HostInstance + -- local hostKey = virtualNode.hostKey + + local domElement = Instance.new(type_) + -- ROBLOX deviation: compatibility with old Roact where instances have their name + -- set to the key value + if internalInstanceHandle.key then + domElement.Name = internalInstanceHandle.key + else + local currentHandle = internalInstanceHandle.return_ + while currentHandle do + if currentHandle.key then + domElement.Name = currentHandle.key + break + end + currentHandle = currentHandle.return_ + end + end + + precacheFiberNode(internalInstanceHandle, domElement) + updateFiberProps(domElement, props) + + -- TODO: Support refs (does that actually happen here, or later?) + -- applyRef(element.props[Ref], instance) + + -- Will have to be managed outside of createInstance + -- if virtualNode.eventManager ~= nil then + -- virtualNode.eventManager:resume() + -- end + + return domElement + + -- return Instance.new("Frame") + -- local parentNamespace: string + -- if __DEV__) + -- -- TODO: take namespace into account when validating. + -- local hostContextDev = ((hostContext: any): HostContextDev) + -- validateDOMNesting(type, nil, hostContextDev.ancestorInfo) + -- if + -- typeof props.children == 'string' or + -- typeof props.children == 'number' + -- ) + -- local string = '' + props.children + -- local ownAncestorInfo = updatedAncestorInfo( + -- hostContextDev.ancestorInfo, + -- type, + -- ) + -- validateDOMNesting(null, string, ownAncestorInfo) + -- end + -- parentNamespace = hostContextDev.namespace + -- } else { + -- parentNamespace = ((hostContext: any): HostContextProd) + -- end + -- local domElement: Instance = createElement( + -- type, + -- props, + -- rootContainerInstance, + -- parentNamespace, + -- ) +end + +exports.appendInitialChild = function(parentInstance: Instance, child: Instance) + -- ROBLOX deviation: Establish hierarchy with Parent property + child.Parent = parentInstance +end + +exports.finalizeInitialChildren = function( + domElement: HostInstance, + type_: string, + props: Props, + rootContainerInstance: Container, + hostContext: HostContext +): boolean + setInitialProperties(domElement, type_, props, rootContainerInstance) + return false + -- return shouldAutoFocusHostComponent(type_, props) +end + +local function prepareUpdate( + domElement: Instance, + type_: string, + oldProps: Props, + newProps: Props, + rootContainerInstance: Container, + hostContext: HostContext +): nil | Array + -- if _G.__DEV__ then + -- local hostContextDev = ((hostContext: any): HostContextDev) + -- if + -- typeof newProps.children ~= typeof oldProps.children and + -- (typeof newProps.children == 'string' or + -- typeof newProps.children == 'number') + -- ) + -- local string = '' + newProps.children + -- local ownAncestorInfo = updatedAncestorInfo( + -- hostContextDev.ancestorInfo, + -- type, + -- ) + -- validateDOMNesting(null, string, ownAncestorInfo) + -- end + -- end + return diffProperties(domElement, type_, oldProps, newProps, rootContainerInstance) +end +exports.prepareUpdate = prepareUpdate + +exports.shouldSetTextContent = function(_type: string, _props: Props): boolean + -- ROBLOX deviation: Ignore TextInstance logic, which isn't applicable to Roblox + return false + -- return ( + -- type == 'textarea' or + -- type == 'option' or + -- type == 'noscript' or + -- typeof props.children == 'string' or + -- typeof props.children == 'number' or + -- (typeof props.dangerouslySetInnerHTML == 'table’' and + -- props.dangerouslySetInnerHTML ~= nil and + -- props.dangerouslySetInnerHTML.__html ~= nil) + -- ) +end + +-- ROBLOX deviation: Text nodes aren't supported in Roblox renderer, so error so that tests fail immediately +exports.createTextInstance = function( + text: string, + rootContainerInstance: Container, + hostContext: HostContext, + internalInstanceHandle: Object +): any + unimplemented("createTextInstance") + return nil +end + +exports.isPrimaryRenderer = true +exports.warnsIfNotActing = true +-- This initialization code may run even on server environments +-- if a component just imports ReactDOM (e.g. for findDOMNode). +-- Some environments might not have setTimeout or clearTimeout. +-- ROBLOX deviation: We're only dealing with client right now, so these always populate +exports.scheduleTimeout = setTimeout +exports.cancelTimeout = clearTimeout +exports.noTimeout = -1 + +-- ------------------- +-- Mutation +-- ------------------- + +exports.supportsMutation = true + +exports.commitMount = function( + domElement: Instance, + type: string, + newProps: Props, + internalInstanceHandle: Object +) + unimplemented("commitMount") + -- -- Despite the naming that might imply otherwise, this method only + -- -- fires if there is an `Update` effect scheduled during mounting. + -- -- This happens if `finalizeInitialChildren` returns `true` (which it + -- -- does to implement the `autoFocus` attribute on the client). But + -- -- there are also other cases when this might happen (such as patching + -- -- up text content during hydration mismatch). So we'll check this again. + -- if shouldAutoFocusHostComponent(type, newProps)) + -- ((domElement: any): + -- | HTMLButtonElement + -- | HTMLInputElement + -- | HTMLSelectElement + -- | HTMLTextAreaElement).focus() + -- end +end + +exports.commitUpdate = function( + domElement: Instance, + updatePayload: Array, + type_: string, + oldProps: Props, + newProps: Props, + internalInstanceHandle: Object +) + -- Update the props handle so that we know which props are the ones with + -- with current event handlers. + updateFiberProps(domElement, newProps) + -- Apply the diff to the DOM node. + updateProperties(domElement, updatePayload, oldProps) +end + +-- ROBLOX deviation: Ignore TextInstance logic, which isn't applicable to Roblox +-- exports.resetTextContent(domElement: Instance): void { +-- setTextContent(domElement, '') +-- end + +-- ROBLOX deviation: Ignore TextInstance logic, which isn't applicable to Roblox +-- exports.commitTextUpdate( +-- textInstance: TextInstance, +-- oldText: string, +-- newText: string, +-- ): void { +-- textInstance.nodeValue = newText +-- end + +local function checkTags(instance: Instance) + if typeof(instance :: any) ~= "Instance" then + console.warn("Could not check tags on non-instance %s.", inspect(instance)) + return + end + if not instance:IsDescendantOf(game) then + if #CollectionService:GetTags(instance) > 0 then + console.warn( + 'Tags applied to orphaned %s "%s" cannot be accessed via' + .. " CollectionService:GetTagged. If you're relying on tag" + .. " behavior in a unit test, consider mounting your test " + .. "root into the DataModel.", + instance.ClassName, + instance.Name + ) + end + end +end + +exports.appendChild = function(parentInstance: Instance, child: Instance) + -- ROBLOX deviation: Roblox's DOM is based on child->parent references + child.Parent = parentInstance + -- parentInstance.appendChild(child) + if _G.__DEV__ then + checkTags(child) + end +end + +exports.appendChildToContainer = function(container: Container, child: Instance) + -- ROBLOX TODO: Some of this logic may come back; for now, keep it simple + local parentNode = container + exports.appendChild(parentNode, child) + + -- if container.nodeType == COMMENT_NODE) + -- parentNode = (container.parentNode: any) + -- parentNode.insertBefore(child, container) + -- } else { + -- parentNode = container + -- parentNode.appendChild(child) + -- end + -- -- This container might be used for a portal. + -- -- If something inside a portal is clicked, that click should bubble + -- -- through the React tree. However, on Mobile Safari the click would + -- -- never bubble through the *DOM* tree unless an ancestor with onclick + -- -- event exists. So we wouldn't see it and dispatch it. + -- -- This is why we ensure that non React root containers have inline onclick + -- -- defined. + -- -- https://github.com/facebook/react/issues/11918 + -- local reactRootContainer = container._reactRootContainer + -- if + -- reactRootContainer == nil and parentNode.onclick == nil + -- then + -- -- TODO: This cast may not be sound for SVG, MathML or custom elements. + -- trapClickOnNonInteractiveElement(((parentNode: any): HTMLElement)) + -- end +end + +exports.insertBefore = + function(parentInstance: Instance, child: Instance, _beforeChild: Instance) + -- ROBLOX deviation: Roblox's DOM is based on child->parent references + child.Parent = parentInstance + -- parentInstance.insertBefore(child, beforeChild) + if _G.__DEV__ then + checkTags(child) + end + end + +exports.insertInContainerBefore = + function(container: Container, child: Instance, beforeChild: Instance) + -- ROBLOX deviation: use our container definition + local parentNode = container + exports.insertBefore(parentNode, child, beforeChild) + -- if container.nodeType == COMMENT_NODE) + -- (container.parentNode: any).insertBefore(child, beforeChild) + -- } else { + -- container.insertBefore(child, beforeChild) + -- end + end + +-- function createEvent(type: DOMEventName, bubbles: boolean): Event { +-- local event = document.createEvent('Event') +-- event.initEvent(((type: any): string), bubbles, false) +-- return event +-- end + +-- function dispatchBeforeDetachedBlur(target: HTMLElement): void { +-- if enableCreateEventHandleAPI) +-- local event = createEvent('beforeblur', true) +-- -- Dispatch "beforeblur" directly on the target, +-- -- so it gets picked up by the event system and +-- -- can propagate through the React internal tree. +-- target.dispatchEvent(event) +-- end +-- end + +-- function dispatchAfterDetachedBlur(target: HTMLElement): void { +-- if enableCreateEventHandleAPI) +-- local event = createEvent('afterblur', false) +-- -- So we know what was detached, make the relatedTarget the +-- -- detached target on the "afterblur" event. +-- (event: any).relatedTarget = target +-- -- Dispatch the event on the document. +-- document.dispatchEvent(event) +-- end +-- end + +exports.removeChild = function(_parentInstance: Instance, child: Instance) + recursivelyUncacheFiberNode(child) + -- ROBLOX deviation: The roblox renderer tracks bindings and event managers + -- for instances, so make sure we clean those up when we remove the instance + cleanupHostComponent(child) + -- ROBLOX deviation: Roblox's DOM is based on child->parent references + child.Parent = nil + -- parentInstance.removeChild(child) + -- ROBLOX deviation: Guard against misuse by locking parent and forcing external cleanup via Destroy + child:Destroy() +end + +exports.removeChildFromContainer = function(_container: Container, child: Instance) + -- ROBLOX deviation: Containers don't have special behavior and comment nodes + -- have no datamodel equivalent, so just forward to the removeChild logic + exports.removeChild(_container, child) + -- if container.nodeType == COMMENT_NODE) + -- (container.parentNode: any).removeChild(child) + -- } else { + -- container.removeChild(child) + -- end +end + +exports.clearSuspenseBoundary = + function(parentInstance: Instance, suspenseInstance: SuspenseInstance) + -- ROBLOX FIXME: this is a major thing we need to fix for Suspense to work as a feature + unimplemented("clearSuspenseBoundary") + -- local node = suspenseInstance + -- -- Delete all nodes within this suspense boundary. + -- -- There might be nested nodes so we need to keep track of how + -- -- deep we are and only break out when we're back on top. + -- local depth = 0 + -- do { + -- local nextNode = node.nextSibling + -- parentInstance.removeChild(node) + -- if nextNode and nextNode.nodeType == COMMENT_NODE) + -- local data = ((nextNode: any).data: string) + -- if data == SUSPENSE_END_DATA) + -- if depth == 0) + -- parentInstance.removeChild(nextNode) + -- -- Retry if any event replaying was blocked on this. + -- retryIfBlockedOn(suspenseInstance) + -- return + -- } else { + -- depth-- + -- end + -- } else if + -- data == SUSPENSE_START_DATA or + -- data == SUSPENSE_PENDING_START_DATA or + -- data == SUSPENSE_FALLBACK_START_DATA + -- ) + -- depth++ + -- end + -- end + -- node = nextNode + -- } while (node) + -- -- TODO: Warn, we didn't find the end comment boundary. + -- -- Retry if any event replaying was blocked on this. + -- retryIfBlockedOn(suspenseInstance) + end + +exports.clearSuspenseBoundaryFromContainer = + function(container: Container, suspenseInstance: SuspenseInstance) + -- ROBLOX FIXME: this is a major thing we need to fix for Suspense to work as a feature + unimplemented("clearSuspenseBoundaryFromContainer") + -- if container.nodeType == COMMENT_NODE) + -- clearSuspenseBoundary((container.parentNode: any), suspenseInstance) + -- } else if container.nodeType == ELEMENT_NODE) + -- clearSuspenseBoundary((container: any), suspenseInstance) + -- } else { + -- -- Document nodes should never contain suspense boundaries. + -- end + -- -- Retry if any event replaying was blocked on this. + -- retryIfBlockedOn(container) + end + +exports.hideInstance = function(instance: Instance) + unimplemented("hideInstance") + -- -- TODO: Does this work for all element types? What about MathML? Should we + -- -- pass host context to this method? + -- instance = ((instance: any): HTMLElement) + -- local style = instance.style + -- if typeof style.setProperty == 'function') + -- style.setProperty('display', 'none', 'important') + -- } else { + -- style.display = 'none' + -- end +end + +-- ROBLOX deviation: error on TextInstance logic, which isn't applicable to Roblox +exports.hideTextInstance = function(textInstance: TextInstance): () + unimplemented("hideTextInstance") + -- textInstance.nodeValue = '' +end + +exports.unhideInstance = function(instance: Instance, props: Props) + unimplemented("unhideInstance") + -- instance = ((instance: any): HTMLElement) + -- local styleProp = props[STYLE] + -- local display = + -- styleProp ~= undefined and + -- styleProp ~= nil and + -- styleProp.hasOwnProperty('display') + -- ? styleProp.display + -- : nil + -- instance.style.display = dangerousStyleValue('display', display) +end + +-- ROBLOX deviation: error on TextInstance logic, which isn't applicable to Roblox +exports.unhideTextInstance = function(textInstance: TextInstance, text: string): () + unimplemented("unhideTextInstance") + -- textInstance.nodeValue = text +end + +exports.clearContainer = function(container: Container) + -- ROBLOX deviation: with Roblox, we can simply enumerate and remove the children + local parentInstance = container + for _, child in parentInstance:GetChildren() do + exports.removeChild(parentInstance, child) + end + -- if container.nodeType == ELEMENT_NODE) + -- ((container: any): Element).textContent = '' + -- } else if container.nodeType == DOCUMENT_NODE) + -- local body = ((container: any): Document).body + -- if body ~= nil) + -- body.textContent = '' + -- end + -- end +end + +-- -- ------------------- +-- -- Hydration +-- -- ------------------- + +-- export local supportsHydration = true + +-- exports.canHydrateInstance( +-- instance: HydratableInstance, +-- type: string, +-- props: Props, +-- ): nil | Instance { +-- if +-- instance.nodeType ~= ELEMENT_NODE or +-- type.toLowerCase() ~= instance.nodeName.toLowerCase() +-- ) +-- return nil +-- end +-- -- This has now been refined to an element node. +-- return ((instance: any): Instance) +-- end + +-- exports.canHydrateTextInstance( +-- instance: HydratableInstance, +-- text: string, +-- ): nil | TextInstance { +-- if text == '' or instance.nodeType ~= TEXT_NODE) +-- -- Empty strings are not parsed by HTML so there won't be a correct match here. +-- return nil +-- end +-- -- This has now been refined to a text node. +-- return ((instance: any): TextInstance) +-- end + +-- exports.canHydrateSuspenseInstance( +-- instance: HydratableInstance, +-- ): nil | SuspenseInstance { +-- if instance.nodeType ~= COMMENT_NODE) +-- -- Empty strings are not parsed by HTML so there won't be a correct match here. +-- return nil +-- end +-- -- This has now been refined to a suspense node. +-- return ((instance: any): SuspenseInstance) +-- end + +-- exports.isSuspenseInstanceFallback(instance: SuspenseInstance) +-- return instance.data == SUSPENSE_FALLBACK_START_DATA +-- end + +-- exports.registerSuspenseInstanceRetry( +-- instance: SuspenseInstance, +-- callback: () => void, +-- ) +-- instance._reactRetry = callback +-- end + +-- function getNextHydratable(node) +-- -- Skip non-hydratable nodes. +-- for (; node ~= nil; node = node.nextSibling) +-- local nodeType = node.nodeType +-- if nodeType == ELEMENT_NODE or nodeType == TEXT_NODE) +-- break +-- end +-- if enableSuspenseServerRenderer) +-- if nodeType == COMMENT_NODE) +-- local nodeData = (node: any).data +-- if +-- nodeData == SUSPENSE_START_DATA or +-- nodeData == SUSPENSE_FALLBACK_START_DATA or +-- nodeData == SUSPENSE_PENDING_START_DATA +-- ) +-- break +-- end +-- end +-- end +-- end +-- return (node: any) +-- end + +-- exports.getNextHydratableSibling( +-- instance: HydratableInstance, +-- ): nil | HydratableInstance { +-- return getNextHydratable(instance.nextSibling) +-- end + +-- exports.getFirstHydratableChild( +-- parentInstance: Container | Instance, +-- ): nil | HydratableInstance { +-- return getNextHydratable(parentInstance.firstChild) +-- end + +-- exports.hydrateInstance( +-- instance: Instance, +-- type: string, +-- props: Props, +-- rootContainerInstance: Container, +-- hostContext: HostContext, +-- internalInstanceHandle: Object, +-- ): nil | Array { +-- precacheFiberNode(internalInstanceHandle, instance) +-- -- TODO: Possibly defer this until the commit phase where all the events +-- -- get attached. +-- updateFiberProps(instance, props) +-- local parentNamespace: string +-- if __DEV__) +-- local hostContextDev = ((hostContext: any): HostContextDev) +-- parentNamespace = hostContextDev.namespace +-- } else { +-- parentNamespace = ((hostContext: any): HostContextProd) +-- end +-- return diffHydratedProperties( +-- instance, +-- type, +-- props, +-- parentNamespace, +-- rootContainerInstance, +-- ) +-- end + +-- exports.hydrateTextInstance( +-- textInstance: TextInstance, +-- text: string, +-- internalInstanceHandle: Object, +-- ): boolean { +-- precacheFiberNode(internalInstanceHandle, textInstance) +-- return diffHydratedText(textInstance, text) +-- end + +-- exports.hydrateSuspenseInstance( +-- suspenseInstance: SuspenseInstance, +-- internalInstanceHandle: Object, +-- ) +-- precacheFiberNode(internalInstanceHandle, suspenseInstance) +-- end + +-- exports.getNextHydratableInstanceAfterSuspenseInstance( +-- suspenseInstance: SuspenseInstance, +-- ): nil | HydratableInstance { +-- local node = suspenseInstance.nextSibling +-- -- Skip past all nodes within this suspense boundary. +-- -- There might be nested nodes so we need to keep track of how +-- -- deep we are and only break out when we're back on top. +-- local depth = 0 +-- while (node) +-- if node.nodeType == COMMENT_NODE) +-- local data = ((node: any).data: string) +-- if data == SUSPENSE_END_DATA) +-- if depth == 0) +-- return getNextHydratableSibling((node: any)) +-- } else { +-- depth-- +-- end +-- } else if +-- data == SUSPENSE_START_DATA or +-- data == SUSPENSE_FALLBACK_START_DATA or +-- data == SUSPENSE_PENDING_START_DATA +-- ) +-- depth++ +-- end +-- end +-- node = node.nextSibling +-- end +-- -- TODO: Warn, we didn't find the end comment boundary. +-- return nil +-- end + +-- -- Returns the SuspenseInstance if this node is a direct child of a +-- -- SuspenseInstance. I.e. if its previous sibling is a Comment with +-- -- SUSPENSE_x_START_DATA. Otherwise, nil. +-- exports.getParentSuspenseInstance( +-- targetInstance: Node, +-- ): nil | SuspenseInstance { +-- local node = targetInstance.previousSibling +-- -- Skip past all nodes within this suspense boundary. +-- -- There might be nested nodes so we need to keep track of how +-- -- deep we are and only break out when we're back on top. +-- local depth = 0 +-- while (node) +-- if node.nodeType == COMMENT_NODE) +-- local data = ((node: any).data: string) +-- if +-- data == SUSPENSE_START_DATA or +-- data == SUSPENSE_FALLBACK_START_DATA or +-- data == SUSPENSE_PENDING_START_DATA +-- ) +-- if depth == 0) +-- return ((node: any): SuspenseInstance) +-- } else { +-- depth-- +-- end +-- } else if data == SUSPENSE_END_DATA) +-- depth++ +-- end +-- end +-- node = node.previousSibling +-- end +-- return nil +-- end + +-- exports.commitHydratedContainer(container: Container): void { +-- -- Retry if any event replaying was blocked on this. +-- retryIfBlockedOn(container) +-- end + +-- exports.commitHydratedSuspenseInstance( +-- suspenseInstance: SuspenseInstance, +-- ): void { +-- -- Retry if any event replaying was blocked on this. +-- retryIfBlockedOn(suspenseInstance) +-- end + +-- exports.didNotMatchHydratedContainerTextInstance( +-- parentContainer: Container, +-- textInstance: TextInstance, +-- text: string, +-- ) +-- if __DEV__) +-- warnForUnmatchedText(textInstance, text) +-- end +-- end + +-- exports.didNotMatchHydratedTextInstance( +-- parentType: string, +-- parentProps: Props, +-- parentInstance: Instance, +-- textInstance: TextInstance, +-- text: string, +-- ) +-- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) +-- warnForUnmatchedText(textInstance, text) +-- end +-- end + +-- exports.didNotHydrateContainerInstance( +-- parentContainer: Container, +-- instance: HydratableInstance, +-- ) +-- if __DEV__) +-- if instance.nodeType == ELEMENT_NODE) +-- warnForDeletedHydratableElement(parentContainer, (instance: any)) +-- } else if instance.nodeType == COMMENT_NODE) +-- -- TODO: warnForDeletedHydratableSuspenseBoundary +-- } else { +-- warnForDeletedHydratableText(parentContainer, (instance: any)) +-- end +-- end +-- end + +-- exports.didNotHydrateInstance( +-- parentType: string, +-- parentProps: Props, +-- parentInstance: Instance, +-- instance: HydratableInstance, +-- ) +-- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) +-- if instance.nodeType == ELEMENT_NODE) +-- warnForDeletedHydratableElement(parentInstance, (instance: any)) +-- } else if instance.nodeType == COMMENT_NODE) +-- -- TODO: warnForDeletedHydratableSuspenseBoundary +-- } else { +-- warnForDeletedHydratableText(parentInstance, (instance: any)) +-- end +-- end +-- end + +-- exports.didNotFindHydratableContainerInstance( +-- parentContainer: Container, +-- type: string, +-- props: Props, +-- ) +-- if __DEV__) +-- warnForInsertedHydratedElement(parentContainer, type, props) +-- end +-- end + +-- exports.didNotFindHydratableContainerTextInstance( +-- parentContainer: Container, +-- text: string, +-- ) +-- if __DEV__) +-- warnForInsertedHydratedText(parentContainer, text) +-- end +-- end + +-- exports.didNotFindHydratableContainerSuspenseInstance( +-- parentContainer: Container, +-- ) +-- if __DEV__) +-- -- TODO: warnForInsertedHydratedSuspense(parentContainer) +-- end +-- end + +-- exports.didNotFindHydratableInstance( +-- parentType: string, +-- parentProps: Props, +-- parentInstance: Instance, +-- type: string, +-- props: Props, +-- ) +-- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) +-- warnForInsertedHydratedElement(parentInstance, type, props) +-- end +-- end + +-- exports.didNotFindHydratableTextInstance( +-- parentType: string, +-- parentProps: Props, +-- parentInstance: Instance, +-- text: string, +-- ) +-- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) +-- warnForInsertedHydratedText(parentInstance, text) +-- end +-- end + +-- exports.didNotFindHydratableSuspenseInstance( +-- parentType: string, +-- parentProps: Props, +-- parentInstance: Instance, +-- ) +-- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) +-- -- TODO: warnForInsertedHydratedSuspense(parentInstance) +-- end +-- end + +-- exports.getFundamentalComponentInstance( +-- fundamentalInstance: ReactDOMFundamentalComponentInstance, +-- ): Instance { +-- if enableFundamentalAPI) +-- local {currentFiber, impl, props, state} = fundamentalInstance +-- local instance = impl.getInstance(null, props, state) +-- precacheFiberNode(currentFiber, instance) +-- return instance +-- end +-- -- Because of the flag above, this gets around the Flow error +-- return (null: any) +-- end + +-- exports.mountFundamentalComponent( +-- fundamentalInstance: ReactDOMFundamentalComponentInstance, +-- ): void { +-- if enableFundamentalAPI) +-- local {impl, instance, props, state} = fundamentalInstance +-- local onMount = impl.onMount +-- if onMount ~= undefined) +-- onMount(null, instance, props, state) +-- end +-- end +-- end + +-- exports.shouldUpdateFundamentalComponent( +-- fundamentalInstance: ReactDOMFundamentalComponentInstance, +-- ): boolean { +-- if enableFundamentalAPI) +-- local {impl, prevProps, props, state} = fundamentalInstance +-- local shouldUpdate = impl.shouldUpdate +-- if shouldUpdate ~= undefined) +-- return shouldUpdate(null, prevProps, props, state) +-- end +-- end +-- return true +-- end + +-- exports.updateFundamentalComponent( +-- fundamentalInstance: ReactDOMFundamentalComponentInstance, +-- ): void { +-- if enableFundamentalAPI) +-- local {impl, instance, prevProps, props, state} = fundamentalInstance +-- local onUpdate = impl.onUpdate +-- if onUpdate ~= undefined) +-- onUpdate(null, instance, prevProps, props, state) +-- end +-- end +-- end + +-- exports.unmountFundamentalComponent( +-- fundamentalInstance: ReactDOMFundamentalComponentInstance, +-- ): void { +-- if enableFundamentalAPI) +-- local {impl, instance, props, state} = fundamentalInstance +-- local onUnmount = impl.onUnmount +-- if onUnmount ~= undefined) +-- onUnmount(null, instance, props, state) +-- end +-- end +-- end + +-- exports.getInstanceFromNode(node: HTMLElement): nil | Object { +-- return getClosestInstanceFromNode(node) or nil +-- end + +-- local clientId: number = 0 +-- exports.makeClientId(): OpaqueIDType { +-- return 'r:' + (clientId++).toString(36) +-- end + +-- exports.makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType { +-- local id = 'r:' + (clientId++).toString(36) +-- return { +-- toString() +-- warnOnAccessInDEV() +-- return id +-- }, +-- valueOf() +-- warnOnAccessInDEV() +-- return id +-- }, +-- end +-- end + +-- exports.isOpaqueHydratingObject(value: mixed): boolean { +-- return ( +-- value ~= nil and +-- typeof value == 'table’' and +-- value.$$typeof == REACT_OPAQUE_ID_TYPE +-- ) +-- end + +-- exports.makeOpaqueHydratingObject( +-- attemptToReadValue: () => void, +-- ): OpaqueIDType { +-- return { +-- $$typeof: REACT_OPAQUE_ID_TYPE, +-- toString: attemptToReadValue, +-- valueOf: attemptToReadValue, +-- end +-- end + +exports.preparePortalMount = function(portalInstance: Instance): () + -- ROBLOX TODO: Revisit this logic and see if any of it applies + -- if enableEagerRootListeners then + -- listenToAllSupportedEvents(portalInstance) + -- else + -- listenToReactEvent('onMouseEnter', portalInstance) + -- end +end + +-- exports.prepareScopeUpdate( +-- scopeInstance: ReactScopeInstance, +-- internalInstanceHandle: Object, +-- ): void { +-- if enableScopeAPI) +-- precacheFiberNode(internalInstanceHandle, scopeInstance) +-- end +-- end + +-- exports.getInstanceFromScope( +-- scopeInstance: ReactScopeInstance, +-- ): nil | Object { +-- if enableScopeAPI) +-- return getFiberFromScopeInstance(scopeInstance) +-- end +-- return nil +-- end + +-- export local supportsTestSelectors = true + +-- exports.findFiberRoot(node: Instance): nil | FiberRoot { +-- local stack = [node] +-- local index = 0 +-- while (index < stack.length) +-- local current = stack[index++] +-- if isContainerMarkedAsRoot(current)) +-- return ((getInstanceFromNodeDOMTree(current): any): FiberRoot) +-- end +-- stack.push(...current.children) +-- end +-- return nil +-- end + +-- exports.getBoundingRect(node: Instance): BoundingRect { +-- local rect = node.getBoundingClientRect() +-- return { +-- x: rect.left, +-- y: rect.top, +-- width: rect.width, +-- height: rect.height, +-- end +-- end + +-- exports.matchAccessibilityRole(node: Instance, role: string): boolean { +-- if hasRole(node, role)) +-- return true +-- end + +-- return false +-- end + +-- exports.getTextContent(fiber: Fiber): string | nil { +-- switch (fiber.tag) +-- case HostComponent: +-- local textContent = '' +-- local childNodes = fiber.stateNode.childNodes +-- for (local i = 0; i < childNodes.length; i++) +-- local childNode = childNodes[i] +-- if childNode.nodeType == Node.TEXT_NODE) +-- textContent += childNode.textContent +-- end +-- end +-- return textContent +-- case HostText: +-- return fiber.stateNode.textContent +-- end + +-- return nil +-- end + +-- exports.isHiddenSubtree(fiber: Fiber): boolean { +-- return fiber.tag == HostComponent and fiber.memoizedProps.hidden == true +-- end + +-- exports.setFocusIfFocusable(node: Instance): boolean { +-- -- The logic for determining if an element is focusable is kind of complex, +-- -- and since we want to actually change focus anyway- we can just skip it. +-- -- Instead we'll just listen for a "focus" event to verify that focus was set. +-- -- +-- -- We could compare the node to document.activeElement after focus, +-- -- but this would not handle the case where application code managed focus to automatically blur. +-- local didFocus = false +-- local handleFocus = () => { +-- didFocus = true +-- end + +-- local element = ((node: any): HTMLElement) +-- try { +-- element.addEventListener('focus', handleFocus) +-- (element.focus or HTMLElement.prototype.focus).call(element) +-- } finally { +-- element.removeEventListener('focus', handleFocus) +-- end + +-- return didFocus +-- end + +-- type RectRatio = { +-- ratio: number, +-- rect: BoundingRect, +-- end + +-- exports.setupIntersectionObserver( +-- targets: Array, +-- callback: ObserveVisibleRectsCallback, +-- options?: IntersectionObserverOptions, +-- ): {| +-- disconnect: () => void, +-- observe: (instance: Instance) => void, +-- unobserve: (instance: Instance) => void, +-- |} { +-- local rectRatioCache: Map = new Map() +-- targets.forEach(target => { +-- rectRatioCache.set(target, { +-- rect: getBoundingRect(target), +-- ratio: 0, +-- }) +-- }) + +-- local handleIntersection = (entries: Array) => { +-- entries.forEach(entry => { +-- local {boundingClientRect, intersectionRatio, target} = entry +-- rectRatioCache.set(target, { +-- rect: { +-- x: boundingClientRect.left, +-- y: boundingClientRect.top, +-- width: boundingClientRect.width, +-- height: boundingClientRect.height, +-- }, +-- ratio: intersectionRatio, +-- }) +-- }) + +-- callback(Array.from(rectRatioCache.values())) +-- end + +-- local observer = new IntersectionObserver(handleIntersection, options) +-- targets.forEach(target => { +-- observer.observe((target: any)) +-- }) + +-- return { +-- disconnect: () => observer.disconnect(), +-- observe: target => { +-- rectRatioCache.set(target, { +-- rect: getBoundingRect(target), +-- ratio: 0, +-- }) +-- observer.observe((target: any)) +-- }, +-- unobserve: target => { +-- rectRatioCache.delete(target) +-- observer.unobserve((target: any)) +-- }, +-- end +-- end + +return exports diff --git a/samples/Luau/Replicator.luau b/samples/Luau/Replicator.luau new file mode 100644 index 0000000000..69d3012104 --- /dev/null +++ b/samples/Luau/Replicator.luau @@ -0,0 +1,546 @@ +--// Authored by @ColRealPro (https://github.com/colrealpro) +--// Fetched from (https://github.com/ColRealPro/SimplyReplicate/blob/main/lib/Replicator.luau) +--// Licensed under the MIT License (https://github.com/ColRealPro/SimplyReplicate/blob/main/LICENSE) + +--!strict + +-- // Services +local Players = game:GetService("Players") +local RunService = game:GetService("RunService") + +-- // Modules +local NetworkManager = require(script.Parent.NetworkManager) +local console = require(script.Parent.console) +local Maid = require(script.Parent.Maid) +local Signal = require(script.Parent.Signal) + +-- console.WriteEnv() +local print = console.log + +-- // Internal Types +type dataType = T +type data = { [string]: dataType } +type DataStructure = typeof(setmetatable({} :: data, {})) + +type ReplicatorImpl = { + __index: ReplicatorImpl, + __tostring: (self: Replicator) -> string, + + new: (identifier: string | Instance, data: data) -> Replicator, + changeStates: (self: Replicator, changedStates: data, players: ({ Player } | Player)?) -> (), + syncPlayer: (self: Replicator, player: Player) -> (), + _prepareReplication: (self: Replicator) -> (), + _setState: (self: Replicator, state: string, value: any, players: { Player }?) -> (), + _createDataStructure: (self: Replicator, data: data) -> DataStructure, + _fetchFromServer: (self: Replicator) -> (), + _recieveUpdate: (self: Replicator, data: { syncData: { Remove: { string }?, Modify: { { Index: string, NewValue: any } }? }?, updateData: { [string]: any } }) -> (), + get: (self: Replicator, index: string?) -> { [string]: any } | any, + getForPlayer: (self: Replicator, player: Player, index: string?) -> { [string]: any } | any, + Destroy: (self: Replicator) -> () +} + +type Replicator = typeof(setmetatable({} :: { + identifier: string | Instance, + _maid: typeof(Maid), + _networkManager: NetworkManager.NetworkManager, + _isPlayer: boolean, + _context: "Server" | "Client" | string, + _deferringReplication: boolean, + _replicationNeeded: { Updates: { { Index: string, NewValue: any, Players: { Player }?, updateOrderTime: number } }, Syncs: { { Remove: { string }, Modify: { { Index: string, NewValue: any } }, Player: Player, SyncOrderTime: number } } }, + -- _dataStructure: DataStructure, + _localDataCopy: DataStructure, + _userStateCopies: { [Player]: DataStructure }, + _originalDataStructure: { [string]: any }, + StateChanged: Signal.Signal +}, {} :: ReplicatorImpl)) + +--[=[ + @class Replicator + + This is the main class of SimplyReplicate + Replicators are used to replicate data from the server to the client with ease + + Here is an example implementation of a replicator: + + **Server:** + ```lua + local Replicator = require(path.to.module) + + local GameStateReplicator = Replicator.new("GameState", { + Status = "Waiting", + RoundStarted = false, + }) + + task.wait(3) + + GameStateReplicator:changeStates({ + Status = "Game starts soon", + }) + + task.wait(3) + + GameStateReplicator:changeStates({ + Status = "Game started", + RoundStarted = true, + }) + ``` + + **Client:** + ```lua + local Replicator = require(path.to.module) + + local GameStateReplicator = Replicator.new("GameState") + + GameStateReplicator.StateChanged:Connect(function(state, value) + print("State changed", state, value) + end) + ``` +]=] +local Replicator: ReplicatorImpl = {} :: ReplicatorImpl +Replicator.__index = Replicator +Replicator.__tostring = function(self) + return `Replicator({tostring(self.identifier)})` +end + +--[=[ + Constructs a new replicator + + @yields + @function new + @within Replicator + @param identifier string | Instance + @param data { [string]: any } -- The data that will be replicated to clients + @return Replicator + + :::warning + When creating a replicator on the client, this function will yield until the initial data has been fetched from the server + ::: + + :::info + Note that if you are using strict luau, you will want to specify a type for the data parameter, + if you let Luau infer the type, when you go to change the state, it will want you to include every state as it wont have an option to be nil. + ::: +]=] +function Replicator.new(identifier: string | Instance, data: T?) + local self = setmetatable({}, Replicator) :: Replicator + + self.identifier = identifier + self._maid = Maid.new() + self._networkManager = NetworkManager.new(identifier) + self._isPlayer = typeof(identifier) == "Instance" and identifier:IsA("Player") + self._context = RunService:IsServer() and "Server" or "Client" + self._userStateCopies = {} + + -- // Signals + self.StateChanged = Signal.new() + + if self._context == "Server" then + if not data then + error("Data must be provided when creating a replicator on the server.", 2) + end + + if typeof(data) ~= "table" then + error("Data must be a table.", 2) + end + + self._replicationNeeded = { + Updates = {}, + Syncs = {} + } + self._deferringReplication = false + self._originalDataStructure = table.freeze(table.clone(data)) + self._localDataCopy = self:_createDataStructure(data) + -- self._dataStructure = self:_createDataStructure(data) + + self._networkManager:listenToFetch("FETCH_REPLICATOR", function(player) + if self._isPlayer and player ~= identifier then + return + end + + return self:get() + end) + else + self:_fetchFromServer() + self._networkManager:listen("UPDATE_REPLICATOR", function(data) + if data.identifier == self.identifier then + print("Recieved update from server") + self:_recieveUpdate(data :: { syncData: { Remove: { string }?, Modify: { { Index: string, NewValue: any } }? }?, updateData: { [string]: any } }) + end + end) + end + + return self +end + +--[=[ + Changes states in the replicator and replicate whatever changes to the clients + + @param changedStates { [string]: any? } -- The states that have been changed and their new values + @error "Invalid state" -- Thrown when the state does not exist in the data structure +]=] +function Replicator:changeStates(changedStates: data, players: ({ Player } | Player)?) + if self._context == "Client" then + error("This function can only be called on the server", 2) + end + + local playersTbl = type(players) == "table" and players or { players :: Player } + + for state, value in changedStates do + self:_setState(state, value, players and playersTbl) + end +end + +--[=[ + Syncs a player's state with the server's state + + @param player Player +]=] +function Replicator:syncPlayer(player: Player) + if self._context == "Client" then + error("This function can only be called on the server", 2) + end + + local userStateCopy = self._userStateCopies[player] + + if not userStateCopy then + print(`{player.Name} is already in sync with the server`) + return + end + + local remove = {} + local modify = {} + + for state, value in next, userStateCopy do + if not rawget(self._localDataCopy :: any, state) then + table.insert(remove, state) + elseif self._localDataCopy[state] ~= value then + -- FIXME: If a value is modified in the same frame before a sync, it will be put in the sync table even though it has not replication + -- this isn't a huge issue but it leads to wasted data being sent to the client as the original value will be dropped during replication + -- meaning that replicating the value as part of a sync is now useless + table.insert(modify, { + Index = state, + NewValue = self._localDataCopy[state] + }) + end + end + + table.insert(self._replicationNeeded.Syncs, { + Remove = remove, + Modify = modify, + Player = player, + + SyncOrderTime = os.clock() + }) + + self:_prepareReplication() +end + +function Replicator:_prepareReplication() + print("Preparing replication") + self._deferringReplication = true + + task.defer(function() + self._deferringReplication = false + + local replicationNeeded = self._replicationNeeded + self._replicationNeeded = { + Updates = {}, + Syncs = {} + } + + local replicationData = { + ["$all"] = { + updateData = {}, + } + } + + type PlayerReplicationData = { + syncData: { Remove: { string }?, Modify: { { Index: string, NewValue: any } }? }, + updateData: { [string]: any }, + internalData: { syncOrderTime: number? } + } + + local playerReplicationDataTemplate = { + syncData = {}, + updateData = {}, + internalData = {} + } + + -- Evaluate syncs first to make sure that we have enough data to keep the replication in order + -- and have the client in sync with the server + for _, v in replicationNeeded.Syncs do + local player = v.Player + local remove = v.Remove + local modify = v.Modify + local syncOrderTime = v.SyncOrderTime + + if not replicationData[player] then + replicationData[player] = table.clone(playerReplicationDataTemplate) :: PlayerReplicationData + end + + local previousSyncOrderTime = replicationData[player].internalData.syncOrderTime + if previousSyncOrderTime and syncOrderTime < previousSyncOrderTime then + print(`Dropping sync for player {player.Name} as a newer sync has already been evaluated`) + continue + end + + replicationData[player].syncData.Remove = remove + replicationData[player].syncData.Modify = modify + + replicationData[player].internalData.syncOrderTime = syncOrderTime + end + + for _, v in replicationNeeded.Updates do + local players = v.Players + local index = v.Index + local newValue = v.NewValue + + if players then + for _, player in players do + if not replicationData[player] then + replicationData[player] = table.clone(playerReplicationDataTemplate) :: PlayerReplicationData + end + + local syncOrderTime = replicationData[player].internalData.syncOrderTime + if syncOrderTime and v.updateOrderTime < syncOrderTime then + print(`Dropping update for state {v.Index} for player {player.Name} as a sync has already been evaluated after this update`) + continue + end + + replicationData[player].updateData[index] = newValue + end + else + replicationData["$all"].updateData[index] = newValue + end + end + + print("About to replicate data, sending out:", replicationData, "created from:", replicationNeeded) + + self._networkManager:send("UPDATE_REPLICATOR", { + identifier = self.identifier, + updateData = replicationData["$all"].updateData, + }) + + for player: Player | string, data in replicationData do + -- if player == "$all" then + -- self._networkManager:send("UPDATE_REPLICATOR", { + -- identifier = self.identifier, + -- updateData = data.updateData, + -- }) + -- continue + -- end + + if player == "$all" then + continue + end + + self._networkManager:send("UPDATE_REPLICATOR", { + identifier = self.identifier, + syncData = data.syncData, + updateData = data.updateData, + }, {player :: Player}) + end + end) +end + +function Replicator:_setState(state: string, value: any, players: { Player }?) + -- self._dataStructure[state] = value + -- self._replicationNeeded[state] = value + + table.insert(self._replicationNeeded.Updates, { + Index = state, + NewValue = value, + Players = players, + updateOrderTime = os.clock() + }) + + if not players then + self._localDataCopy[state] = value + players = Players:GetPlayers() + + task.defer(function() + self.StateChanged:Fire(state, value) + end) + end + + for _, player in players :: { Player } do + if not self._userStateCopies[player] then + self._userStateCopies[player] = self:_createDataStructure(self._originalDataStructure) + end + + self._userStateCopies[player][state] = value + end + + if not self._deferringReplication then + self:_prepareReplication() + end + + print(`Successfully changed state {state} to {value}`) +end + +function Replicator:_createDataStructure(data: { [string]: any }) : DataStructure + local dataStructure = setmetatable(table.clone(data), { + __index = function(self, key) + error(string.format("Invalid state %s, does not exist in the data structure.", + tostring(key)), 4) + end, + __newindex = function(self, key, value) + error(string.format("Invalid state %s, does not exist in the data structure.", + tostring(key)), 4) + end + }) + + return dataStructure +end + +function Replicator:_fetchFromServer() + local data = self._networkManager:fetch("FETCH_REPLICATOR", { + identifier = self.identifier, + }) :: { [string]: any } + + self._localDataCopy = self:_createDataStructure(data) + + print("Successfully fetched initial data from server") +end + +function Replicator:_recieveUpdate(data: { syncData: { Remove: { string }?, Modify: { { Index: string, NewValue: any } }? }?, updateData: { [string]: any } }) + -- for state, value in data do + -- self._localDataCopy[state] = value + + -- task.defer(function() + -- self.StateChanged:Fire(state, value) + -- end) + -- end + + -- print("Handled update from server") + + local syncData = data.syncData + local updateData = data.updateData + + if syncData then + local remove = syncData.Remove + local modify = syncData.Modify + + if remove then + for _, state in remove do + self._localDataCopy[state] = nil + + task.defer(function() + if self._localDataCopy[state] then + return + end + + self.StateChanged:Fire(state, nil) + end) + end + end + + if modify then + for _, v in modify do + local index = v.Index + local newValue = v.NewValue + + if self._localDataCopy[index] == newValue then + print(`Droping update for state {index} as the value is already the same`) + continue + end + + self._localDataCopy[index] = newValue + + task.defer(function() + if self._localDataCopy[index] ~= newValue then + return + end + + self.StateChanged:Fire(index, newValue) + end) + end + end + end + + for state, value in updateData do + self._localDataCopy[state] = value + + task.defer(function() + if self._localDataCopy[state] ~= value then + return + end + + self.StateChanged:Fire(state, value) + end) + end +end + +--[=[ + Returns the current state stored in the replicator + + @error "Invalid state" -- Thrown when the state does not exist in the data structure + @param index string? -- If provided, returns the state given, otherwise returns all states +]=] +function Replicator:get(index: string?): { [string]: any } | any + if index then + return self._localDataCopy[index] + end + + local dataStructure = self._localDataCopy + local data = {} + + -- Manually clone the table to remove the metatable + for state, value in next, dataStructure do + data[state] = value + end + + return data +end + +--[=[ + Returns the current state stored in the replicator for a specific player + + @error "Invalid state" -- Thrown when the state does not exist in the data structure + @param player Player + @param index string? -- If provided, returns the state given, otherwise returns all states +]=] +function Replicator:getForPlayer(player: Player, index: string?): { [string]: any } | any + if self._context == "Client" then + error("This function can only be called on the server", 2) + end + + if not self._userStateCopies[player] then + return self:get(index) + end + + if index then + return self._userStateCopies[player][index] + end + + local dataStructure = self._userStateCopies[player] + local data = {} + + -- Manually clone the table to remove the metatable + for state, value in next, dataStructure do + data[state] = value + end + + return data +end + +--[=[ + This function should be called when you are done using the replicator +]=] +function Replicator:Destroy() + self._networkManager:Destroy() + self._maid:DoCleaning() + + table.clear(self :: any) + setmetatable(self :: any, { + __metatable = "The metatable is locked", + __index = function() + error("This replicator has been destroyed", 2) + end, + __newindex = function() + error("This replicator has been destroyed", 2) + end + }) +end + +return Replicator diff --git a/samples/Luau/createProcessor.luau b/samples/Luau/createProcessor.luau deleted file mode 100644 index d55aba076e..0000000000 --- a/samples/Luau/createProcessor.luau +++ /dev/null @@ -1,22 +0,0 @@ ---// Authored by @sinlerdev (https://github.com/sinlerdev) ---// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/createProcessor.luau) ---// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) - ---!strict -local function createProcessor(valueChecker: (a: any, b: any) -> boolean, cleaner: (taskItem: any) -> ()) - return function(old: T, new: T, isDestroying: boolean) - if isDestroying then - cleaner(old) - return false - end - - if valueChecker(old, new) then - return false - end - - cleaner(old) - return true - end -end - -return createProcessor diff --git a/samples/Luau/createSignal.luau b/samples/Luau/createSignal.luau deleted file mode 100644 index 5ffd461fff..0000000000 --- a/samples/Luau/createSignal.luau +++ /dev/null @@ -1,37 +0,0 @@ ---// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) ---// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/createSignal.luau) ---// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) - ---!strict - -local function createSignal() - local listeners = {} - local signal = {} - - function signal:Connect(listener) - listeners[listener] = true - - local connection = { - Connected = true, - } - - function connection.Disconnect() - connection.Connected = false - listeners[listener] = nil - end - connection.disconnect = connection.Disconnect - - return connection - end - signal.connect = signal.Connect - - local function fire(...) - for listener, _ in pairs(listeners) do - spawn(listener, ...) - end - end - - return signal, fire -end - -return createSignal diff --git a/samples/Luau/equals.luau b/samples/Luau/equals.luau deleted file mode 100644 index 6b259e0359..0000000000 --- a/samples/Luau/equals.luau +++ /dev/null @@ -1,17 +0,0 @@ ---// Authored by @sinlerdev (https://github.com/sinlerdev) ---// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/equals.luau) ---// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) - ---!strict - -local function equals(a: any, b: any): boolean - -- INFO: Vinum doesn't officially support immutability currently- so, we just assume - -- that every new table entry is not equal. See issue 26 - if type(a) == "table" then - return false - end - - return a == b -end - -return equals diff --git a/samples/Luau/isEmpty.luau b/samples/Luau/isEmpty.luau deleted file mode 100644 index c90e654801..0000000000 --- a/samples/Luau/isEmpty.luau +++ /dev/null @@ -1,21 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/isEmpty.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---!strict ---[=[ - Returns true if the collection is empty. - - ```lua - Freeze.isEmpty({}) - -- true - ``` - - @within Freeze - @function isEmpty - @return boolean -]=] - -return function(collection) - return next(collection) == nil -end diff --git a/samples/Luau/none.luau b/samples/Luau/none.luau deleted file mode 100644 index 118a75d8ce..0000000000 --- a/samples/Luau/none.luau +++ /dev/null @@ -1,21 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/None.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---[=[ - @prop None None - @within Freeze - - Since lua tables cannot distinguish between values not being present and a value of nil, - `Freeze.None` exists to represent values that should be interpreted as `nil`. - - This is useful when removing values with functions such as [`Freeze.Dictionary.merge`](../api/Dictionary#merge). -]=] - -local None = newproxy(true) - -getmetatable(None).__tostring = function() - return "Freeze.None" -end - -return None diff --git a/samples/Luau/pathToRegexp.luau b/samples/Luau/pathToRegexp.luau new file mode 100644 index 0000000000..37b528c051 --- /dev/null +++ b/samples/Luau/pathToRegexp.luau @@ -0,0 +1,854 @@ +--// Authored by @Roblox (https://github.com/Roblox) +--// Fetched from (https://github.com/Roblox/roact-navigation/blob/master/src/routers/pathToRegexp.lua) +--// Licensed under the MIT License (https://github.com/Roblox/roact-navigation/blob/master/LICENSE) + +-- upstream: https://github.com/pillarjs/path-to-regexp/blob/feddb3d3391d843f21ea9cde195f066149dba0be/src/index.ts +--[[ +The MIT License (MIT) + +Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]] + +--!strict +--!nolint LocalShadow +local root = script.Parent.Parent +local Packages = root.Parent +local LuauPolyfill = require(Packages.LuauPolyfill) +local Array = LuauPolyfill.Array +local RegExp = require(Packages.RegExp) + +type Record = { [T]: U } + +local function TypeError(message) + return message +end + +local exports = {} + +-- Roblox deviation: predeclare function variables +local escapeString +local flags + +--[[ + * Tokenizer results. + ]] +type LexToken = { + type: string, + -- | "OPEN" + -- | "CLOSE" + -- | "PATTERN" + -- | "NAME" + -- | "CHAR" + -- | "ESCAPED_CHAR" + -- | "MODIFIER" + -- | "END", + index: number, + value: string, +} + +--[[ + * Tokenize input string. + ]] +local function lexer(str: string): { LexToken } + local tokens: { LexToken } = {} + local i = 1 + + -- Roblox deviation: the original JavaScript contains a lot of `i++`, which + -- does not translate really well to Luau. This function mimic the operation + -- while still being an expression (because it's a function call) + local function preIncrement_i(value) + i += 1 + return value + end + -- Roblox deviation: use this function to translate `str[n]` directly + local function getChar(n) + return string.sub(str, n, n) + end + + local strLength = string.len(str) + + while i <= strLength do + local char = string.sub(str, i, i) + + if char == "*" or char == "+" or char == "?" then + table.insert(tokens, { + type = "MODIFIER", + index = i, + value = getChar(preIncrement_i(i)), + }) + continue + end + + if char == "\\" then + table.insert(tokens, { + type = "ESCAPED_CHAR", + index = preIncrement_i(i), + value = getChar(preIncrement_i(i)), + }) + continue + end + + if char == "{" then + table.insert(tokens, { + type = "OPEN", + index = i, + value = getChar(preIncrement_i(i)), + }) + continue + end + + if char == "}" then + table.insert(tokens, { + type = "CLOSE", + index = i, + value = getChar(preIncrement_i(i)), + }) + continue + end + + if char == ":" then + local name = "" + local j = i + 1 + + -- Roblox deviation: the original JavaScript contains a lot of `j++`, which + -- does not translate really well to Luau. This function mimic the operation + -- while still being an expression (because it's a function call) + local function preIncrement_j(value) + j += 1 + return value + end + + while j <= strLength do + local code = string.byte(str, j) + + if + -- // `0-9` + (code >= 48 and code <= 57) + -- // `A-Z` + or (code >= 65 and code <= 90) + -- // `a-z` + or (code >= 97 and code <= 122) + -- // `_` + or code == 95 + then + name = name .. getChar(preIncrement_j(j)) + continue + end + + break + end + + if name == "" then + error(TypeError(("Missing parameter name at %d"):format(i))) + end + + table.insert(tokens, { + type = "NAME", + index = i, + value = name, + }) + i = j + continue + end + + if char == "(" then + local count = 1 + local pattern = "" + local j = i + 1 + + if getChar(j) == "?" then + error(TypeError(('Pattern cannot start with "?" at %d'):format(j))) + end + + -- Roblox deviation: the original JavaScript contains a lot of `j++`, which + -- does not translate really well to Luau. This function mimic the operation + -- while still being an expression (because it's a function call) + local function preIncrement_j(value) + j += 1 + return value + end + + while j <= strLength do + if getChar(j) == "\\" then + pattern = pattern .. (getChar(preIncrement_j(j)) .. getChar(preIncrement_j(j))) + end + + if getChar(j) == ")" then + count = count - 1 + if count == 0 then + j = j + 1 + break + end + elseif getChar(j) == "(" then + count = count + 1 + + if getChar(j + 1) ~= "?" then + error(TypeError(("Capturing groups are not allowed at %d"):format(j))) + end + end + + pattern = pattern .. getChar(preIncrement_j(j)) + end + + if count ~= 0 then + error(TypeError(("Unbalanced pattern at %d"):format(i))) + end + if pattern == "" then + error(TypeError(("Missing pattern at %d"):format(i))) + end + + table.insert(tokens, { + type = "PATTERN", + index = i, + value = pattern, + }) + i = j + continue + end + + table.insert(tokens, { + type = "CHAR", + index = i, + value = getChar(preIncrement_i(i)), + }) + end + + table.insert(tokens, { + type = "END", + index = i, + value = "", + }) + + return tokens +end + +export type ParseOptions = { + --[[ + * Set the default delimiter for repeat parameters. (default: `'/'`) + ]] + delimiter: string?, + --[[ + * List of characters to automatically consider prefixes when parsing. + ]] + prefixes: string?, +} + +--[[ + * Parse a string for the raw tokens. + ]] +function exports.parse(str: string, optionalOptions: ParseOptions?): { Token } + local options = optionalOptions or {} + local tokens = lexer(str) + + local prefixes = "./" + if options.prefixes ~= nil and options.prefixes ~= "" then + prefixes = options.prefixes + end + local defaultPattern = string.format("[^%s]+?", escapeString(options.delimiter or "/#?")) + local result: { Token } = {} + local key = 0 + local i = 1 + local path = "" + + -- Roblox deviation: the original JavaScript contains a lot of `i++`, which + -- does not translate really well to Luau. This function mimic the operation + -- while still being an expression (because it's a function call) + local function preIncrement_i(value) + i += 1 + return value + end + + -- Roblox deviation: the original JavaScript contains a lot of `key++`, which + -- does not translate really well to Luau. This function mimic the operation + -- while still being an expression (because it's a function call) + local function preIncrement_key(value) + key += 1 + return value + end + + local function tryConsume(type_): string? + if i <= #tokens and tokens[i].type == type_ then + local v = tokens[preIncrement_i(i)].value + return v + end + return nil + end + + local function mustConsume(type_): string + local value = tryConsume(type_) + if value ~= nil then + return value + end + local token = tokens[i] + if token == nil then + error(TypeError(("Expected token %s, got nil"):format(type_))) + end + local nextType = token.type + local index = token.index + error(TypeError(("Unexpected %s at %d, expected %s"):format(nextType, index, type_))) + end + + local function consumeText(): string + local result = "" + local value: string? = tryConsume("CHAR") or tryConsume("ESCAPED_CHAR") + while value and value ~= "" do + result ..= value + value = tryConsume("CHAR") or tryConsume("ESCAPED_CHAR") + end + return result + end + + while i <= #tokens do + local char = tryConsume("CHAR") + local name = tryConsume("NAME") + local pattern = tryConsume("PATTERN") + + if (name and name ~= "") or (pattern and pattern ~= "") then + local prefix = char or "" + + if string.find(prefixes, prefix) == nil then + path = path .. prefix + prefix = "" + end + + if path ~= nil and path ~= "" then + table.insert(result, path) + path = "" + end + + local resultName = name + if name == nil or name == "" then + resultName = preIncrement_key(key) + end + local resultPattern = pattern + if pattern == nil or pattern == "" then + resultPattern = defaultPattern + end + + table.insert(result, { + name = resultName, + prefix = prefix, + suffix = "", + pattern = resultPattern, + modifier = tryConsume("MODIFIER") or "", + }) + continue + end + + local value = char or tryConsume("ESCAPED_CHAR") + + if value and value ~= "" then + path ..= value + continue + end + + if path and path ~= "" then + table.insert(result, path) + path = "" + end + + local open = tryConsume("OPEN") + + if open and open ~= "" then + local prefix = consumeText() + local name = tryConsume("NAME") or "" + local pattern = tryConsume("PATTERN") or "" + local suffix = consumeText() + + mustConsume("CLOSE") + + if name == "" and pattern ~= "" then + name = preIncrement_key(key) + end + -- Roblox deviation: we need to check if name is not 0, because 0 is false in JavaScript, and + -- it could be number because it could be assigned to the key value + if (name ~= "" and name ~= 0) and (pattern == nil or pattern == "") then + pattern = defaultPattern + end + + table.insert(result, { + name = name, + pattern = pattern, + prefix = prefix, + suffix = suffix, + modifier = tryConsume("MODIFIER") or "", + }) + continue + end + + mustConsume("END") + end + + return result +end + +export type TokensToFunctionOptions = { + --[[ + * When `true` the regexp will be case sensitive. (default: `false`) + ]] + sensitive: boolean?, + --[[ + * Function for encoding input strings for output. + ]] + encode: nil | (string, Key) -> string, + --[[ + * When `false` the function can produce an invalid (unmatched) path. (default: `true`) + ]] + validate: boolean?, +} + +--[[ + * Compile a string to a template function for the path. + ]] +function exports.compile(str: string, options: (ParseOptions & TokensToFunctionOptions)?) + return exports.tokensToFunction(exports.parse(str, options), options) +end + +export type PathFunction

= (P?) -> string + +--[[ + * Expose a method for transforming tokens into the path function. + ]] +function exports.tokensToFunction(tokens: { Token }, optionalOptions: TokensToFunctionOptions?) + if optionalOptions == nil then + optionalOptions = {} + end + local options = optionalOptions :: TokensToFunctionOptions + local reFlags = flags(options) + local encode = options.encode or function(x: string): string + return x + end + local validate = options.validate + if validate == nil then + validate = true + end + + -- Compile all the tokens into regexps. + local matches = Array.map(tokens, function(token) + if type(token) == "table" then + return RegExp(("^(?:%s)$"):format(token.pattern), reFlags) + end + return nil + end) + + return function(data: Record?) + local path = "" + + for i, token in tokens do + if type(token) == "string" then + path ..= token + continue + end + + -- Roblox deviation: in JavaScript, indexing an object with a number will coerce the number + -- value into a string + local value = if data then data[tostring(token.name)] else nil + local optional = token.modifier == "?" or token.modifier == "*" + local repeat_ = token.modifier == "*" or token.modifier == "+" + + if Array.isArray(value) then + if not repeat_ then + error(TypeError(('Expected "%s" to not repeat, but got an array'):format(token.name))) + end + + if #value == 0 then + if optional then + continue + end + + error(TypeError(('Expected "%s" to not be empty'):format(token.name))) + end + + for _, element in value do + local segment = encode(element, token) + + if validate and not matches[i]:test(segment) then + error( + TypeError( + ('Expected all "%s" to match "%s", but got "%s"'):format( + token.name, + token.pattern, + segment + ) + ) + ) + end + + path ..= token.prefix .. segment .. token.suffix + end + + continue + end + + local valueType = type(value) + if valueType == "string" or valueType == "number" then + local segment = encode(tostring(value), token) + + if validate and not matches[i]:test(segment) then + error( + TypeError( + ('Expected "%s" to match "%s", but got "%s"'):format(token.name, token.pattern, segment) + ) + ) + end + + path ..= token.prefix .. segment .. token.suffix + continue + end + + if optional then + continue + end + + local typeOfMessage = if repeat_ then "an array" else "a string" + error(TypeError(('Expected "%s" to be %s'):format(tostring(token.name), typeOfMessage))) + end + + return path + end +end + +export type RegexpToFunctionOptions = { + --[[ + * Function for decoding strings for params. + ]] + decode: nil | (string, Key) -> string, +} + +--[[ + * A match result contains data about the path match. + ]] +export type MatchResult

= { + path: string, + index: number, + params: P, +} + +--[[ + * A match is either `false` (no match) or a match result. + ]] +-- export type Match

= false | MatchResult

+export type Match

= boolean | MatchResult

+ +--[[ + * The match function takes a string and returns whether it matched the path. + ]] +export type MatchFunction

= (string) -> Match

+ +--[[ + * Create path match function from `path-to-regexp` spec. + ]] +function exports.match(str, options) + local keys: { Key } = {} + local re = exports.pathToRegexp(str, keys, options) + return exports.regexpToFunction(re, keys, options) +end + +--[[ + * Create a path match function from `path-to-regexp` output. + ]] +function exports.regexpToFunction(re: any, keys: { Key }, options: RegexpToFunctionOptions) + if options == nil then + options = {} + end + local decode = options.decode or function(x: string) + return x + end + + return function(pathname: string) + local matches = re:exec(pathname) + if not matches then + return false + end + + local path = matches[1] + local index = matches.index or 0 + local params = {} + + -- Roblox deviation: start the iteration from 2 instead of 1 because the individual + -- matches start at 2 in our polyfill version of RegExp objects. + for i = 2, matches.n do + if matches[i] == nil then + continue + end + + -- Roblox comment: keep the `-1` because our matches array start from 1, + -- so the loop starts at index 2 (index 1 is the full matched string) + local key = keys[i - 1] + if key.modifier == "*" or key.modifier == "+" then + params[key.name] = Array.map(string.split(matches[i], key.prefix .. key.suffix), function(value) + return decode(value, key) + end) + else + params[key.name] = decode(matches[i], key) + end + end + + return { + path = path, + index = index, + params = params, + } + end +end + +--[[ + * Escape a regular expression string. + ]] +function escapeString(str: string) + return string.gsub(str, "[%.%+%*%?=%^!:${}%(%)%[%]|/\\]", function(match) + return "\\" .. match + end) +end + +--[[ + * Get the flags for a regexp from the options. + ]] +function flags(options: { sensitive: boolean? }?) + if options and options.sensitive then + return "" + else + return "i" + end +end + +--[[ + * Metadata about a key. + ]] +export type Key = { + name: string | number, + prefix: string, + suffix: string, + pattern: string, + modifier: string, +} + +--[[ + * A token is a string (nothing special) or key metadata (capture group). + ]] +export type Token = string | Key + +-- Roblox deviation: this functionality is not required so it has been omitted +--[[ + * Pull out keys from a regexp. + ]] +-- local function regexpToRegexp(path: string, keys: { Key }?): string +-- if not keys then +-- return path +-- end + +-- local groupsRegex = "%(" .. "(%?<(.*)>)?" .. "[^%?]" + +-- local index = 0 +-- local matchGenerator = path.source:gmatch(groupsRegex) +-- -- local execResult = groupsRegex.exec(path.source) +-- local execResult = matchGenerator() + +-- while execResult do +-- error('got match -> ' .. execResult .. " for path = " .. path) +-- local name = execResult[1] +-- if name then +-- name = index +-- index += 1 +-- end +-- table.insert(keys, { +-- -- // Use parenthesized substring match if available, index otherwise +-- name = name, +-- prefix = "", +-- suffix = "", +-- modifier = "", +-- pattern = "", +-- }) + +-- -- execResult = groupsRegex.exec(path.source) +-- execResult = matchGenerator() +-- end + +-- return path +-- end + +--[[ + * Transform an array into a regexp. + ]] +local function arrayToRegexp( + paths: { string }, + keys: { Key }?, + options: (TokensToRegexpOptions & ParseOptions)? +): string + local parts = Array.map(paths, function(path) + return exports.pathToRegexp(path, keys, options).source + end) + + return RegExp(("(?:%s)"):format(table.concat(parts, "|")), flags(options)) +end + +--[[ + * Create a path regexp from string input. + ]] +local function stringToRegexp(path, keys, options) + return exports.tokensToRegexp(exports.parse(path, options), keys, options) +end + +export type TokensToRegexpOptions = { + --[[ + * When `true` the regexp will be case sensitive. (default: `false`) + ]] + sensitive: boolean?, + --[[ + * When `true` the regexp won't allow an optional trailing delimiter to match. (default: `false`) + ]] + strict: boolean?, + --[[ + * When `true` the regexp will match to the end of the string. (default: `true`) + ]] + end_: boolean?, + --[[ + * When `true` the regexp will match from the beginning of the string. (default: `true`) + ]] + start: boolean?, + --[[ + * Sets the final character for non-ending optimistic matches. (default: `/`) + ]] + delimiter: string?, + --[[ + * List of characters that can also be "end" characters. + ]] + endsWith: string?, + --[[ + * Encode path tokens for use in the `RegExp`. + ]] + encode: nil | (string) -> string, +} + +--[[ + * Expose a function for taking tokens and returning a RegExp. + ]] +function exports.tokensToRegexp(tokens: { Token }, keys: { Key }?, optionalOptions: TokensToRegexpOptions?) + local options = {} + if optionalOptions ~= nil then + options = optionalOptions + end + local strict = options.strict + if strict == nil then + strict = false + end + local start = options.start + if start == nil then + start = true + end + local end_ = options.end_ + if end_ == nil then + end_ = true + end + local encode = options.encode or function(x: string) + return x + end + -- Roblox deviation: our Lua regex implementation does not support empty character class + local endsWith = if options.endsWith then ("[%s]|$"):format(escapeString(options.endsWith or "")) else "$" + local delimiter = ("[%s]"):format(escapeString(options.delimiter or "/#?")) + local route = if start then "^" else "" + + -- // Iterate over the tokens and create our regexp string. + for _, token in tokens do + if type(token) == "string" then + route ..= escapeString(encode(token)) + else + local prefix = escapeString(encode(token.prefix)) + local suffix = escapeString(encode(token.suffix)) + + if token.pattern and token.pattern ~= "" then + if keys then + table.insert(keys, token) + end + + if (prefix and prefix ~= "") or (suffix and suffix ~= "") then + if token.modifier == "+" or token.modifier == "*" then + local mod = if token.modifier == "*" then "?" else "" + route ..= ("(?:%s((?:%s)(?:%s%s(?:%s))*)%s)%s"):format( + prefix, + token.pattern, + suffix, + prefix, + token.pattern, + suffix, + mod + ) + else + route ..= ("(?:%s(%s)%s)%s"):format(prefix, token.pattern, suffix, token.modifier) + end + else + route ..= ("(%s)%s"):format(token.pattern, token.modifier) + end + else + route ..= ("(?:%s%s)%s"):format(prefix, suffix, token.modifier) + end + end + end + + if end_ then + if not strict then + route ..= ("%s?"):format(delimiter) + end + + if options.endsWith and options.endsWith ~= "" then + route ..= ("(?=%s)"):format(endsWith) + else + route ..= "$" + end + else + local endToken = tokens[#tokens] + local isEndDelimited = endToken == nil + if type(endToken) == "string" then + isEndDelimited = string.find(delimiter, endToken:sub(-1)) ~= nil + end + + if not strict then + route ..= string.format("(?:%s(?=%s))?", delimiter, endsWith) + end + if not isEndDelimited then + route ..= string.format("(?=%s|%s)", delimiter, endsWith) + end + end + + return RegExp(route, flags(options)) +end + +--[[ + * Supported `path-to-regexp` input types. + ]] +export type Path = string | { string } + +--[[ + * Normalize the given path string, returning a regular expression. + * + * An empty array can be passed in for the keys, which will hold the + * placeholder key descriptions. For example, using `/user/:id`, `keys` will + * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. + ]] +function exports.pathToRegexp(path: Path, keys: { Key }?, options: (TokensToRegexpOptions & ParseOptions)?) + -- if (path instanceof RegExp) return regexpToRegexp(path, keys); + if Array.isArray(path) then + return arrayToRegexp(path :: { string }, keys, options) + end + return stringToRegexp(path, keys, options) +end + +return exports \ No newline at end of file diff --git a/samples/Luau/replaceMacros.luau b/samples/Luau/replaceMacros.luau deleted file mode 100644 index 11daa0b368..0000000000 --- a/samples/Luau/replaceMacros.luau +++ /dev/null @@ -1,311 +0,0 @@ ---// Authored by @grilme99 (https://github.com/grilme99) ---// Fetched from (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/scripts/replaceMacros.luau) ---// Licensed under the MIT License (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/LICENSE-Brooke) - --- upstream: https://github.com/dead/typeflex/blob/422cb26/tools/repalce_macros.py - -local function YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - if node:getStyle().instanceName ~= paramName then - local style: YGStyle = node:getStyle() - style.instanceName = paramName - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) - if - node:getStyle().instanceName.value ~= YGFloatSanitize(paramName) - or node:getStyle().instanceName.unit ~= YGUnit.Percent - then - local style: YGStyle = node:getStyle() - style.instanceName.value = YGFloatSanitize(paramName) - style.instanceName.unit = if YGFloatIsUndefined(paramName) then YGUnit.Auto else YGUnit.Percent - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -local function YGNodeStyleSet##nameAuto(node: YGNode) - if node:getStyle().instanceName.unit ~= YGUnit.Auto then - local style: YGStyle = node:getStyle() - style.instanceName.value = 0 - style.instanceName.unit = YGUnit.Auto - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - return node:getStyle().instanceName -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, edge: YGEdge, paramName: number) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName[edge].unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName[edge] = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, edge: YGEdge, paramName: number) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent - ) - - if - (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName[edge].unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName[edge] = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -local function YGNodeStyleGet##name(node: YGNode, edge: YGEdge): type - local value: YGValue = node:getStyle().instanceName[edge] - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeStyleSet##nameAuto(node: YGNode, edge: YGEdge) - if node:getStyle().instanceName[edge].unit ~= YGUnit.Auto then - local style: YGStyle = node:getStyle() - style.instanceName[edge].value = 0 - style.instanceName[edge].unit = YGUnit.Auto - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL("number", name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - local value: YGValue = node:getStyle().instanceName - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL("number", name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - local value: YGValue = node:getStyle().instanceName - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_LAYOUT_PROPERTY_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeLayoutGet##name(node: YGNode): type - return node:getLayout().instanceName -end -exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local function YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeLayoutGet##name(node: YGNode, edge: YGEdge): type - -- YGAssertWithNode(node, edge <= YGEdge.End, "Cannot get layout properties of multi-edge shorthands") - - if edge == YGEdge.Start then - if node:getLayout().direction == YGDirection.RTL then - return node:getLayout().instanceName[YGEdge.Right] - else - return node:getLayout().instanceName[YGEdge.Left] - end - end - - if edge == YGEdge.End then - if node:getLayout().direction == YGDirection.RTL then - return node:getLayout().instanceName[YGEdge.Left] - else - return node:getLayout().instanceName[YGEdge.Right] - end - end - - return node:getLayout().instanceName[edge] -end -exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local cod = "" - -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDirection", "Direction", "direction", "direction") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGFlexDirection", "FlexDirection", "flexDirection", "flexDirection") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGJustify", "JustifyContent", "justifyContent", "justifyContent") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignContent", "alignContent", "alignContent") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignItems", "alignItems", "alignItems") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignSelf", "alignSelf", "alignSelf") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGPositionType", "PositionType", "positionType", "positionType") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGWrap", "FlexWrap", "flexWrap", "flexWrap") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGOverflow", "Overflow", "overflow", "overflow") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDisplay", "Display", "display", "display") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Position", "position", "position") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Margin", "margin", "margin") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Padding", "padding", "padding") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Margin", "margin") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Width", "width", "dimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Height", "height", "dimensions[YGDimension.Height]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinWidth", "minWidth", "minDimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinHeight", "minHeight", "minDimensions[YGDimension.Height]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxWidth", "maxWidth", "maxDimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxHeight", "maxHeight", "maxDimensions[YGDimension.Height]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Left", "position[YGEdge.Left]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Top", "position[YGEdge.Top]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Right", "position[YGEdge.Right]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Bottom", "position[YGEdge.Bottom]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Width", "dimensions[YGDimension.Width]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Height", "dimensions[YGDimension.Height]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("YGDirection", "Direction", "direction") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("boolean", "HadOverflow", "hadOverflow") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Margin", "margin") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Border", "border") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Padding", "padding") - -print(cod) \ No newline at end of file diff --git a/samples/Luau/roblox.luau b/samples/Luau/roblox.luau new file mode 100644 index 0000000000..2b1ea9beb8 --- /dev/null +++ b/samples/Luau/roblox.luau @@ -0,0 +1,512 @@ +--// Authored by @Filiptibell (https://github.com/filiptibell) +--// Fetched from (https://github.com/lune-org/lune/blob/main/types/roblox.luau) +--// Licensed under the Mozilla Public License v2.0 (https://github.com/lune-org/lune/blob/main/LICENSE.txt) + +--!strict +export type DatabaseScriptability = "None" | "Custom" | "Read" | "ReadWrite" | "Write" + +export type DatabasePropertyTag = + "Deprecated" + | "Hidden" + | "NotBrowsable" + | "NotReplicated" + | "NotScriptable" + | "ReadOnly" + | "WriteOnly" + +export type DatabaseClassTag = + "Deprecated" + | "NotBrowsable" + | "NotCreatable" + | "NotReplicated" + | "PlayerReplicated" + | "Service" + | "Settings" + | "UserSettings" + +export type DatabaseProperty = { + --[=[ + The name of the property. + ]=] + Name: string, + --[=[ + The datatype of the property. + + For normal datatypes this will be a string such as `string`, `Color3`, ... + + For enums this will be a string formatted as `Enum.EnumName`. + ]=] + Datatype: string, + --[=[ + The scriptability of this property, meaning if it can be written / read at runtime. + + All properties are writable and readable in Lune even if scriptability is not. + ]=] + Scriptability: DatabaseScriptability, + --[=[ + Tags describing the property. + + These include information such as if the property can be replicated to players + at runtime, if the property should be hidden in Roblox Studio, and more. + ]=] + Tags: { DatabasePropertyTag }, +} + +export type DatabaseClass = { + --[=[ + The name of the class. + ]=] + Name: string, + --[=[ + The superclass (parent class) of this class. + + May be nil if no parent class exists. + ]=] + Superclass: string?, + --[=[ + Known properties for this class. + ]=] + Properties: { [string]: DatabaseProperty }, + --[=[ + Default values for properties of this class. + + Note that these default properties use Lune's built-in datatype + userdatas, and that if there is a new datatype that Lune does + not yet know about, it may be missing from this table. + ]=] + DefaultProperties: { [string]: any }, + --[=[ + Tags describing the class. + + These include information such as if the class can be replicated + to players at runtime, and top-level class categories. + ]=] + Tags: { DatabaseClassTag }, +} + +export type DatabaseEnum = { + --[=[ + The name of this enum, for example `PartType` or `UserInputState`. + ]=] + Name: string, + --[=[ + Members of this enum. + + Note that this is a direct map of name -> enum values, + and does not actually use the EnumItem datatype itself. + ]=] + Items: { [string]: number }, +} + +export type Database = { + --[=[ + The current version of the reflection database. + + This will follow the format `x.y.z.w`, which most commonly looks something like `0.567.0.123456789` + ]=] + Version: string, + --[=[ + Retrieves a list of all currently known class names. + ]=] + GetClassNames: (self: Database) -> { string }, + --[=[ + Retrieves a list of all currently known enum names. + ]=] + GetEnumNames: (self: Database) -> { string }, + --[=[ + Gets a class with the exact given name, if one exists. + ]=] + GetClass: (self: Database, name: string) -> DatabaseClass?, + --[=[ + Gets an enum with the exact given name, if one exists. + ]=] + GetEnum: (self: Database, name: string) -> DatabaseEnum?, + --[=[ + Finds a class with the given name. + + This will use case-insensitive matching and ignore leading and trailing whitespace. + ]=] + FindClass: (self: Database, name: string) -> DatabaseClass?, + --[=[ + Finds an enum with the given name. + + This will use case-insensitive matching and ignore leading and trailing whitespace. + ]=] + FindEnum: (self: Database, name: string) -> DatabaseEnum?, +} + +type InstanceProperties = { + Parent: Instance?, + ClassName: string, + Name: string, + -- FIXME: This breaks intellisense, but we need some way to access + -- instance properties without casting the entire instance to any... + -- [string]: any, +} + +type InstanceMetatable = { + Clone: (self: Instance) -> Instance, + Destroy: (self: Instance) -> (), + ClearAllChildren: (self: Instance) -> (), + + GetChildren: (self: Instance) -> { Instance }, + GetDebugId: (self: Instance) -> string, + GetDescendants: (self: Instance) -> { Instance }, + GetFullName: (self: Instance) -> string, + + FindFirstAncestor: (self: Instance, name: string) -> Instance?, + FindFirstAncestorOfClass: (self: Instance, className: string) -> Instance?, + FindFirstAncestorWhichIsA: (self: Instance, className: string) -> Instance?, + FindFirstChild: (self: Instance, name: string, recursive: boolean?) -> Instance?, + FindFirstChildOfClass: (self: Instance, className: string, recursive: boolean?) -> Instance?, + FindFirstChildWhichIsA: (self: Instance, className: string, recursive: boolean?) -> Instance?, + + IsA: (self: Instance, className: string) -> boolean, + IsAncestorOf: (self: Instance, descendant: Instance) -> boolean, + IsDescendantOf: (self: Instance, ancestor: Instance) -> boolean, + + GetAttribute: (self: Instance, name: string) -> any, + GetAttributes: (self: Instance) -> { [string]: any }, + SetAttribute: (self: Instance, name: string, value: any) -> (), + + GetTags: (self: Instance) -> { string }, + HasTag: (self: Instance, name: string) -> boolean, + AddTag: (self: Instance, name: string) -> (), + RemoveTag: (self: Instance, name: string) -> (), +} + +export type Instance = typeof(setmetatable( + (nil :: any) :: InstanceProperties, + (nil :: any) :: { __index: InstanceMetatable } +)) + +export type DataModelProperties = {} +export type DataModelMetatable = { + GetService: (self: DataModel, name: string) -> Instance, + FindService: (self: DataModel, name: string) -> Instance?, +} + +export type DataModel = + Instance + & typeof(setmetatable( + (nil :: any) :: DataModelProperties, + (nil :: any) :: { __index: DataModelMetatable } + )) + +--[=[ + @class Roblox + + Built-in library for manipulating Roblox place & model files + + ### Example usage + + ```lua + local fs = require("@lune/fs") + local roblox = require("@lune/roblox") + + -- Reading a place file + local placeFile = fs.readFile("myPlaceFile.rbxl") + local game = roblox.deserializePlace(placeFile) + + -- Manipulating and reading instances - just like in Roblox! + local workspace = game:GetService("Workspace") + for _, child in workspace:GetChildren() do + print("Found child " .. child.Name .. " of class " .. child.ClassName) + end + + -- Writing a place file + local newPlaceFile = roblox.serializePlace(game) + fs.writeFile("myPlaceFile.rbxl", newPlaceFile) + ``` +]=] +local roblox = {} + +--[=[ + @within Roblox + @tag must_use + + Deserializes a place into a DataModel instance. + + This function accepts a string of contents, *not* a file path. + If reading a place file from a file path is desired, `fs.readFile` + can be used and the resulting string may be passed to this function. + + ### Example usage + + ```lua + local fs = require("@lune/fs") + local roblox = require("@lune/roblox") + + local placeFile = fs.readFile("filePath.rbxl") + local game = roblox.deserializePlace(placeFile) + ``` + + @param contents The contents of the place to read +]=] +function roblox.deserializePlace(contents: string): DataModel + return nil :: any +end + +--[=[ + @within Roblox + @tag must_use + + Deserializes a model into an array of instances. + + This function accepts a string of contents, *not* a file path. + If reading a model file from a file path is desired, `fs.readFile` + can be used and the resulting string may be passed to this function. + + ### Example usage + + ```lua + local fs = require("@lune/fs") + local roblox = require("@lune/roblox") + + local modelFile = fs.readFile("filePath.rbxm") + local instances = roblox.deserializeModel(modelFile) + ``` + + @param contents The contents of the model to read +]=] +function roblox.deserializeModel(contents: string): { Instance } + return nil :: any +end + +--[=[ + @within Roblox + @tag must_use + + Serializes a place from a DataModel instance. + + This string can then be written to a file, or sent over the network. + + ### Example usage + + ```lua + local fs = require("@lune/fs") + local roblox = require("@lune/roblox") + + local placeFile = roblox.serializePlace(game) + fs.writeFile("filePath.rbxl", placeFile) + ``` + + @param dataModel The DataModel for the place to serialize + @param xml If the place should be serialized as xml or not. Defaults to `false`, meaning the place gets serialized using the binary format and not xml. +]=] +function roblox.serializePlace(dataModel: DataModel, xml: boolean?): string + return nil :: any +end + +--[=[ + @within Roblox + @tag must_use + + Serializes one or more instances as a model. + + This string can then be written to a file, or sent over the network. + + ### Example usage + + ```lua + local fs = require("@lune/fs") + local roblox = require("@lune/roblox") + + local modelFile = roblox.serializeModel({ instance1, instance2, ... }) + fs.writeFile("filePath.rbxm", modelFile) + ``` + + @param instances The array of instances to serialize + @param xml If the model should be serialized as xml or not. Defaults to `false`, meaning the model gets serialized using the binary format and not xml. +]=] +function roblox.serializeModel(instances: { Instance }, xml: boolean?): string + return nil :: any +end + +--[=[ + @within Roblox + @tag must_use + + Gets the current auth cookie, for usage with Roblox web APIs. + + Note that this auth cookie is formatted for use as a "Cookie" header, + and that it contains restrictions so that it may only be used for + official Roblox endpoints. To get the raw cookie value without any + additional formatting, you can pass `true` as the first and only parameter. + + ### Example usage + + ```lua + local roblox = require("@lune/roblox") + local net = require("@lune/net") + + local cookie = roblox.getAuthCookie() + assert(cookie ~= nil, "Failed to get roblox auth cookie") + + local myPrivatePlaceId = 1234567890 + + local response = net.request({ + url = "https://assetdelivery.roblox.com/v2/assetId/" .. tostring(myPrivatePlaceId), + headers = { + Cookie = cookie, + }, + }) + + local responseTable = net.jsonDecode(response.body) + local responseLocation = responseTable.locations[1].location + print("Download link to place: " .. responseLocation) + ``` + + @param raw If the cookie should be returned as a pure value or not. Defaults to false +]=] +function roblox.getAuthCookie(raw: boolean?): string? + return nil :: any +end + +--[=[ + @within Roblox + @tag must_use + + Gets the bundled reflection database. + + This database contains information about Roblox enums, classes, and their properties. + + ### Example usage + + ```lua + local roblox = require("@lune/roblox") + + local db = roblox.getReflectionDatabase() + + print("There are", #db:GetClassNames(), "classes in the reflection database") + + print("All base instance properties:") + + local class = db:GetClass("Instance") + for name, prop in class.Properties do + print(string.format( + "- %s with datatype %s and default value %s", + prop.Name, + prop.Datatype, + tostring(class.DefaultProperties[prop.Name]) + )) + end + ``` +]=] +function roblox.getReflectionDatabase(): Database + return nil :: any +end + +--[=[ + @within Roblox + + Implements a property for all instances of the given `className`. + + This takes into account class hierarchies, so implementing a property + for the `BasePart` class will also implement it for `Part` and others, + unless a more specific implementation is added to the `Part` class directly. + + ### Behavior + + The given `getter` callback will be called each time the property is + indexed, with the instance as its one and only argument. The `setter` + callback, if given, will be called each time the property should be set, + with the instance as the first argument and the property value as second. + + ### Example usage + + ```lua + local roblox = require("@lune/roblox") + + local part = roblox.Instance.new("Part") + + local propertyValues = {} + roblox.implementProperty( + "BasePart", + "CoolProp", + function(instance) + if propertyValues[instance] == nil then + propertyValues[instance] = 0 + end + propertyValues[instance] += 1 + return propertyValues[instance] + end, + function(instance, value) + propertyValues[instance] = value + end + ) + + print(part.CoolProp) --> 1 + print(part.CoolProp) --> 2 + print(part.CoolProp) --> 3 + + part.CoolProp = 10 + + print(part.CoolProp) --> 11 + print(part.CoolProp) --> 12 + print(part.CoolProp) --> 13 + ``` + + @param className The class to implement the property for. + @param propertyName The name of the property to implement. + @param getter The function which will be called to get the property value when indexed. + @param setter The function which will be called to set the property value when indexed. Defaults to a function that will error with a message saying the property is read-only. +]=] +function roblox.implementProperty( + className: string, + propertyName: string, + getter: (instance: Instance) -> T, + setter: ((instance: Instance, value: T) -> ())? +) + return nil :: any +end + +--[=[ + @within Roblox + + Implements a method for all instances of the given `className`. + + This takes into account class hierarchies, so implementing a method + for the `BasePart` class will also implement it for `Part` and others, + unless a more specific implementation is added to the `Part` class directly. + + ### Behavior + + The given `callback` will be called every time the method is called, + and will receive the instance it was called on as its first argument. + The remaining arguments will be what the caller passed to the method, and + all values returned from the callback will then be returned to the caller. + + ### Example usage + + ```lua + local roblox = require("@lune/roblox") + + local part = roblox.Instance.new("Part") + + roblox.implementMethod("BasePart", "TestMethod", function(instance, ...) + print("Called TestMethod on instance", instance, "with", ...) + end) + + part:TestMethod("Hello", "world!") + --> Called TestMethod on instance Part with Hello, world! + ``` + + @param className The class to implement the method for. + @param methodName The name of the method to implement. + @param callback The function which will be called when the method is called. +]=] +function roblox.implementMethod( + className: string, + methodName: string, + callback: (instance: Instance, ...any) -> ...any +) + return nil :: any +end + +-- TODO: Make typedefs for all of the datatypes as well... +roblox.Instance = (nil :: any) :: { + new: ((className: "DataModel") -> DataModel) & ((className: string) -> Instance), +} + +return roblox diff --git a/samples/Luau/timeStepper.luau b/samples/Luau/timeStepper.luau deleted file mode 100644 index 038a614451..0000000000 --- a/samples/Luau/timeStepper.luau +++ /dev/null @@ -1,36 +0,0 @@ ---// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) ---// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/TimeStepper.luau) ---// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) - ---!strict - ---[[ - -A time stepper is responsible for stepping systems in a deterministic order at a set interval. - -]] - -local TimeStepper = {} -TimeStepper.__index = TimeStepper - -function TimeStepper.new(interval: number, systems) - local self = setmetatable({ - _systems = systems, - _interval = interval, - }, TimeStepper) - - return self -end - -function TimeStepper:start() - coroutine.resume(coroutine.create(function() - while true do - local timeStep, _ = wait(self._interval) - for _, system in ipairs(self._systems) do - system:step(timeStep) - end - end - end)) -end - -return TimeStepper diff --git a/samples/Luau/toSet.luau b/samples/Luau/toSet.luau deleted file mode 100644 index 7d18f99ed2..0000000000 --- a/samples/Luau/toSet.luau +++ /dev/null @@ -1,22 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/List/toSet.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---!strict - ---[=[ - Returns a Set from the given List. - - @within List - @function toSet - @ignore -]=] -return function(list: { Value }): { [Value]: boolean } - local set = {} - - for _, v in list do - set[v] = true - end - - return set -end diff --git a/vendor/CodeMirror b/vendor/CodeMirror index 0c8456c3bc..53faa33ac6 160000 --- a/vendor/CodeMirror +++ b/vendor/CodeMirror @@ -1 +1 @@ -Subproject commit 0c8456c3bc92fb3085ac636f5ed117df24e22ca7 +Subproject commit 53faa33ac69598b7495e160824b58ebb8d70fe97 diff --git a/vendor/grammars/Handlebars b/vendor/grammars/Handlebars index 9fb01fefe4..c2c09947b6 160000 --- a/vendor/grammars/Handlebars +++ b/vendor/grammars/Handlebars @@ -1 +1 @@ -Subproject commit 9fb01fefe48532deb901926cd80774215b454ff9 +Subproject commit c2c09947b6b83d740e9ee94a6e3a0199476f2e15 diff --git a/vendor/grammars/MATLAB-Language-grammar b/vendor/grammars/MATLAB-Language-grammar index f3533822b2..c2c0e4ec8a 160000 --- a/vendor/grammars/MATLAB-Language-grammar +++ b/vendor/grammars/MATLAB-Language-grammar @@ -1 +1 @@ -Subproject commit f3533822b2d740fd4128722854c98b9f1b5d07ee +Subproject commit c2c0e4ec8a401b2c6f76e6069aa0b19da3d0b3bd diff --git a/vendor/grammars/Nasal.tmbundle b/vendor/grammars/Nasal.tmbundle index beb4c5bee4..856bd56438 160000 --- a/vendor/grammars/Nasal.tmbundle +++ b/vendor/grammars/Nasal.tmbundle @@ -1 +1 @@ -Subproject commit beb4c5bee4e83de7cbf25c0cdc25e28489c8a1f7 +Subproject commit 856bd56438b2ca2a82676b461dcebbe74e75600c diff --git a/vendor/grammars/NimLime b/vendor/grammars/NimLime index df69515d85..6297c32c11 160000 --- a/vendor/grammars/NimLime +++ b/vendor/grammars/NimLime @@ -1 +1 @@ -Subproject commit df69515d853380cde7ad58c54e69cf52e725d3bb +Subproject commit 6297c32c11fd92eb9bc3c5cd09a9ff79b7f3d4f9 diff --git a/vendor/grammars/Terraform.tmLanguage b/vendor/grammars/Terraform.tmLanguage index 1576c26c2a..7faec10b40 160000 --- a/vendor/grammars/Terraform.tmLanguage +++ b/vendor/grammars/Terraform.tmLanguage @@ -1 +1 @@ -Subproject commit 1576c26c2ac215f9bed5e1f8a061f123bf63e6b2 +Subproject commit 7faec10b4057b95bff82fb585730e560871d26c4 diff --git a/vendor/grammars/TypeScript-TmLanguage b/vendor/grammars/TypeScript-TmLanguage index 4fdfd38727..82dcf60b94 160000 --- a/vendor/grammars/TypeScript-TmLanguage +++ b/vendor/grammars/TypeScript-TmLanguage @@ -1 +1 @@ -Subproject commit 4fdfd387273124944e32582f9f5af019f515158e +Subproject commit 82dcf60b94c305088165eff5c7e14b07f6048b22 diff --git a/vendor/grammars/VscodeAdblockSyntax b/vendor/grammars/VscodeAdblockSyntax index b4a53bee21..bbd868b428 160000 --- a/vendor/grammars/VscodeAdblockSyntax +++ b/vendor/grammars/VscodeAdblockSyntax @@ -1 +1 @@ -Subproject commit b4a53bee21c0f5074956a7377a36a45febf97d25 +Subproject commit bbd868b4288bf03cb3a2551b5ef35b2103e5a70f diff --git a/vendor/grammars/abap-cds-grammar b/vendor/grammars/abap-cds-grammar index dd803e6ad2..14d892fb0e 160000 --- a/vendor/grammars/abap-cds-grammar +++ b/vendor/grammars/abap-cds-grammar @@ -1 +1 @@ -Subproject commit dd803e6ad2eea202b537a87c0da0536b92f60d3e +Subproject commit 14d892fb0eb8bd98b0340a2b6d265cbeeb938884 diff --git a/vendor/grammars/abl-tmlanguage b/vendor/grammars/abl-tmlanguage index bc85d5f1b0..e928116b41 160000 --- a/vendor/grammars/abl-tmlanguage +++ b/vendor/grammars/abl-tmlanguage @@ -1 +1 @@ -Subproject commit bc85d5f1b0e10ca4a3eb6946d5426d2560a4e328 +Subproject commit e928116b4138bcf191b6f64be5bf92ef9c345d75 diff --git a/vendor/grammars/aidl-language b/vendor/grammars/aidl-language index c2c0074c59..ce07cdf6b9 160000 --- a/vendor/grammars/aidl-language +++ b/vendor/grammars/aidl-language @@ -1 +1 @@ -Subproject commit c2c0074c593954bcd27036db118aee6a13abfa20 +Subproject commit ce07cdf6b9b9dbc4857c4b2fc59a55ac0e17ecd4 diff --git a/vendor/grammars/astro b/vendor/grammars/astro index 1a55b6f122..1e84fac22f 160000 --- a/vendor/grammars/astro +++ b/vendor/grammars/astro @@ -1 +1 @@ -Subproject commit 1a55b6f122f35d1e08716627fad6c8bcfeedab2f +Subproject commit 1e84fac22fb3ed6686ec4d5f963a0420ba2d7d82 diff --git a/vendor/grammars/atom-language-julia b/vendor/grammars/atom-language-julia index 0735fb8026..134a10664d 160000 --- a/vendor/grammars/atom-language-julia +++ b/vendor/grammars/atom-language-julia @@ -1 +1 @@ -Subproject commit 0735fb802696a2b6d89e12ba302d0a85d901bc17 +Subproject commit 134a10664d7b9010af432f4042637c16fd61fe2b diff --git a/vendor/grammars/atom-language-perl6 b/vendor/grammars/atom-language-perl6 index bd341e83c1..8c196e381e 160000 --- a/vendor/grammars/atom-language-perl6 +++ b/vendor/grammars/atom-language-perl6 @@ -1 +1 @@ -Subproject commit bd341e83c14a69b68cb304fb6931abed8473b05a +Subproject commit 8c196e381edb0e5575db611bad4ca7de8b437d66 diff --git a/vendor/grammars/atomic-dreams b/vendor/grammars/atomic-dreams new file mode 160000 index 0000000000..14891385ac --- /dev/null +++ b/vendor/grammars/atomic-dreams @@ -0,0 +1 @@ +Subproject commit 14891385ac0110cdcdb22f2dc1a8930cdf501c91 diff --git a/vendor/grammars/bicep b/vendor/grammars/bicep index 0c0394b9f7..d5f69a7dd2 160000 --- a/vendor/grammars/bicep +++ b/vendor/grammars/bicep @@ -1 +1 @@ -Subproject commit 0c0394b9f70f59b4a2d31dc9cc9b0ba03493685f +Subproject commit d5f69a7dd2e38820b1c8af0fc7ca009a406f6c66 diff --git a/vendor/grammars/bikeshed b/vendor/grammars/bikeshed index 584813e638..4cd224458e 160000 --- a/vendor/grammars/bikeshed +++ b/vendor/grammars/bikeshed @@ -1 +1 @@ -Subproject commit 584813e6380533a19c6656594c810bf974854e68 +Subproject commit 4cd224458e3672f5aa8d521d8ad0ac5658cf1a1c diff --git a/vendor/grammars/cds-textmate-grammar b/vendor/grammars/cds-textmate-grammar index d538952370..b6a736eabd 160000 --- a/vendor/grammars/cds-textmate-grammar +++ b/vendor/grammars/cds-textmate-grammar @@ -1 +1 @@ -Subproject commit d5389523709ba010cf1b9de78c6c0478bab31bde +Subproject commit b6a736eabd7d11ef9a1863cfbc0965e1d9b18c0b diff --git a/vendor/grammars/csharp-tmLanguage b/vendor/grammars/csharp-tmLanguage index 7a7482ffc7..1fc58ea221 160000 --- a/vendor/grammars/csharp-tmLanguage +++ b/vendor/grammars/csharp-tmLanguage @@ -1 +1 @@ -Subproject commit 7a7482ffc72a6677a87eb1ed76005593a4f7f131 +Subproject commit 1fc58ea2212577cd5249f4cd540a07942a1e5806 diff --git a/vendor/grammars/d.tmbundle b/vendor/grammars/d.tmbundle index 9fb354be1c..e031d03ce0 160000 --- a/vendor/grammars/d.tmbundle +++ b/vendor/grammars/d.tmbundle @@ -1 +1 @@ -Subproject commit 9fb354be1c3fbb6a91f543f584d47099d338baf0 +Subproject commit e031d03ce0c2fe0f9e064dad1faf670a19bde482 diff --git a/vendor/grammars/d2-vscode b/vendor/grammars/d2-vscode index d722a70f36..0b4e66fc23 160000 --- a/vendor/grammars/d2-vscode +++ b/vendor/grammars/d2-vscode @@ -1 +1 @@ -Subproject commit d722a70f36e634790eb60e86b678577af0e569c6 +Subproject commit 0b4e66fc235c494a7bf7423d0b9eab3d7bf92833 diff --git a/vendor/grammars/dart-syntax-highlight b/vendor/grammars/dart-syntax-highlight index 1b307d29d4..6ec2cb7fa6 160000 --- a/vendor/grammars/dart-syntax-highlight +++ b/vendor/grammars/dart-syntax-highlight @@ -1 +1 @@ -Subproject commit 1b307d29d454a382f1c143de516335b1a885484b +Subproject commit 6ec2cb7fa65c0e0bed366223920171c0a02db29d diff --git a/vendor/grammars/denizenscript-grammar b/vendor/grammars/denizenscript-grammar index a9a9523202..069cb69265 160000 --- a/vendor/grammars/denizenscript-grammar +++ b/vendor/grammars/denizenscript-grammar @@ -1 +1 @@ -Subproject commit a9a95232026192c0d5eb23557882f9b69a40d66e +Subproject commit 069cb69265ec500e690e8df20a58addc968fbc31 diff --git a/vendor/grammars/earthfile-grammar b/vendor/grammars/earthfile-grammar index 3ab3ea3b51..4afcb6f2cb 160000 --- a/vendor/grammars/earthfile-grammar +++ b/vendor/grammars/earthfile-grammar @@ -1 +1 @@ -Subproject commit 3ab3ea3b512ce54fb71f8070c7e03926ef259c03 +Subproject commit 4afcb6f2cb55d560d94d9ab2b827b6a784d08428 diff --git a/vendor/grammars/elixir-tmbundle b/vendor/grammars/elixir-tmbundle index f6867d6aee..b01fffc491 160000 --- a/vendor/grammars/elixir-tmbundle +++ b/vendor/grammars/elixir-tmbundle @@ -1 +1 @@ -Subproject commit f6867d6aee89c23a9803d958c62eef1c60170e28 +Subproject commit b01fffc49179bdec936ca19b53ba4fc7c51a2cc0 diff --git a/vendor/grammars/elvish b/vendor/grammars/elvish index eadae7fc65..1c0cffbfed 160000 --- a/vendor/grammars/elvish +++ b/vendor/grammars/elvish @@ -1 +1 @@ -Subproject commit eadae7fc651345f925e6e412628e1a463954ae5f +Subproject commit 1c0cffbfed892aa7129ae0a19dad1617255a9faa diff --git a/vendor/grammars/gemini-vscode b/vendor/grammars/gemini-vscode index 72ef757a0c..172351ff9e 160000 --- a/vendor/grammars/gemini-vscode +++ b/vendor/grammars/gemini-vscode @@ -1 +1 @@ -Subproject commit 72ef757a0cb5d1e7137edd97579a92588f7e54cb +Subproject commit 172351ff9e2cbf1296c29ab1ea475cb0fa3344c2 diff --git a/vendor/grammars/genero.tmbundle b/vendor/grammars/genero.tmbundle new file mode 160000 index 0000000000..ea111e21c5 --- /dev/null +++ b/vendor/grammars/genero.tmbundle @@ -0,0 +1 @@ +Subproject commit ea111e21c5424b09330197696428cfe1dee65df4 diff --git a/vendor/grammars/godot-vscode-plugin b/vendor/grammars/godot-vscode-plugin index 5cef963162..acfcfdbdab 160000 --- a/vendor/grammars/godot-vscode-plugin +++ b/vendor/grammars/godot-vscode-plugin @@ -1 +1 @@ -Subproject commit 5cef96316208d76795c9763291889b92f2d84d4b +Subproject commit acfcfdbdabff9bed65024d3807ed3c53f5e3a29d diff --git a/vendor/grammars/graphiql b/vendor/grammars/graphiql index ece99f63f5..57b5fcd874 160000 --- a/vendor/grammars/graphiql +++ b/vendor/grammars/graphiql @@ -1 +1 @@ -Subproject commit ece99f63f5d8d01057b735e90a6957edea3e42b9 +Subproject commit 57b5fcd8749bd1c54f4a6a04a9ab07316fd05e71 diff --git a/vendor/grammars/ionide-fsgrammar b/vendor/grammars/ionide-fsgrammar index 7d029a46f1..8740e610a3 160000 --- a/vendor/grammars/ionide-fsgrammar +++ b/vendor/grammars/ionide-fsgrammar @@ -1 +1 @@ -Subproject commit 7d029a46f17637228b2ee85dd02e511c3e8039b3 +Subproject commit 8740e610a367c5e3f15be716acc7207655ced4cf diff --git a/vendor/grammars/language-csound b/vendor/grammars/language-csound index 55beb6eab9..975436138f 160000 --- a/vendor/grammars/language-csound +++ b/vendor/grammars/language-csound @@ -1 +1 @@ -Subproject commit 55beb6eab9ad3783336fbefac52d759245954bc1 +Subproject commit 975436138f2ab808e4a2c3897586ccb0b3c958c2 diff --git a/vendor/grammars/language-etc b/vendor/grammars/language-etc index 1fd00541cc..6d176400d6 160000 --- a/vendor/grammars/language-etc +++ b/vendor/grammars/language-etc @@ -1 +1 @@ -Subproject commit 1fd00541cc0cd25b319c2a749d55e0144a91451c +Subproject commit 6d176400d627b0c723180c46bec862b078250401 diff --git a/vendor/grammars/language-kotlin b/vendor/grammars/language-kotlin index b65a18d4c2..bad0234aa6 160000 --- a/vendor/grammars/language-kotlin +++ b/vendor/grammars/language-kotlin @@ -1 +1 @@ -Subproject commit b65a18d4c23eedcd2bc280cd04282d29d191721a +Subproject commit bad0234aa658d1eb7e18881f71930fd7d70701a4 diff --git a/vendor/grammars/language-subtitles b/vendor/grammars/language-subtitles index 82cf7686f8..70c9d731c1 160000 --- a/vendor/grammars/language-subtitles +++ b/vendor/grammars/language-subtitles @@ -1 +1 @@ -Subproject commit 82cf7686f8f15c19c80e612c8b7da57d87eeb5b7 +Subproject commit 70c9d731c1ba24f058c6630824cf1188cbee9843 diff --git a/vendor/grammars/language-viml b/vendor/grammars/language-viml index 979e1c6223..5030985ab9 160000 --- a/vendor/grammars/language-viml +++ b/vendor/grammars/language-viml @@ -1 +1 @@ -Subproject commit 979e1c62230bbf29fd6963e3620028ccba6848ad +Subproject commit 5030985ab9f5a84a3a18d999d7c3a82f581b0283 diff --git a/vendor/grammars/latex.tmbundle b/vendor/grammars/latex.tmbundle index 90d3383ff8..8e472548d1 160000 --- a/vendor/grammars/latex.tmbundle +++ b/vendor/grammars/latex.tmbundle @@ -1 +1 @@ -Subproject commit 90d3383ff86b7f4495e33c7c5240dde7308376ee +Subproject commit 8e472548d189604712739a925dbdd735069e0668 diff --git a/vendor/grammars/lua.tmbundle b/vendor/grammars/lua.tmbundle index 94ce82cc4d..8ae5641365 160000 --- a/vendor/grammars/lua.tmbundle +++ b/vendor/grammars/lua.tmbundle @@ -1 +1 @@ -Subproject commit 94ce82cc4d45f82641a5252d7a7fd9e28c875adc +Subproject commit 8ae5641365b28f697121ba1133890e8d81f5b00e diff --git a/vendor/grammars/markdown-tm-language b/vendor/grammars/markdown-tm-language index 371d61df9d..e14b65157a 160000 --- a/vendor/grammars/markdown-tm-language +++ b/vendor/grammars/markdown-tm-language @@ -1 +1 @@ -Subproject commit 371d61df9ddc3850e12aabe61b602d02e259e8a4 +Subproject commit e14b65157a0b738430309b14b2f025f3bb37526a diff --git a/vendor/grammars/mint-vscode b/vendor/grammars/mint-vscode index 2de0c6ae29..4ea454c6be 160000 --- a/vendor/grammars/mint-vscode +++ b/vendor/grammars/mint-vscode @@ -1 +1 @@ -Subproject commit 2de0c6ae292a3b23d71fa2eebfbb48f0a0cfb66b +Subproject commit 4ea454c6beff96deccb7abfc6cf04464b3ce8963 diff --git a/vendor/grammars/nu-grammar b/vendor/grammars/nu-grammar index 1ee4b15bd2..03a70db4a8 160000 --- a/vendor/grammars/nu-grammar +++ b/vendor/grammars/nu-grammar @@ -1 +1 @@ -Subproject commit 1ee4b15bd214c951b75270d55c3e273b486f0a75 +Subproject commit 03a70db4a8cb3a0e2113fc63c48b8780b0cecd0d diff --git a/vendor/grammars/qsharp-compiler b/vendor/grammars/qsharp-compiler index 1b4270217a..ce9622e4e4 160000 --- a/vendor/grammars/qsharp-compiler +++ b/vendor/grammars/qsharp-compiler @@ -1 +1 @@ -Subproject commit 1b4270217aff846fb8d2d1f24094bd7bb36514a5 +Subproject commit ce9622e4e49c0fc5e4e887509e1651ac884c1a04 diff --git a/vendor/grammars/rescript-vscode b/vendor/grammars/rescript-vscode index 69bfb269cd..59855be802 160000 --- a/vendor/grammars/rescript-vscode +++ b/vendor/grammars/rescript-vscode @@ -1 +1 @@ -Subproject commit 69bfb269cde531e2d2cd3ad8e6768ae4a7d62d1f +Subproject commit 59855be8021f78e6c24e98ecccdb303498381c5f diff --git a/vendor/grammars/rust-syntax b/vendor/grammars/rust-syntax index cf3c686a50..8f2bc1cb0c 160000 --- a/vendor/grammars/rust-syntax +++ b/vendor/grammars/rust-syntax @@ -1 +1 @@ -Subproject commit cf3c686a50295380ce9994218138691f8767870c +Subproject commit 8f2bc1cb0cf329f1292248bc546b605f01695308 diff --git a/vendor/grammars/sas.tmbundle b/vendor/grammars/sas.tmbundle index 9d84eddcbf..7a70af2284 160000 --- a/vendor/grammars/sas.tmbundle +++ b/vendor/grammars/sas.tmbundle @@ -1 +1 @@ -Subproject commit 9d84eddcbffb86df3091fc88d0983bc33ec358a0 +Subproject commit 7a70af22840d2fe04e17634685bab7b529613822 diff --git a/vendor/grammars/selinux-policy-languages b/vendor/grammars/selinux-policy-languages index 502c96ccf3..7bd17d9c91 160000 --- a/vendor/grammars/selinux-policy-languages +++ b/vendor/grammars/selinux-policy-languages @@ -1 +1 @@ -Subproject commit 502c96ccf38476a627897209bf726fd4e40296b8 +Subproject commit 7bd17d9c9160cd8168a159c6cc574d45cb67b125 diff --git a/vendor/grammars/smithy-vscode b/vendor/grammars/smithy-vscode index 43b3416a4d..f205110486 160000 --- a/vendor/grammars/smithy-vscode +++ b/vendor/grammars/smithy-vscode @@ -1 +1 @@ -Subproject commit 43b3416a4ddedb57c3992b831bed6346e974d7d3 +Subproject commit f205110486d3bac531b0d655992d96d3fd928401 diff --git a/vendor/grammars/sourcepawn-vscode b/vendor/grammars/sourcepawn-vscode index 339ceeb9d3..88a48c8263 160000 --- a/vendor/grammars/sourcepawn-vscode +++ b/vendor/grammars/sourcepawn-vscode @@ -1 +1 @@ -Subproject commit 339ceeb9d3c3c61706a743d14284821f33f04d26 +Subproject commit 88a48c8263acd92cc877d624b3918cecfcb18d03 diff --git a/vendor/grammars/sublime-odin b/vendor/grammars/sublime-odin index c4b626f17c..1746bce31b 160000 --- a/vendor/grammars/sublime-odin +++ b/vendor/grammars/sublime-odin @@ -1 +1 @@ -Subproject commit c4b626f17c97542c922b55a33618aa953cff4771 +Subproject commit 1746bce31b2872906a5fab6282d0855dbefae9a1 diff --git a/vendor/grammars/sublime-pony b/vendor/grammars/sublime-pony index 80f6c4dfbf..13f1b55340 160000 --- a/vendor/grammars/sublime-pony +++ b/vendor/grammars/sublime-pony @@ -1 +1 @@ -Subproject commit 80f6c4dfbff2edc4e8546b33c983f4314de06630 +Subproject commit 13f1b55340ddd46c7dfeac6446030dfe8d105f90 diff --git a/vendor/grammars/sublime-q b/vendor/grammars/sublime-q index 236b80c1f3..c4f98f62b4 160000 --- a/vendor/grammars/sublime-q +++ b/vendor/grammars/sublime-q @@ -1 +1 @@ -Subproject commit 236b80c1f32fbcb16d9ee7487a6f35b62a946af0 +Subproject commit c4f98f62b4911d27611c883c0428694dc984126d diff --git a/vendor/grammars/sway-vscode-plugin b/vendor/grammars/sway-vscode-plugin index a6bb690d0a..3b3b13357b 160000 --- a/vendor/grammars/sway-vscode-plugin +++ b/vendor/grammars/sway-vscode-plugin @@ -1 +1 @@ -Subproject commit a6bb690d0a9839450971eae22a0f207c654126a5 +Subproject commit 3b3b13357b5a5cdc3b353a90e48f500c19767009 diff --git a/vendor/grammars/swift.tmbundle b/vendor/grammars/swift.tmbundle new file mode 160000 index 0000000000..aa73c3410d --- /dev/null +++ b/vendor/grammars/swift.tmbundle @@ -0,0 +1 @@ +Subproject commit aa73c3410d26dea8d39c834fd8e9bec4319dad2c diff --git a/vendor/grammars/vscode-antlers-language-server b/vendor/grammars/vscode-antlers-language-server index bff5066893..25b1f58a94 160000 --- a/vendor/grammars/vscode-antlers-language-server +++ b/vendor/grammars/vscode-antlers-language-server @@ -1 +1 @@ -Subproject commit bff50668934abb41b197d75650a13fb5e300132e +Subproject commit 25b1f58a9429697b4a1631324ad8583dfa64270b diff --git a/vendor/grammars/vscode-brightscript-language b/vendor/grammars/vscode-brightscript-language index b05f3a31a8..bdae42c2e4 160000 --- a/vendor/grammars/vscode-brightscript-language +++ b/vendor/grammars/vscode-brightscript-language @@ -1 +1 @@ -Subproject commit b05f3a31a8877a8fa0e00ff554a3e98328050c44 +Subproject commit bdae42c2e4a42ec8504aa29e6bbbbf8e6d8755a2 diff --git a/vendor/grammars/vscode-cadence b/vendor/grammars/vscode-cadence index 2ce921581a..650029d38c 160000 --- a/vendor/grammars/vscode-cadence +++ b/vendor/grammars/vscode-cadence @@ -1 +1 @@ -Subproject commit 2ce921581a804fa32317b829c54c1c0fd904975a +Subproject commit 650029d38c6ac2cddb7ce9fd4054461f4b140f5a diff --git a/vendor/grammars/vscode-codeql b/vendor/grammars/vscode-codeql index 3005dacf4e..93fc90fcd0 160000 --- a/vendor/grammars/vscode-codeql +++ b/vendor/grammars/vscode-codeql @@ -1 +1 @@ -Subproject commit 3005dacf4ed480aa76e541b7d3697f5a34571faf +Subproject commit 93fc90fcd081f8d049440f3575e1bbbe74ff2db7 diff --git a/vendor/grammars/vscode-fluent b/vendor/grammars/vscode-fluent index 51ceeb59d6..cae702ed1b 160000 --- a/vendor/grammars/vscode-fluent +++ b/vendor/grammars/vscode-fluent @@ -1 +1 @@ -Subproject commit 51ceeb59d645d0d1ec9b4080175bc1ce4d709075 +Subproject commit cae702ed1b3a6a2b6a58260a8e1194bc40421218 diff --git a/vendor/grammars/vscode-go b/vendor/grammars/vscode-go index b4b68a76fa..d9015c19ed 160000 --- a/vendor/grammars/vscode-go +++ b/vendor/grammars/vscode-go @@ -1 +1 @@ -Subproject commit b4b68a76fac190ca12179ef9ef24ecffc57ea9d6 +Subproject commit d9015c19ed5be58bb51f3c53b651fe2468540086 diff --git a/vendor/grammars/vscode-hack b/vendor/grammars/vscode-hack index dd79d7146c..d75dd72a5d 160000 --- a/vendor/grammars/vscode-hack +++ b/vendor/grammars/vscode-hack @@ -1 +1 @@ -Subproject commit dd79d7146ccffb3e0a1020e9f8790242c37b6c21 +Subproject commit d75dd72a5d52436d208a627a2ead5423c94eb3e9 diff --git a/vendor/grammars/vscode-ibmi-languages b/vendor/grammars/vscode-ibmi-languages index 7ed423b793..e9d72cece7 160000 --- a/vendor/grammars/vscode-ibmi-languages +++ b/vendor/grammars/vscode-ibmi-languages @@ -1 +1 @@ -Subproject commit 7ed423b7930e652f8b3fdf36d3f77d6e122ced41 +Subproject commit e9d72cece714ae968802802f09bdc089f79d7d4b diff --git a/vendor/grammars/vscode-jest b/vendor/grammars/vscode-jest index 0c57873f91..ad719cb5b9 160000 --- a/vendor/grammars/vscode-jest +++ b/vendor/grammars/vscode-jest @@ -1 +1 @@ -Subproject commit 0c57873f91138961f90fedeb5fd1f0da9d45c601 +Subproject commit ad719cb5b9509715047f1cae8ea7204db5f8faa5 diff --git a/vendor/grammars/vscode-lean b/vendor/grammars/vscode-lean index 1589ca3a65..0696b29285 160000 --- a/vendor/grammars/vscode-lean +++ b/vendor/grammars/vscode-lean @@ -1 +1 @@ -Subproject commit 1589ca3a65e394b3789409707febbd2d166c9344 +Subproject commit 0696b292855c7cae711e00d69c79b2687c336f25 diff --git a/vendor/grammars/vscode-motoko b/vendor/grammars/vscode-motoko index 1d256baa0c..4ecca55b8d 160000 --- a/vendor/grammars/vscode-motoko +++ b/vendor/grammars/vscode-motoko @@ -1 +1 @@ -Subproject commit 1d256baa0cfec3cb3083c519d926a0453f201623 +Subproject commit 4ecca55b8dd1fcebce92c80e67f0dcaf9ac4fdf3 diff --git a/vendor/grammars/vscode-move-syntax b/vendor/grammars/vscode-move-syntax index f9436ad72c..de9244e815 160000 --- a/vendor/grammars/vscode-move-syntax +++ b/vendor/grammars/vscode-move-syntax @@ -1 +1 @@ -Subproject commit f9436ad72cc7d3c6848eb02759c808ae0663295d +Subproject commit de9244e81505ff9154dc9422dbce434ac86fb981 diff --git a/vendor/grammars/vscode-opa b/vendor/grammars/vscode-opa index dcf4476992..0282a611d1 160000 --- a/vendor/grammars/vscode-opa +++ b/vendor/grammars/vscode-opa @@ -1 +1 @@ -Subproject commit dcf447699243ef2984d8dcb0b009b35b3fe74400 +Subproject commit 0282a611d1c3e70ffc353fd2003f0027a1c9ccfe diff --git a/vendor/grammars/vscode-pddl b/vendor/grammars/vscode-pddl index 73b0cb1b72..4fc7fc6d1a 160000 --- a/vendor/grammars/vscode-pddl +++ b/vendor/grammars/vscode-pddl @@ -1 +1 @@ -Subproject commit 73b0cb1b72c72ff795f173ba18e9f4ae810b0250 +Subproject commit 4fc7fc6d1a5f1378ea5039f77e8074da1a882918 diff --git a/vendor/grammars/vscode-prisma b/vendor/grammars/vscode-prisma index bf28282fd0..0b8d95ba11 160000 --- a/vendor/grammars/vscode-prisma +++ b/vendor/grammars/vscode-prisma @@ -1 +1 @@ -Subproject commit bf28282fd07c675b79282cafb63b4912bc6c7156 +Subproject commit 0b8d95ba11276ba694b73dcf568a747d13a882e5 diff --git a/vendor/grammars/vscode-procfile b/vendor/grammars/vscode-procfile index 4e149fd5e7..b1ddcf5c34 160000 --- a/vendor/grammars/vscode-procfile +++ b/vendor/grammars/vscode-procfile @@ -1 +1 @@ -Subproject commit 4e149fd5e757352aa5867b59934acc16e19182a1 +Subproject commit b1ddcf5c349acaa005fabdcf7111f34443d552f3 diff --git a/vendor/grammars/vscode-scala-syntax b/vendor/grammars/vscode-scala-syntax index fb73e8a0bf..d9036418a5 160000 --- a/vendor/grammars/vscode-scala-syntax +++ b/vendor/grammars/vscode-scala-syntax @@ -1 +1 @@ -Subproject commit fb73e8a0bfcd9a3c45f2c1b712e00c35865a9178 +Subproject commit d9036418a5e1f5c0cb7e3225e7bd05753d4934d6 diff --git a/vendor/grammars/vscode-slice b/vendor/grammars/vscode-slice index a4aacfa8bc..45660f594c 160000 --- a/vendor/grammars/vscode-slice +++ b/vendor/grammars/vscode-slice @@ -1 +1 @@ -Subproject commit a4aacfa8bc1ec6cc925dfd891dd75dfbf9df207d +Subproject commit 45660f594c4dc3b4eaf9f14b5a6cd7a5ce01aee2 diff --git a/vendor/grammars/vscode_cobol b/vendor/grammars/vscode_cobol index b09f64b00e..8bded749c0 160000 --- a/vendor/grammars/vscode_cobol +++ b/vendor/grammars/vscode_cobol @@ -1 +1 @@ -Subproject commit b09f64b00eee66dca496a69ea68f4c2c5c78e359 +Subproject commit 8bded749c0cf77ffd665b73afbe7ff8b7bc7f938 diff --git a/vendor/grammars/wgsl-analyzer b/vendor/grammars/wgsl-analyzer index 8851962fc1..92219fd398 160000 --- a/vendor/grammars/wgsl-analyzer +++ b/vendor/grammars/wgsl-analyzer @@ -1 +1 @@ -Subproject commit 8851962fc191aa5ea85a25b0ebd10cb9f70627b3 +Subproject commit 92219fd3987158b8399d0b2c1dfb426af77f9c51 From 2f9a8a20ea944b9f8290a1c0a73e400a89759398 Mon Sep 17 00:00:00 2001 From: robloxiandemo <76854027+robloxiandemo@users.noreply.github.com> Date: Sat, 6 Apr 2024 17:19:31 -0400 Subject: [PATCH 24/29] Patch: Update old samples and their sources. --- samples/Luau/EnumList.luau | 109 ++++ samples/Luau/Maid.luau | 213 ++++++++ samples/Luau/Option.luau | 497 ++++++++++++++++++ samples/Luau/Signal.luau | 437 +++++++++++++++ samples/Luau/createProcessor.luau | 22 - samples/Luau/createSignal.luau | 37 -- samples/Luau/equals.luau | 17 - samples/Luau/getEnv.luau | 39 ++ samples/Luau/isEmpty.luau | 21 - samples/Luau/none.luau | 21 - samples/Luau/replaceMacros.luau | 311 ----------- samples/Luau/timeStepper.luau | 36 -- samples/Luau/toSet.luau | 22 - vendor/CodeMirror | 2 +- vendor/grammars/Handlebars | 2 +- vendor/grammars/MATLAB-Language-grammar | 2 +- vendor/grammars/Nasal.tmbundle | 2 +- vendor/grammars/NimLime | 2 +- vendor/grammars/Terraform.tmLanguage | 2 +- vendor/grammars/TypeScript-TmLanguage | 2 +- vendor/grammars/VscodeAdblockSyntax | 2 +- vendor/grammars/abap-cds-grammar | 2 +- vendor/grammars/abl-tmlanguage | 2 +- vendor/grammars/aidl-language | 2 +- vendor/grammars/astro | 2 +- vendor/grammars/atom-language-julia | 2 +- vendor/grammars/atom-language-perl6 | 2 +- vendor/grammars/bicep | 2 +- vendor/grammars/bikeshed | 2 +- vendor/grammars/cds-textmate-grammar | 2 +- vendor/grammars/csharp-tmLanguage | 2 +- vendor/grammars/d.tmbundle | 2 +- vendor/grammars/d2-vscode | 2 +- vendor/grammars/dart-syntax-highlight | 2 +- vendor/grammars/denizenscript-grammar | 2 +- vendor/grammars/earthfile-grammar | 2 +- vendor/grammars/elixir-tmbundle | 2 +- vendor/grammars/elvish | 2 +- vendor/grammars/gemini-vscode | 2 +- vendor/grammars/godot-vscode-plugin | 2 +- vendor/grammars/graphiql | 2 +- vendor/grammars/ionide-fsgrammar | 2 +- vendor/grammars/language-csound | 2 +- vendor/grammars/language-etc | 2 +- vendor/grammars/language-kotlin | 2 +- vendor/grammars/language-subtitles | 2 +- vendor/grammars/language-viml | 2 +- vendor/grammars/latex.tmbundle | 2 +- vendor/grammars/lua.tmbundle | 2 +- vendor/grammars/markdown-tm-language | 2 +- vendor/grammars/mint-vscode | 2 +- vendor/grammars/nu-grammar | 2 +- vendor/grammars/qsharp-compiler | 2 +- vendor/grammars/rescript-vscode | 2 +- vendor/grammars/rust-syntax | 2 +- vendor/grammars/sas.tmbundle | 2 +- vendor/grammars/selinux-policy-languages | 2 +- vendor/grammars/smithy-vscode | 2 +- vendor/grammars/sourcepawn-vscode | 2 +- vendor/grammars/sublime-odin | 2 +- vendor/grammars/sublime-pony | 2 +- vendor/grammars/sublime-q | 2 +- vendor/grammars/sway-vscode-plugin | 2 +- .../grammars/vscode-antlers-language-server | 2 +- vendor/grammars/vscode-brightscript-language | 2 +- vendor/grammars/vscode-cadence | 2 +- vendor/grammars/vscode-codeql | 2 +- vendor/grammars/vscode-fluent | 2 +- vendor/grammars/vscode-go | 2 +- vendor/grammars/vscode-hack | 2 +- vendor/grammars/vscode-ibmi-languages | 2 +- vendor/grammars/vscode-jest | 2 +- vendor/grammars/vscode-lean | 2 +- vendor/grammars/vscode-motoko | 2 +- vendor/grammars/vscode-move-syntax | 2 +- vendor/grammars/vscode-opa | 2 +- vendor/grammars/vscode-pddl | 2 +- vendor/grammars/vscode-prisma | 2 +- vendor/grammars/vscode-procfile | 2 +- vendor/grammars/vscode-scala-syntax | 2 +- vendor/grammars/vscode-slice | 2 +- vendor/grammars/vscode_cobol | 2 +- vendor/grammars/wgsl-analyzer | 2 +- 83 files changed, 1365 insertions(+), 557 deletions(-) create mode 100644 samples/Luau/EnumList.luau create mode 100644 samples/Luau/Maid.luau create mode 100644 samples/Luau/Option.luau create mode 100644 samples/Luau/Signal.luau delete mode 100644 samples/Luau/createProcessor.luau delete mode 100644 samples/Luau/createSignal.luau delete mode 100644 samples/Luau/equals.luau create mode 100644 samples/Luau/getEnv.luau delete mode 100644 samples/Luau/isEmpty.luau delete mode 100644 samples/Luau/none.luau delete mode 100644 samples/Luau/replaceMacros.luau delete mode 100644 samples/Luau/timeStepper.luau delete mode 100644 samples/Luau/toSet.luau diff --git a/samples/Luau/EnumList.luau b/samples/Luau/EnumList.luau new file mode 100644 index 0000000000..6b3403a9ed --- /dev/null +++ b/samples/Luau/EnumList.luau @@ -0,0 +1,109 @@ +--// Authored by @Sleitnick (https://github.com/sleitnick) +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/enum-list/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +--!optimize 2 +--!strict +--!native + +-- EnumList +-- Stephen Leitnick +-- January 08, 2021 + +type EnumNames = { string } + +--[=[ + @interface EnumItem + .Name string + .Value number + .EnumType EnumList + @within EnumList +]=] +export type EnumItem = { + Name: string, + Value: number, + EnumType: any, +} + +local LIST_KEY = newproxy() +local NAME_KEY = newproxy() + +local function CreateEnumItem(name: string, value: number, enum: any): EnumItem + local enumItem = { + Name = name, + Value = value, + EnumType = enum, + } + table.freeze(enumItem) + return enumItem +end + +--[=[ + @class EnumList + Defines a new Enum. +]=] +local EnumList = {} +EnumList.__index = EnumList + +--[=[ + @param name string + @param enums {string} + @return EnumList + Constructs a new EnumList. + + ```lua + local directions = EnumList.new("Directions", { + "Up", + "Down", + "Left", + "Right", + }) + + local direction = directions.Up + ``` +]=] +function EnumList.new(name: string, enums: EnumNames) + assert(type(name) == "string", "Name string required") + assert(type(enums) == "table", "Enums table required") + local self = {} + self[LIST_KEY] = {} + self[NAME_KEY] = name + for i, enumName in ipairs(enums) do + assert(type(enumName) == "string", "Enum name must be a string") + local enumItem = CreateEnumItem(enumName, i, self) + self[enumName] = enumItem + table.insert(self[LIST_KEY], enumItem) + end + return table.freeze(setmetatable(self, EnumList)) +end + +--[=[ + @param obj any + @return boolean + Returns `true` if `obj` belongs to the EnumList. +]=] +function EnumList:BelongsTo(obj: any): boolean + return type(obj) == "table" and obj.EnumType == self +end + +--[=[ + Returns an array of all enum items. + @return {EnumItem} + @since v2.0.0 +]=] +function EnumList:GetEnumItems() + return self[LIST_KEY] +end + +--[=[ + Get the name of the enum. + @return string + @since v2.0.0 +]=] +function EnumList:GetName() + return self[NAME_KEY] +end + +export type EnumList = typeof(EnumList.new(...)) + +return EnumList diff --git a/samples/Luau/Maid.luau b/samples/Luau/Maid.luau new file mode 100644 index 0000000000..0a6b91cd75 --- /dev/null +++ b/samples/Luau/Maid.luau @@ -0,0 +1,213 @@ +--// Authored by @Dig1t (https://github.com/dig1t) +--// Fetched from (https://github.com/dig1t/dlib/blob/main/src/Maid.luau) +--// Licensed under the MIT License (https://github.com/dig1t/dlib/blob/main/LICENSE) + +--!optimize 2 +--!strict +--!native + +local Util = require(script.Parent.Util) + +type MaidClass = { + __index: MaidClass, + new: () -> MaidType, + task: (self: MaidType, task: any, cleaner: () -> any) -> string, + removeTask: (self: MaidType, taskToRemove: MaidTask) -> nil, + clean: (self: MaidType) -> nil, + destroy: (any) -> nil, + + MaidType: MaidType +} + +type MaidInstance = { + _tasks: { [string]: MaidTask }, + [any]: any +} + +export type MaidType = typeof(setmetatable( + {} :: MaidInstance, + {} :: MaidClass +)) + +type MaidTask = { + Connected: boolean?, + Disconnect: () -> nil?, + Destroy: (any) -> nil?, + destroy: (any) -> nil?, + destructor: (task: any) -> nil?, + [any]: any +}? | () -> nil? | Instance; + +--[=[ + Task management class for cleaning up things for garbage collection. + + @class Maid +]=] +local Maid: MaidClass = {} :: MaidClass +Maid.__index = Maid + +--[=[ + Creates a new Maid instance. + + ```lua + local maid = Maid.new() + ``` + + @return Maid +]=] +function Maid.new(): MaidType + local self = setmetatable({}, Maid) + + self._tasks = {} + + return self +end + +--[=[ + Adds a task to the Maid instance. + + ```lua + local maid = Maid.new() + + maid:task(function() + print("Hello world!") + end) + ``` + + Multiple types of tasks can be added to the Maid instance. + + ```lua + local maid = Maid.new() + + -- Functions + maid:task(function() + print("Hello world!") + end) + + -- RBXScriptConnections + maid:task(workspace.ChildAdded:Connect(function() + print("Hello world!") + end)) + + -- Instances with "Destroy" methods + maid:task(Instance.new("Part")) + + -- Packages with "Destroy", "destroy", or "destructor" methods + local instance = Class.new({ + PackageVariable = "Hello world!", + Destroy = function() + -- destroy this package instance + end + }) + + maid:task(instance) + ``` + + @param _task any -- The task to add. + @return string -- The task id. +]=] +function Maid:task(_task: MaidTask): string + local taskId = Util.randomString(14) + + self._tasks[taskId] = _task + + return taskId +end + +--[=[ + Removes a task from the Maid instance. + + ```lua + local maid = Maid.new() + + local taskId = maid:task(function() + print("Hello world!") + end) + + maid:removeTask(taskId) + ``` + + @param taskToRemove any -- The task item to remove. + @return nil +]=] +function Maid:removeTask(taskToRemove: string | MaidTask): nil + -- Remove by task id + if typeof(taskToRemove) == "string" then + self._tasks[taskToRemove] = nil + + return + end + + -- Remove by task + for taskId, _task: MaidTask in pairs(self._tasks) do + if _task == taskToRemove then + self._tasks[taskId] = nil + end + end + + return +end + +--[=[ + Cleans up all tasks in the Maid instance. + + ```lua + local maid: typeof(Maid.MaidType) = Maid.new() + + maid:task(function() + print("Hello world!") + end) + + maid:clean() -- Hello world! + ``` + + @return nil +]=] +function Maid:clean(): nil + for taskId, _task: MaidTask in pairs(self._tasks) do + if typeof(_task) == "function" then + _task() -- Run cleaning _task + elseif typeof(_task) == "RBXScriptConnection" and _task.Connected then + _task:Disconnect() + elseif typeof(_task) == "Instance" or (_task and _task.Destroy) then + _task:Destroy() + elseif _task and _task.destroy then + _task:destroy() + elseif typeof(_task) == "table" then + if _task.destructor then + _task.destructor(_task.task) + end + end + + self._tasks[taskId] = nil + end + + return +end + +--[=[ + Destroys the Maid instance. + + ```lua + local maid = Maid.new() + + maid:task(function() + print("Hello world!") + end) + + maid:destroy() + + maid:clean() -- method no longer exists + ``` + + @return nil +]=] +function Maid:destroy(): nil + for key: any, _ in pairs(self) do + self[key] = nil + end + + return nil +end + +return Maid diff --git a/samples/Luau/Option.luau b/samples/Luau/Option.luau new file mode 100644 index 0000000000..726b443eac --- /dev/null +++ b/samples/Luau/Option.luau @@ -0,0 +1,497 @@ +--// Authored by @Sleitnick (https://github.com/sleitnick) +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/option/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +--!optimize 2 +--!strict +--!native + +-- Option +-- Stephen Leitnick +-- August 28, 2020 + +--[[ + + MatchTable { + Some: (value: any) -> any + None: () -> any + } + + CONSTRUCTORS: + + Option.Some(anyNonNilValue): Option + Option.Wrap(anyValue): Option + + + STATIC FIELDS: + + Option.None: Option + + + STATIC METHODS: + + Option.Is(obj): boolean + + + METHODS: + + opt:Match(): (matches: MatchTable) -> any + opt:IsSome(): boolean + opt:IsNone(): boolean + opt:Unwrap(): any + opt:Expect(errMsg: string): any + opt:ExpectNone(errMsg: string): void + opt:UnwrapOr(default: any): any + opt:UnwrapOrElse(default: () -> any): any + opt:And(opt2: Option): Option + opt:AndThen(predicate: (unwrapped: any) -> Option): Option + opt:Or(opt2: Option): Option + opt:OrElse(orElseFunc: () -> Option): Option + opt:XOr(opt2: Option): Option + opt:Contains(value: any): boolean + + -------------------------------------------------------------------- + + Options are useful for handling nil-value cases. Any time that an + operation might return nil, it is useful to instead return an + Option, which will indicate that the value might be nil, and should + be explicitly checked before using the value. This will help + prevent common bugs caused by nil values that can fail silently. + + + Example: + + local result1 = Option.Some(32) + local result2 = Option.Some(nil) + local result3 = Option.Some("Hi") + local result4 = Option.Some(nil) + local result5 = Option.None + + -- Use 'Match' to match if the value is Some or None: + result1:Match { + Some = function(value) print(value) end; + None = function() print("No value") end; + } + + -- Raw check: + if result2:IsSome() then + local value = result2:Unwrap() -- Explicitly call Unwrap + print("Value of result2:", value) + end + + if result3:IsNone() then + print("No result for result3") + end + + -- Bad, will throw error bc result4 is none: + local value = result4:Unwrap() + +--]] + +export type MatchTable = { + Some: (value: T) -> any, + None: () -> any, +} + +export type MatchFn = (matches: MatchTable) -> any + +export type DefaultFn = () -> T + +export type AndThenFn = (value: T) -> Option + +export type OrElseFn = () -> Option + +export type Option = typeof(setmetatable( + {} :: { + Match: (self: Option) -> MatchFn, + IsSome: (self: Option) -> boolean, + IsNone: (self: Option) -> boolean, + Contains: (self: Option, value: T) -> boolean, + Unwrap: (self: Option) -> T, + Expect: (self: Option, errMsg: string) -> T, + ExpectNone: (self: Option, errMsg: string) -> nil, + UnwrapOr: (self: Option, default: T) -> T, + UnwrapOrElse: (self: Option, defaultFn: DefaultFn) -> T, + And: (self: Option, opt2: Option) -> Option, + AndThen: (self: Option, predicate: AndThenFn) -> Option, + Or: (self: Option, opt2: Option) -> Option, + OrElse: (self: Option, orElseFunc: OrElseFn) -> Option, + XOr: (self: Option, opt2: Option) -> Option, + }, + {} :: { + __index: Option, + } +)) + +local CLASSNAME = "Option" + +--[=[ + @class Option + + Represents an optional value in Lua. This is useful to avoid `nil` bugs, which can + go silently undetected within code and cause hidden or hard-to-find bugs. +]=] +local Option = {} +Option.__index = Option + +function Option._new(value) + local self = setmetatable({ + ClassName = CLASSNAME, + _v = value, + _s = (value ~= nil), + }, Option) + return self +end + +--[=[ + @param value T + @return Option + + Creates an Option instance with the given value. Throws an error + if the given value is `nil`. +]=] +function Option.Some(value) + assert(value ~= nil, "Option.Some() value cannot be nil") + return Option._new(value) +end + +--[=[ + @param value T + @return Option | Option + + Safely wraps the given value as an option. If the + value is `nil`, returns `Option.None`, otherwise + returns `Option.Some(value)`. +]=] +function Option.Wrap(value) + if value == nil then + return Option.None + else + return Option.Some(value) + end +end + +--[=[ + @param obj any + @return boolean + Returns `true` if `obj` is an Option. +]=] +function Option.Is(obj) + return type(obj) == "table" and getmetatable(obj) == Option +end + +--[=[ + @param obj any + Throws an error if `obj` is not an Option. +]=] +function Option.Assert(obj) + assert(Option.Is(obj), "Result was not of type Option") +end + +--[=[ + @param data table + @return Option + Deserializes the data into an Option. This data should have come from + the `option:Serialize()` method. +]=] +function Option.Deserialize(data) -- type data = {ClassName: string, Value: any} + assert(type(data) == "table" and data.ClassName == CLASSNAME, "Invalid data for deserializing Option") + return data.Value == nil and Option.None or Option.Some(data.Value) +end + +--[=[ + @return table + Returns a serialized version of the option. +]=] +function Option:Serialize() + return { + ClassName = self.ClassName, + Value = self._v, + } +end + +--[=[ + @param matches {Some: (value: any) -> any, None: () -> any} + @return any + + Matches against the option. + + ```lua + local opt = Option.Some(32) + opt:Match { + Some = function(num) print("Number", num) end, + None = function() print("No value") end, + } + ``` +]=] +function Option:Match(matches) + local onSome = matches.Some + local onNone = matches.None + assert(type(onSome) == "function", "Missing 'Some' match") + assert(type(onNone) == "function", "Missing 'None' match") + if self:IsSome() then + return onSome(self:Unwrap()) + else + return onNone() + end +end + +--[=[ + @return boolean + Returns `true` if the option has a value. +]=] +function Option:IsSome() + return self._s +end + +--[=[ + @return boolean + Returns `true` if the option is None. +]=] +function Option:IsNone() + return not self._s +end + +--[=[ + @param msg string + @return value: any + Unwraps the value in the option, otherwise throws an error with `msg` as the error message. + ```lua + local opt = Option.Some(10) + print(opt:Expect("No number")) -> 10 + print(Option.None:Expect("No number")) -- Throws an error "No number" + ``` +]=] +function Option:Expect(msg) + assert(self:IsSome(), msg) + return self._v +end + +--[=[ + @param msg string + Throws an error with `msg` as the error message if the value is _not_ None. +]=] +function Option:ExpectNone(msg) + assert(self:IsNone(), msg) +end + +--[=[ + @return value: any + Returns the value in the option, or throws an error if the option is None. +]=] +function Option:Unwrap() + return self:Expect("Cannot unwrap option of None type") +end + +--[=[ + @param default any + @return value: any + If the option holds a value, returns the value. Otherwise, returns `default`. +]=] +function Option:UnwrapOr(default) + if self:IsSome() then + return self:Unwrap() + else + return default + end +end + +--[=[ + @param defaultFn () -> any + @return value: any + If the option holds a value, returns the value. Otherwise, returns the + result of the `defaultFn` function. +]=] +function Option:UnwrapOrElse(defaultFn) + if self:IsSome() then + return self:Unwrap() + else + return defaultFn() + end +end + +--[=[ + @param optionB Option + @return Option + Returns `optionB` if the calling option has a value, + otherwise returns None. + + ```lua + local optionA = Option.Some(32) + local optionB = Option.Some(64) + local opt = optionA:And(optionB) + -- opt == optionB + + local optionA = Option.None + local optionB = Option.Some(64) + local opt = optionA:And(optionB) + -- opt == Option.None + ``` +]=] +function Option:And(optionB) + if self:IsSome() then + return optionB + else + return Option.None + end +end + +--[=[ + @param andThenFn (value: any) -> Option + @return value: Option + If the option holds a value, then the `andThenFn` + function is called with the held value of the option, + and then the resultant Option returned by the `andThenFn` + is returned. Otherwise, None is returned. + + ```lua + local optA = Option.Some(32) + local optB = optA:AndThen(function(num) + return Option.Some(num * 2) + end) + print(optB:Expect("Expected number")) --> 64 + ``` +]=] +function Option:AndThen(andThenFn) + if self:IsSome() then + local result = andThenFn(self:Unwrap()) + Option.Assert(result) + return result + else + return Option.None + end +end + +--[=[ + @param optionB Option + @return Option + If caller has a value, returns itself. Otherwise, returns `optionB`. +]=] +function Option:Or(optionB) + if self:IsSome() then + return self + else + return optionB + end +end + +--[=[ + @param orElseFn () -> Option + @return Option + If caller has a value, returns itself. Otherwise, returns the + option generated by the `orElseFn` function. +]=] +function Option:OrElse(orElseFn) + if self:IsSome() then + return self + else + local result = orElseFn() + Option.Assert(result) + return result + end +end + +--[=[ + @param optionB Option + @return Option + If both `self` and `optionB` have values _or_ both don't have a value, + then this returns None. Otherwise, it returns the option that does have + a value. +]=] +function Option:XOr(optionB) + local someOptA = self:IsSome() + local someOptB = optionB:IsSome() + if someOptA == someOptB then + return Option.None + elseif someOptA then + return self + else + return optionB + end +end + +--[=[ + @param predicate (value: any) -> boolean + @return Option + Returns `self` if this option has a value and the predicate returns `true. + Otherwise, returns None. +]=] +function Option:Filter(predicate) + if self:IsNone() or not predicate(self._v) then + return Option.None + else + return self + end +end + +--[=[ + @param value any + @return boolean + Returns `true` if this option contains `value`. +]=] +function Option:Contains(value) + return self:IsSome() and self._v == value +end + +--[=[ + @return string + Metamethod to transform the option into a string. + ```lua + local optA = Option.Some(64) + local optB = Option.None + print(optA) --> Option + print(optB) --> Option + ``` +]=] +function Option:__tostring() + if self:IsSome() then + return ("Option<" .. typeof(self._v) .. ">") + else + return "Option" + end +end + +--[=[ + @return boolean + @param opt Option + Metamethod to check equality between two options. Returns `true` if both + options hold the same value _or_ both options are None. + ```lua + local o1 = Option.Some(32) + local o2 = Option.Some(32) + local o3 = Option.Some(64) + local o4 = Option.None + local o5 = Option.None + + print(o1 == o2) --> true + print(o1 == o3) --> false + print(o1 == o4) --> false + print(o4 == o5) --> true + ``` +]=] +function Option:__eq(opt) + if Option.Is(opt) then + if self:IsSome() and opt:IsSome() then + return (self:Unwrap() == opt:Unwrap()) + elseif self:IsNone() and opt:IsNone() then + return true + end + end + return false +end + +--[=[ + @prop None Option + @within Option + Represents no value. +]=] +Option.None = Option._new() + +return (Option :: any) :: { + Some: (value: T) -> Option, + Wrap: (value: T) -> Option, + + Is: (obj: any) -> boolean, + + None: Option, +} diff --git a/samples/Luau/Signal.luau b/samples/Luau/Signal.luau new file mode 100644 index 0000000000..ccf7aebc4f --- /dev/null +++ b/samples/Luau/Signal.luau @@ -0,0 +1,437 @@ +--// Coauthored by @Sleitnick (https://github.com/sleitnick) and @Stravant (https://github.com/stravant) +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/signal/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +--!optimize 2 +--!strict +--!native + +-- ----------------------------------------------------------------------------- +-- Batched Yield-Safe Signal Implementation -- +-- This is a Signal class which has effectively identical behavior to a -- +-- normal RBXScriptSignal, with the only difference being a couple extra -- +-- stack frames at the bottom of the stack trace when an error is thrown. -- +-- This implementation caches runner coroutines, so the ability to yield in -- +-- the signal handlers comes at minimal extra cost over a naive signal -- +-- implementation that either always or never spawns a thread. -- +-- -- +-- License: -- +-- Licensed under the MIT license. -- +-- -- +-- Authors: -- +-- stravant - July 31st, 2021 - Created the file. -- +-- sleitnick - August 3rd, 2021 - Modified for Knit. -- +-- ----------------------------------------------------------------------------- + +-- Signal types +export type Connection = { + Disconnect: (self: Connection) -> (), + Destroy: (self: Connection) -> (), + Connected: boolean, +} + +export type Signal = { + Fire: (self: Signal, T...) -> (), + FireDeferred: (self: Signal, T...) -> (), + Connect: (self: Signal, fn: (T...) -> ()) -> Connection, + Once: (self: Signal, fn: (T...) -> ()) -> Connection, + DisconnectAll: (self: Signal) -> (), + GetConnections: (self: Signal) -> { Connection }, + Destroy: (self: Signal) -> (), + Wait: (self: Signal) -> T..., +} + +-- The currently idle thread to run the next handler on +local freeRunnerThread = nil + +-- Function which acquires the currently idle handler runner thread, runs the +-- function fn on it, and then releases the thread, returning it to being the +-- currently idle one. +-- If there was a currently idle runner thread already, that's okay, that old +-- one will just get thrown and eventually GCed. +local function acquireRunnerThreadAndCallEventHandler(fn, ...) + local acquiredRunnerThread = freeRunnerThread + freeRunnerThread = nil + fn(...) + -- The handler finished running, this runner thread is free again. + freeRunnerThread = acquiredRunnerThread +end + +-- Coroutine runner that we create coroutines of. The coroutine can be +-- repeatedly resumed with functions to run followed by the argument to run +-- them with. +local function runEventHandlerInFreeThread(...) + acquireRunnerThreadAndCallEventHandler(...) + while true do + acquireRunnerThreadAndCallEventHandler(coroutine.yield()) + end +end + +--[=[ + @within Signal + @interface SignalConnection + .Connected boolean + .Disconnect (SignalConnection) -> () + + Represents a connection to a signal. + ```lua + local connection = signal:Connect(function() end) + print(connection.Connected) --> true + connection:Disconnect() + print(connection.Connected) --> false + ``` +]=] + +-- Connection class +local Connection = {} +Connection.__index = Connection + +function Connection:Disconnect() + if not self.Connected then + return + end + self.Connected = false + + -- Unhook the node, but DON'T clear it. That way any fire calls that are + -- currently sitting on this node will be able to iterate forwards off of + -- it, but any subsequent fire calls will not hit it, and it will be GCed + -- when no more fire calls are sitting on it. + if self._signal._handlerListHead == self then + self._signal._handlerListHead = self._next + else + local prev = self._signal._handlerListHead + while prev and prev._next ~= self do + prev = prev._next + end + if prev then + prev._next = self._next + end + end +end + +Connection.Destroy = Connection.Disconnect + +-- Make Connection strict +setmetatable(Connection, { + __index = function(_tb, key) + error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2) + end, + __newindex = function(_tb, key, _value) + error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2) + end, +}) + +--[=[ + @within Signal + @type ConnectionFn (...any) -> () + + A function connected to a signal. +]=] + +--[=[ + @class Signal + + A Signal is a data structure that allows events to be dispatched + and observed. + + This implementation is a direct copy of the de facto standard, [GoodSignal](https://devforum.roblox.com/t/lua-signal-class-comparison-optimal-goodsignal-class/1387063), + with some added methods and typings. + + For example: + ```lua + local signal = Signal.new() + + -- Subscribe to a signal: + signal:Connect(function(msg) + print("Got message:", msg) + end) + + -- Dispatch an event: + signal:Fire("Hello world!") + ``` +]=] +local Signal = {} +Signal.__index = Signal + +--[=[ + Constructs a new Signal + + @return Signal +]=] +function Signal.new(): Signal + local self = setmetatable({ + _handlerListHead = false, + _proxyHandler = nil, + _yieldedThreads = nil, + }, Signal) + + return self +end + +--[=[ + Constructs a new Signal that wraps around an RBXScriptSignal. + + @param rbxScriptSignal RBXScriptSignal -- Existing RBXScriptSignal to wrap + @return Signal + + For example: + ```lua + local signal = Signal.Wrap(workspace.ChildAdded) + signal:Connect(function(part) print(part.Name .. " added") end) + Instance.new("Part").Parent = workspace + ``` +]=] +function Signal.Wrap(rbxScriptSignal: RBXScriptSignal): Signal + assert( + typeof(rbxScriptSignal) == "RBXScriptSignal", + "Argument #1 to Signal.Wrap must be a RBXScriptSignal; got " .. typeof(rbxScriptSignal) + ) + + local signal = Signal.new() + signal._proxyHandler = rbxScriptSignal:Connect(function(...) + signal:Fire(...) + end) + + return signal +end + +--[=[ + Checks if the given object is a Signal. + + @param obj any -- Object to check + @return boolean -- `true` if the object is a Signal. +]=] +function Signal.Is(obj: any): boolean + return type(obj) == "table" and getmetatable(obj) == Signal +end + +--[=[ + @param fn ConnectionFn + @return SignalConnection + + Connects a function to the signal, which will be called anytime the signal is fired. + ```lua + signal:Connect(function(msg, num) + print(msg, num) + end) + + signal:Fire("Hello", 25) + ``` +]=] +function Signal:Connect(fn) + local connection = setmetatable({ + Connected = true, + _signal = self, + _fn = fn, + _next = false, + }, Connection) + + if self._handlerListHead then + connection._next = self._handlerListHead + self._handlerListHead = connection + else + self._handlerListHead = connection + end + + return connection +end + +--[=[ + @deprecated v1.3.0 -- Use `Signal:Once` instead. + @param fn ConnectionFn + @return SignalConnection +]=] +function Signal:ConnectOnce(fn) + return self:Once(fn) +end + +--[=[ + @param fn ConnectionFn + @return SignalConnection + + Connects a function to the signal, which will be called the next time the signal fires. Once + the connection is triggered, it will disconnect itself. + ```lua + signal:Once(function(msg, num) + print(msg, num) + end) + + signal:Fire("Hello", 25) + signal:Fire("This message will not go through", 10) + ``` +]=] +function Signal:Once(fn) + local connection + local done = false + + connection = self:Connect(function(...) + if done then + return + end + + done = true + connection:Disconnect() + fn(...) + end) + + return connection +end + +function Signal:GetConnections() + local items = {} + + local item = self._handlerListHead + while item do + table.insert(items, item) + item = item._next + end + + return items +end + +-- Disconnect all handlers. Since we use a linked list it suffices to clear the +-- reference to the head handler. +--[=[ + Disconnects all connections from the signal. + ```lua + signal:DisconnectAll() + ``` +]=] +function Signal:DisconnectAll() + local item = self._handlerListHead + while item do + item.Connected = false + item = item._next + end + self._handlerListHead = false + + local yieldedThreads = rawget(self, "_yieldedThreads") + if yieldedThreads then + for thread in yieldedThreads do + if coroutine.status(thread) == "suspended" then + warn(debug.traceback(thread, "signal disconnected; yielded thread cancelled", 2)) + task.cancel(thread) + end + end + table.clear(self._yieldedThreads) + end +end + +-- Signal:Fire(...) implemented by running the handler functions on the +-- coRunnerThread, and any time the resulting thread yielded without returning +-- to us, that means that it yielded to the Roblox scheduler and has been taken +-- over by Roblox scheduling, meaning we have to make a new coroutine runner. +--[=[ + @param ... any + + Fire the signal, which will call all of the connected functions with the given arguments. + ```lua + signal:Fire("Hello") + + -- Any number of arguments can be fired: + signal:Fire("Hello", 32, {Test = "Test"}, true) + ``` +]=] +function Signal:Fire(...) + local item = self._handlerListHead + while item do + if item.Connected then + if not freeRunnerThread then + freeRunnerThread = coroutine.create(runEventHandlerInFreeThread) + end + task.spawn(freeRunnerThread, item._fn, ...) + end + item = item._next + end +end + +--[=[ + @param ... any + + Same as `Fire`, but uses `task.defer` internally & doesn't take advantage of thread reuse. + ```lua + signal:FireDeferred("Hello") + ``` +]=] +function Signal:FireDeferred(...) + local item = self._handlerListHead + while item do + local conn = item + task.defer(function(...) + if conn.Connected then + conn._fn(...) + end + end, ...) + item = item._next + end +end + +--[=[ + @return ... any + @yields + + Yields the current thread until the signal is fired, and returns the arguments fired from the signal. + Yielding the current thread is not always desirable. If the desire is to only capture the next event + fired, using `Once` might be a better solution. + ```lua + task.spawn(function() + local msg, num = signal:Wait() + print(msg, num) --> "Hello", 32 + end) + signal:Fire("Hello", 32) + ``` +]=] +function Signal:Wait() + local yieldedThreads = rawget(self, "_yieldedThreads") + if not yieldedThreads then + yieldedThreads = {} + rawset(self, "_yieldedThreads", yieldedThreads) + end + + local thread = coroutine.running() + yieldedThreads[thread] = true + + self:Once(function(...) + yieldedThreads[thread] = nil + task.spawn(thread, ...) + end) + + return coroutine.yield() +end + +--[=[ + Cleans up the signal. + + Technically, this is only necessary if the signal is created using + `Signal.Wrap`. Connections should be properly GC'd once the signal + is no longer referenced anywhere. However, it is still good practice + to include ways to strictly clean up resources. Calling `Destroy` + on a signal will also disconnect all connections immediately. + ```lua + signal:Destroy() + ``` +]=] +function Signal:Destroy() + self:DisconnectAll() + + local proxyHandler = rawget(self, "_proxyHandler") + if proxyHandler then + proxyHandler:Disconnect() + end +end + +-- Make signal strict +setmetatable(Signal, { + __index = function(_tb, key) + error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2) + end, + __newindex = function(_tb, key, _value) + error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2) + end, +}) + +return table.freeze({ + new = Signal.new, + Wrap = Signal.Wrap, + Is = Signal.Is, +}) diff --git a/samples/Luau/createProcessor.luau b/samples/Luau/createProcessor.luau deleted file mode 100644 index d55aba076e..0000000000 --- a/samples/Luau/createProcessor.luau +++ /dev/null @@ -1,22 +0,0 @@ ---// Authored by @sinlerdev (https://github.com/sinlerdev) ---// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/createProcessor.luau) ---// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) - ---!strict -local function createProcessor(valueChecker: (a: any, b: any) -> boolean, cleaner: (taskItem: any) -> ()) - return function(old: T, new: T, isDestroying: boolean) - if isDestroying then - cleaner(old) - return false - end - - if valueChecker(old, new) then - return false - end - - cleaner(old) - return true - end -end - -return createProcessor diff --git a/samples/Luau/createSignal.luau b/samples/Luau/createSignal.luau deleted file mode 100644 index 5ffd461fff..0000000000 --- a/samples/Luau/createSignal.luau +++ /dev/null @@ -1,37 +0,0 @@ ---// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) ---// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/createSignal.luau) ---// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) - ---!strict - -local function createSignal() - local listeners = {} - local signal = {} - - function signal:Connect(listener) - listeners[listener] = true - - local connection = { - Connected = true, - } - - function connection.Disconnect() - connection.Connected = false - listeners[listener] = nil - end - connection.disconnect = connection.Disconnect - - return connection - end - signal.connect = signal.Connect - - local function fire(...) - for listener, _ in pairs(listeners) do - spawn(listener, ...) - end - end - - return signal, fire -end - -return createSignal diff --git a/samples/Luau/equals.luau b/samples/Luau/equals.luau deleted file mode 100644 index 6b259e0359..0000000000 --- a/samples/Luau/equals.luau +++ /dev/null @@ -1,17 +0,0 @@ ---// Authored by @sinlerdev (https://github.com/sinlerdev) ---// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/equals.luau) ---// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) - ---!strict - -local function equals(a: any, b: any): boolean - -- INFO: Vinum doesn't officially support immutability currently- so, we just assume - -- that every new table entry is not equal. See issue 26 - if type(a) == "table" then - return false - end - - return a == b -end - -return equals diff --git a/samples/Luau/getEnv.luau b/samples/Luau/getEnv.luau new file mode 100644 index 0000000000..d4877c3d17 --- /dev/null +++ b/samples/Luau/getEnv.luau @@ -0,0 +1,39 @@ +--// Authored by @Vocksel (https://github.com/vocksel) +--// Fetched from (https://github.com/flipbook-labs/module-loader/blob/main/src/init.lua) +--// Licensed under the MIT License (https://github.com/flipbook-labs/module-loader/blob/main/LICENSE) + +--!optimize 2 +--!strict +--!native + +local baseEnv = getfenv() + +local function getEnv(scriptRelativeTo: LuaSourceContainer?, globals: { [any]: any }?) + local newEnv = {} + + setmetatable(newEnv, { + __index = function(_, key) + if key ~= "plugin" then + return baseEnv[key] + else + return nil + end + end, + }) + + newEnv._G = globals + newEnv.script = scriptRelativeTo + + local realDebug = debug + + newEnv.debug = setmetatable({ + traceback = function(message) + -- Block traces to prevent overly verbose TestEZ output + return message or "" + end, + }, { __index = realDebug }) + + return newEnv +end + +return getEnv diff --git a/samples/Luau/isEmpty.luau b/samples/Luau/isEmpty.luau deleted file mode 100644 index c90e654801..0000000000 --- a/samples/Luau/isEmpty.luau +++ /dev/null @@ -1,21 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/isEmpty.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---!strict ---[=[ - Returns true if the collection is empty. - - ```lua - Freeze.isEmpty({}) - -- true - ``` - - @within Freeze - @function isEmpty - @return boolean -]=] - -return function(collection) - return next(collection) == nil -end diff --git a/samples/Luau/none.luau b/samples/Luau/none.luau deleted file mode 100644 index 118a75d8ce..0000000000 --- a/samples/Luau/none.luau +++ /dev/null @@ -1,21 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/None.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---[=[ - @prop None None - @within Freeze - - Since lua tables cannot distinguish between values not being present and a value of nil, - `Freeze.None` exists to represent values that should be interpreted as `nil`. - - This is useful when removing values with functions such as [`Freeze.Dictionary.merge`](../api/Dictionary#merge). -]=] - -local None = newproxy(true) - -getmetatable(None).__tostring = function() - return "Freeze.None" -end - -return None diff --git a/samples/Luau/replaceMacros.luau b/samples/Luau/replaceMacros.luau deleted file mode 100644 index 11daa0b368..0000000000 --- a/samples/Luau/replaceMacros.luau +++ /dev/null @@ -1,311 +0,0 @@ ---// Authored by @grilme99 (https://github.com/grilme99) ---// Fetched from (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/scripts/replaceMacros.luau) ---// Licensed under the MIT License (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/LICENSE-Brooke) - --- upstream: https://github.com/dead/typeflex/blob/422cb26/tools/repalce_macros.py - -local function YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - if node:getStyle().instanceName ~= paramName then - local style: YGStyle = node:getStyle() - style.instanceName = paramName - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) - if - node:getStyle().instanceName.value ~= YGFloatSanitize(paramName) - or node:getStyle().instanceName.unit ~= YGUnit.Percent - then - local style: YGStyle = node:getStyle() - style.instanceName.value = YGFloatSanitize(paramName) - style.instanceName.unit = if YGFloatIsUndefined(paramName) then YGUnit.Auto else YGUnit.Percent - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -local function YGNodeStyleSet##nameAuto(node: YGNode) - if node:getStyle().instanceName.unit ~= YGUnit.Auto then - local style: YGStyle = node:getStyle() - style.instanceName.value = 0 - style.instanceName.unit = YGUnit.Auto - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - return node:getStyle().instanceName -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, edge: YGEdge, paramName: number) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName[edge].unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName[edge] = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, edge: YGEdge, paramName: number) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent - ) - - if - (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName[edge].unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName[edge] = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -local function YGNodeStyleGet##name(node: YGNode, edge: YGEdge): type - local value: YGValue = node:getStyle().instanceName[edge] - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeStyleSet##nameAuto(node: YGNode, edge: YGEdge) - if node:getStyle().instanceName[edge].unit ~= YGUnit.Auto then - local style: YGStyle = node:getStyle() - style.instanceName[edge].value = 0 - style.instanceName[edge].unit = YGUnit.Auto - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL("number", name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - local value: YGValue = node:getStyle().instanceName - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL("number", name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - local value: YGValue = node:getStyle().instanceName - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_LAYOUT_PROPERTY_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeLayoutGet##name(node: YGNode): type - return node:getLayout().instanceName -end -exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local function YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeLayoutGet##name(node: YGNode, edge: YGEdge): type - -- YGAssertWithNode(node, edge <= YGEdge.End, "Cannot get layout properties of multi-edge shorthands") - - if edge == YGEdge.Start then - if node:getLayout().direction == YGDirection.RTL then - return node:getLayout().instanceName[YGEdge.Right] - else - return node:getLayout().instanceName[YGEdge.Left] - end - end - - if edge == YGEdge.End then - if node:getLayout().direction == YGDirection.RTL then - return node:getLayout().instanceName[YGEdge.Left] - else - return node:getLayout().instanceName[YGEdge.Right] - end - end - - return node:getLayout().instanceName[edge] -end -exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local cod = "" - -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDirection", "Direction", "direction", "direction") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGFlexDirection", "FlexDirection", "flexDirection", "flexDirection") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGJustify", "JustifyContent", "justifyContent", "justifyContent") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignContent", "alignContent", "alignContent") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignItems", "alignItems", "alignItems") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignSelf", "alignSelf", "alignSelf") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGPositionType", "PositionType", "positionType", "positionType") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGWrap", "FlexWrap", "flexWrap", "flexWrap") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGOverflow", "Overflow", "overflow", "overflow") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDisplay", "Display", "display", "display") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Position", "position", "position") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Margin", "margin", "margin") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Padding", "padding", "padding") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Margin", "margin") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Width", "width", "dimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Height", "height", "dimensions[YGDimension.Height]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinWidth", "minWidth", "minDimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinHeight", "minHeight", "minDimensions[YGDimension.Height]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxWidth", "maxWidth", "maxDimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxHeight", "maxHeight", "maxDimensions[YGDimension.Height]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Left", "position[YGEdge.Left]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Top", "position[YGEdge.Top]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Right", "position[YGEdge.Right]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Bottom", "position[YGEdge.Bottom]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Width", "dimensions[YGDimension.Width]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Height", "dimensions[YGDimension.Height]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("YGDirection", "Direction", "direction") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("boolean", "HadOverflow", "hadOverflow") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Margin", "margin") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Border", "border") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Padding", "padding") - -print(cod) \ No newline at end of file diff --git a/samples/Luau/timeStepper.luau b/samples/Luau/timeStepper.luau deleted file mode 100644 index 038a614451..0000000000 --- a/samples/Luau/timeStepper.luau +++ /dev/null @@ -1,36 +0,0 @@ ---// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) ---// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/TimeStepper.luau) ---// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) - ---!strict - ---[[ - -A time stepper is responsible for stepping systems in a deterministic order at a set interval. - -]] - -local TimeStepper = {} -TimeStepper.__index = TimeStepper - -function TimeStepper.new(interval: number, systems) - local self = setmetatable({ - _systems = systems, - _interval = interval, - }, TimeStepper) - - return self -end - -function TimeStepper:start() - coroutine.resume(coroutine.create(function() - while true do - local timeStep, _ = wait(self._interval) - for _, system in ipairs(self._systems) do - system:step(timeStep) - end - end - end)) -end - -return TimeStepper diff --git a/samples/Luau/toSet.luau b/samples/Luau/toSet.luau deleted file mode 100644 index 7d18f99ed2..0000000000 --- a/samples/Luau/toSet.luau +++ /dev/null @@ -1,22 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/List/toSet.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---!strict - ---[=[ - Returns a Set from the given List. - - @within List - @function toSet - @ignore -]=] -return function(list: { Value }): { [Value]: boolean } - local set = {} - - for _, v in list do - set[v] = true - end - - return set -end diff --git a/vendor/CodeMirror b/vendor/CodeMirror index 0c8456c3bc..53faa33ac6 160000 --- a/vendor/CodeMirror +++ b/vendor/CodeMirror @@ -1 +1 @@ -Subproject commit 0c8456c3bc92fb3085ac636f5ed117df24e22ca7 +Subproject commit 53faa33ac69598b7495e160824b58ebb8d70fe97 diff --git a/vendor/grammars/Handlebars b/vendor/grammars/Handlebars index 9fb01fefe4..c2c09947b6 160000 --- a/vendor/grammars/Handlebars +++ b/vendor/grammars/Handlebars @@ -1 +1 @@ -Subproject commit 9fb01fefe48532deb901926cd80774215b454ff9 +Subproject commit c2c09947b6b83d740e9ee94a6e3a0199476f2e15 diff --git a/vendor/grammars/MATLAB-Language-grammar b/vendor/grammars/MATLAB-Language-grammar index f3533822b2..c2c0e4ec8a 160000 --- a/vendor/grammars/MATLAB-Language-grammar +++ b/vendor/grammars/MATLAB-Language-grammar @@ -1 +1 @@ -Subproject commit f3533822b2d740fd4128722854c98b9f1b5d07ee +Subproject commit c2c0e4ec8a401b2c6f76e6069aa0b19da3d0b3bd diff --git a/vendor/grammars/Nasal.tmbundle b/vendor/grammars/Nasal.tmbundle index beb4c5bee4..856bd56438 160000 --- a/vendor/grammars/Nasal.tmbundle +++ b/vendor/grammars/Nasal.tmbundle @@ -1 +1 @@ -Subproject commit beb4c5bee4e83de7cbf25c0cdc25e28489c8a1f7 +Subproject commit 856bd56438b2ca2a82676b461dcebbe74e75600c diff --git a/vendor/grammars/NimLime b/vendor/grammars/NimLime index df69515d85..6297c32c11 160000 --- a/vendor/grammars/NimLime +++ b/vendor/grammars/NimLime @@ -1 +1 @@ -Subproject commit df69515d853380cde7ad58c54e69cf52e725d3bb +Subproject commit 6297c32c11fd92eb9bc3c5cd09a9ff79b7f3d4f9 diff --git a/vendor/grammars/Terraform.tmLanguage b/vendor/grammars/Terraform.tmLanguage index 1576c26c2a..7faec10b40 160000 --- a/vendor/grammars/Terraform.tmLanguage +++ b/vendor/grammars/Terraform.tmLanguage @@ -1 +1 @@ -Subproject commit 1576c26c2ac215f9bed5e1f8a061f123bf63e6b2 +Subproject commit 7faec10b4057b95bff82fb585730e560871d26c4 diff --git a/vendor/grammars/TypeScript-TmLanguage b/vendor/grammars/TypeScript-TmLanguage index 4fdfd38727..82dcf60b94 160000 --- a/vendor/grammars/TypeScript-TmLanguage +++ b/vendor/grammars/TypeScript-TmLanguage @@ -1 +1 @@ -Subproject commit 4fdfd387273124944e32582f9f5af019f515158e +Subproject commit 82dcf60b94c305088165eff5c7e14b07f6048b22 diff --git a/vendor/grammars/VscodeAdblockSyntax b/vendor/grammars/VscodeAdblockSyntax index b4a53bee21..bbd868b428 160000 --- a/vendor/grammars/VscodeAdblockSyntax +++ b/vendor/grammars/VscodeAdblockSyntax @@ -1 +1 @@ -Subproject commit b4a53bee21c0f5074956a7377a36a45febf97d25 +Subproject commit bbd868b4288bf03cb3a2551b5ef35b2103e5a70f diff --git a/vendor/grammars/abap-cds-grammar b/vendor/grammars/abap-cds-grammar index dd803e6ad2..14d892fb0e 160000 --- a/vendor/grammars/abap-cds-grammar +++ b/vendor/grammars/abap-cds-grammar @@ -1 +1 @@ -Subproject commit dd803e6ad2eea202b537a87c0da0536b92f60d3e +Subproject commit 14d892fb0eb8bd98b0340a2b6d265cbeeb938884 diff --git a/vendor/grammars/abl-tmlanguage b/vendor/grammars/abl-tmlanguage index bc85d5f1b0..e928116b41 160000 --- a/vendor/grammars/abl-tmlanguage +++ b/vendor/grammars/abl-tmlanguage @@ -1 +1 @@ -Subproject commit bc85d5f1b0e10ca4a3eb6946d5426d2560a4e328 +Subproject commit e928116b4138bcf191b6f64be5bf92ef9c345d75 diff --git a/vendor/grammars/aidl-language b/vendor/grammars/aidl-language index c2c0074c59..ce07cdf6b9 160000 --- a/vendor/grammars/aidl-language +++ b/vendor/grammars/aidl-language @@ -1 +1 @@ -Subproject commit c2c0074c593954bcd27036db118aee6a13abfa20 +Subproject commit ce07cdf6b9b9dbc4857c4b2fc59a55ac0e17ecd4 diff --git a/vendor/grammars/astro b/vendor/grammars/astro index 1a55b6f122..1e84fac22f 160000 --- a/vendor/grammars/astro +++ b/vendor/grammars/astro @@ -1 +1 @@ -Subproject commit 1a55b6f122f35d1e08716627fad6c8bcfeedab2f +Subproject commit 1e84fac22fb3ed6686ec4d5f963a0420ba2d7d82 diff --git a/vendor/grammars/atom-language-julia b/vendor/grammars/atom-language-julia index 0735fb8026..134a10664d 160000 --- a/vendor/grammars/atom-language-julia +++ b/vendor/grammars/atom-language-julia @@ -1 +1 @@ -Subproject commit 0735fb802696a2b6d89e12ba302d0a85d901bc17 +Subproject commit 134a10664d7b9010af432f4042637c16fd61fe2b diff --git a/vendor/grammars/atom-language-perl6 b/vendor/grammars/atom-language-perl6 index bd341e83c1..8c196e381e 160000 --- a/vendor/grammars/atom-language-perl6 +++ b/vendor/grammars/atom-language-perl6 @@ -1 +1 @@ -Subproject commit bd341e83c14a69b68cb304fb6931abed8473b05a +Subproject commit 8c196e381edb0e5575db611bad4ca7de8b437d66 diff --git a/vendor/grammars/bicep b/vendor/grammars/bicep index 0c0394b9f7..d5f69a7dd2 160000 --- a/vendor/grammars/bicep +++ b/vendor/grammars/bicep @@ -1 +1 @@ -Subproject commit 0c0394b9f70f59b4a2d31dc9cc9b0ba03493685f +Subproject commit d5f69a7dd2e38820b1c8af0fc7ca009a406f6c66 diff --git a/vendor/grammars/bikeshed b/vendor/grammars/bikeshed index 584813e638..4cd224458e 160000 --- a/vendor/grammars/bikeshed +++ b/vendor/grammars/bikeshed @@ -1 +1 @@ -Subproject commit 584813e6380533a19c6656594c810bf974854e68 +Subproject commit 4cd224458e3672f5aa8d521d8ad0ac5658cf1a1c diff --git a/vendor/grammars/cds-textmate-grammar b/vendor/grammars/cds-textmate-grammar index d538952370..b6a736eabd 160000 --- a/vendor/grammars/cds-textmate-grammar +++ b/vendor/grammars/cds-textmate-grammar @@ -1 +1 @@ -Subproject commit d5389523709ba010cf1b9de78c6c0478bab31bde +Subproject commit b6a736eabd7d11ef9a1863cfbc0965e1d9b18c0b diff --git a/vendor/grammars/csharp-tmLanguage b/vendor/grammars/csharp-tmLanguage index 7a7482ffc7..1fc58ea221 160000 --- a/vendor/grammars/csharp-tmLanguage +++ b/vendor/grammars/csharp-tmLanguage @@ -1 +1 @@ -Subproject commit 7a7482ffc72a6677a87eb1ed76005593a4f7f131 +Subproject commit 1fc58ea2212577cd5249f4cd540a07942a1e5806 diff --git a/vendor/grammars/d.tmbundle b/vendor/grammars/d.tmbundle index 9fb354be1c..e031d03ce0 160000 --- a/vendor/grammars/d.tmbundle +++ b/vendor/grammars/d.tmbundle @@ -1 +1 @@ -Subproject commit 9fb354be1c3fbb6a91f543f584d47099d338baf0 +Subproject commit e031d03ce0c2fe0f9e064dad1faf670a19bde482 diff --git a/vendor/grammars/d2-vscode b/vendor/grammars/d2-vscode index d722a70f36..0b4e66fc23 160000 --- a/vendor/grammars/d2-vscode +++ b/vendor/grammars/d2-vscode @@ -1 +1 @@ -Subproject commit d722a70f36e634790eb60e86b678577af0e569c6 +Subproject commit 0b4e66fc235c494a7bf7423d0b9eab3d7bf92833 diff --git a/vendor/grammars/dart-syntax-highlight b/vendor/grammars/dart-syntax-highlight index 1b307d29d4..6ec2cb7fa6 160000 --- a/vendor/grammars/dart-syntax-highlight +++ b/vendor/grammars/dart-syntax-highlight @@ -1 +1 @@ -Subproject commit 1b307d29d454a382f1c143de516335b1a885484b +Subproject commit 6ec2cb7fa65c0e0bed366223920171c0a02db29d diff --git a/vendor/grammars/denizenscript-grammar b/vendor/grammars/denizenscript-grammar index a9a9523202..069cb69265 160000 --- a/vendor/grammars/denizenscript-grammar +++ b/vendor/grammars/denizenscript-grammar @@ -1 +1 @@ -Subproject commit a9a95232026192c0d5eb23557882f9b69a40d66e +Subproject commit 069cb69265ec500e690e8df20a58addc968fbc31 diff --git a/vendor/grammars/earthfile-grammar b/vendor/grammars/earthfile-grammar index 3ab3ea3b51..4afcb6f2cb 160000 --- a/vendor/grammars/earthfile-grammar +++ b/vendor/grammars/earthfile-grammar @@ -1 +1 @@ -Subproject commit 3ab3ea3b512ce54fb71f8070c7e03926ef259c03 +Subproject commit 4afcb6f2cb55d560d94d9ab2b827b6a784d08428 diff --git a/vendor/grammars/elixir-tmbundle b/vendor/grammars/elixir-tmbundle index f6867d6aee..b01fffc491 160000 --- a/vendor/grammars/elixir-tmbundle +++ b/vendor/grammars/elixir-tmbundle @@ -1 +1 @@ -Subproject commit f6867d6aee89c23a9803d958c62eef1c60170e28 +Subproject commit b01fffc49179bdec936ca19b53ba4fc7c51a2cc0 diff --git a/vendor/grammars/elvish b/vendor/grammars/elvish index eadae7fc65..1c0cffbfed 160000 --- a/vendor/grammars/elvish +++ b/vendor/grammars/elvish @@ -1 +1 @@ -Subproject commit eadae7fc651345f925e6e412628e1a463954ae5f +Subproject commit 1c0cffbfed892aa7129ae0a19dad1617255a9faa diff --git a/vendor/grammars/gemini-vscode b/vendor/grammars/gemini-vscode index 72ef757a0c..172351ff9e 160000 --- a/vendor/grammars/gemini-vscode +++ b/vendor/grammars/gemini-vscode @@ -1 +1 @@ -Subproject commit 72ef757a0cb5d1e7137edd97579a92588f7e54cb +Subproject commit 172351ff9e2cbf1296c29ab1ea475cb0fa3344c2 diff --git a/vendor/grammars/godot-vscode-plugin b/vendor/grammars/godot-vscode-plugin index 5cef963162..acfcfdbdab 160000 --- a/vendor/grammars/godot-vscode-plugin +++ b/vendor/grammars/godot-vscode-plugin @@ -1 +1 @@ -Subproject commit 5cef96316208d76795c9763291889b92f2d84d4b +Subproject commit acfcfdbdabff9bed65024d3807ed3c53f5e3a29d diff --git a/vendor/grammars/graphiql b/vendor/grammars/graphiql index ece99f63f5..57b5fcd874 160000 --- a/vendor/grammars/graphiql +++ b/vendor/grammars/graphiql @@ -1 +1 @@ -Subproject commit ece99f63f5d8d01057b735e90a6957edea3e42b9 +Subproject commit 57b5fcd8749bd1c54f4a6a04a9ab07316fd05e71 diff --git a/vendor/grammars/ionide-fsgrammar b/vendor/grammars/ionide-fsgrammar index 7d029a46f1..8740e610a3 160000 --- a/vendor/grammars/ionide-fsgrammar +++ b/vendor/grammars/ionide-fsgrammar @@ -1 +1 @@ -Subproject commit 7d029a46f17637228b2ee85dd02e511c3e8039b3 +Subproject commit 8740e610a367c5e3f15be716acc7207655ced4cf diff --git a/vendor/grammars/language-csound b/vendor/grammars/language-csound index 55beb6eab9..975436138f 160000 --- a/vendor/grammars/language-csound +++ b/vendor/grammars/language-csound @@ -1 +1 @@ -Subproject commit 55beb6eab9ad3783336fbefac52d759245954bc1 +Subproject commit 975436138f2ab808e4a2c3897586ccb0b3c958c2 diff --git a/vendor/grammars/language-etc b/vendor/grammars/language-etc index 1fd00541cc..6d176400d6 160000 --- a/vendor/grammars/language-etc +++ b/vendor/grammars/language-etc @@ -1 +1 @@ -Subproject commit 1fd00541cc0cd25b319c2a749d55e0144a91451c +Subproject commit 6d176400d627b0c723180c46bec862b078250401 diff --git a/vendor/grammars/language-kotlin b/vendor/grammars/language-kotlin index b65a18d4c2..bad0234aa6 160000 --- a/vendor/grammars/language-kotlin +++ b/vendor/grammars/language-kotlin @@ -1 +1 @@ -Subproject commit b65a18d4c23eedcd2bc280cd04282d29d191721a +Subproject commit bad0234aa658d1eb7e18881f71930fd7d70701a4 diff --git a/vendor/grammars/language-subtitles b/vendor/grammars/language-subtitles index 82cf7686f8..70c9d731c1 160000 --- a/vendor/grammars/language-subtitles +++ b/vendor/grammars/language-subtitles @@ -1 +1 @@ -Subproject commit 82cf7686f8f15c19c80e612c8b7da57d87eeb5b7 +Subproject commit 70c9d731c1ba24f058c6630824cf1188cbee9843 diff --git a/vendor/grammars/language-viml b/vendor/grammars/language-viml index 979e1c6223..5030985ab9 160000 --- a/vendor/grammars/language-viml +++ b/vendor/grammars/language-viml @@ -1 +1 @@ -Subproject commit 979e1c62230bbf29fd6963e3620028ccba6848ad +Subproject commit 5030985ab9f5a84a3a18d999d7c3a82f581b0283 diff --git a/vendor/grammars/latex.tmbundle b/vendor/grammars/latex.tmbundle index 90d3383ff8..8e472548d1 160000 --- a/vendor/grammars/latex.tmbundle +++ b/vendor/grammars/latex.tmbundle @@ -1 +1 @@ -Subproject commit 90d3383ff86b7f4495e33c7c5240dde7308376ee +Subproject commit 8e472548d189604712739a925dbdd735069e0668 diff --git a/vendor/grammars/lua.tmbundle b/vendor/grammars/lua.tmbundle index 94ce82cc4d..8ae5641365 160000 --- a/vendor/grammars/lua.tmbundle +++ b/vendor/grammars/lua.tmbundle @@ -1 +1 @@ -Subproject commit 94ce82cc4d45f82641a5252d7a7fd9e28c875adc +Subproject commit 8ae5641365b28f697121ba1133890e8d81f5b00e diff --git a/vendor/grammars/markdown-tm-language b/vendor/grammars/markdown-tm-language index 371d61df9d..e14b65157a 160000 --- a/vendor/grammars/markdown-tm-language +++ b/vendor/grammars/markdown-tm-language @@ -1 +1 @@ -Subproject commit 371d61df9ddc3850e12aabe61b602d02e259e8a4 +Subproject commit e14b65157a0b738430309b14b2f025f3bb37526a diff --git a/vendor/grammars/mint-vscode b/vendor/grammars/mint-vscode index 2de0c6ae29..4ea454c6be 160000 --- a/vendor/grammars/mint-vscode +++ b/vendor/grammars/mint-vscode @@ -1 +1 @@ -Subproject commit 2de0c6ae292a3b23d71fa2eebfbb48f0a0cfb66b +Subproject commit 4ea454c6beff96deccb7abfc6cf04464b3ce8963 diff --git a/vendor/grammars/nu-grammar b/vendor/grammars/nu-grammar index 1ee4b15bd2..03a70db4a8 160000 --- a/vendor/grammars/nu-grammar +++ b/vendor/grammars/nu-grammar @@ -1 +1 @@ -Subproject commit 1ee4b15bd214c951b75270d55c3e273b486f0a75 +Subproject commit 03a70db4a8cb3a0e2113fc63c48b8780b0cecd0d diff --git a/vendor/grammars/qsharp-compiler b/vendor/grammars/qsharp-compiler index 1b4270217a..ce9622e4e4 160000 --- a/vendor/grammars/qsharp-compiler +++ b/vendor/grammars/qsharp-compiler @@ -1 +1 @@ -Subproject commit 1b4270217aff846fb8d2d1f24094bd7bb36514a5 +Subproject commit ce9622e4e49c0fc5e4e887509e1651ac884c1a04 diff --git a/vendor/grammars/rescript-vscode b/vendor/grammars/rescript-vscode index 69bfb269cd..59855be802 160000 --- a/vendor/grammars/rescript-vscode +++ b/vendor/grammars/rescript-vscode @@ -1 +1 @@ -Subproject commit 69bfb269cde531e2d2cd3ad8e6768ae4a7d62d1f +Subproject commit 59855be8021f78e6c24e98ecccdb303498381c5f diff --git a/vendor/grammars/rust-syntax b/vendor/grammars/rust-syntax index cf3c686a50..8f2bc1cb0c 160000 --- a/vendor/grammars/rust-syntax +++ b/vendor/grammars/rust-syntax @@ -1 +1 @@ -Subproject commit cf3c686a50295380ce9994218138691f8767870c +Subproject commit 8f2bc1cb0cf329f1292248bc546b605f01695308 diff --git a/vendor/grammars/sas.tmbundle b/vendor/grammars/sas.tmbundle index 9d84eddcbf..7a70af2284 160000 --- a/vendor/grammars/sas.tmbundle +++ b/vendor/grammars/sas.tmbundle @@ -1 +1 @@ -Subproject commit 9d84eddcbffb86df3091fc88d0983bc33ec358a0 +Subproject commit 7a70af22840d2fe04e17634685bab7b529613822 diff --git a/vendor/grammars/selinux-policy-languages b/vendor/grammars/selinux-policy-languages index 502c96ccf3..7bd17d9c91 160000 --- a/vendor/grammars/selinux-policy-languages +++ b/vendor/grammars/selinux-policy-languages @@ -1 +1 @@ -Subproject commit 502c96ccf38476a627897209bf726fd4e40296b8 +Subproject commit 7bd17d9c9160cd8168a159c6cc574d45cb67b125 diff --git a/vendor/grammars/smithy-vscode b/vendor/grammars/smithy-vscode index 43b3416a4d..f205110486 160000 --- a/vendor/grammars/smithy-vscode +++ b/vendor/grammars/smithy-vscode @@ -1 +1 @@ -Subproject commit 43b3416a4ddedb57c3992b831bed6346e974d7d3 +Subproject commit f205110486d3bac531b0d655992d96d3fd928401 diff --git a/vendor/grammars/sourcepawn-vscode b/vendor/grammars/sourcepawn-vscode index 339ceeb9d3..88a48c8263 160000 --- a/vendor/grammars/sourcepawn-vscode +++ b/vendor/grammars/sourcepawn-vscode @@ -1 +1 @@ -Subproject commit 339ceeb9d3c3c61706a743d14284821f33f04d26 +Subproject commit 88a48c8263acd92cc877d624b3918cecfcb18d03 diff --git a/vendor/grammars/sublime-odin b/vendor/grammars/sublime-odin index c4b626f17c..1746bce31b 160000 --- a/vendor/grammars/sublime-odin +++ b/vendor/grammars/sublime-odin @@ -1 +1 @@ -Subproject commit c4b626f17c97542c922b55a33618aa953cff4771 +Subproject commit 1746bce31b2872906a5fab6282d0855dbefae9a1 diff --git a/vendor/grammars/sublime-pony b/vendor/grammars/sublime-pony index 80f6c4dfbf..13f1b55340 160000 --- a/vendor/grammars/sublime-pony +++ b/vendor/grammars/sublime-pony @@ -1 +1 @@ -Subproject commit 80f6c4dfbff2edc4e8546b33c983f4314de06630 +Subproject commit 13f1b55340ddd46c7dfeac6446030dfe8d105f90 diff --git a/vendor/grammars/sublime-q b/vendor/grammars/sublime-q index 236b80c1f3..c4f98f62b4 160000 --- a/vendor/grammars/sublime-q +++ b/vendor/grammars/sublime-q @@ -1 +1 @@ -Subproject commit 236b80c1f32fbcb16d9ee7487a6f35b62a946af0 +Subproject commit c4f98f62b4911d27611c883c0428694dc984126d diff --git a/vendor/grammars/sway-vscode-plugin b/vendor/grammars/sway-vscode-plugin index a6bb690d0a..3b3b13357b 160000 --- a/vendor/grammars/sway-vscode-plugin +++ b/vendor/grammars/sway-vscode-plugin @@ -1 +1 @@ -Subproject commit a6bb690d0a9839450971eae22a0f207c654126a5 +Subproject commit 3b3b13357b5a5cdc3b353a90e48f500c19767009 diff --git a/vendor/grammars/vscode-antlers-language-server b/vendor/grammars/vscode-antlers-language-server index bff5066893..25b1f58a94 160000 --- a/vendor/grammars/vscode-antlers-language-server +++ b/vendor/grammars/vscode-antlers-language-server @@ -1 +1 @@ -Subproject commit bff50668934abb41b197d75650a13fb5e300132e +Subproject commit 25b1f58a9429697b4a1631324ad8583dfa64270b diff --git a/vendor/grammars/vscode-brightscript-language b/vendor/grammars/vscode-brightscript-language index b05f3a31a8..bdae42c2e4 160000 --- a/vendor/grammars/vscode-brightscript-language +++ b/vendor/grammars/vscode-brightscript-language @@ -1 +1 @@ -Subproject commit b05f3a31a8877a8fa0e00ff554a3e98328050c44 +Subproject commit bdae42c2e4a42ec8504aa29e6bbbbf8e6d8755a2 diff --git a/vendor/grammars/vscode-cadence b/vendor/grammars/vscode-cadence index 2ce921581a..650029d38c 160000 --- a/vendor/grammars/vscode-cadence +++ b/vendor/grammars/vscode-cadence @@ -1 +1 @@ -Subproject commit 2ce921581a804fa32317b829c54c1c0fd904975a +Subproject commit 650029d38c6ac2cddb7ce9fd4054461f4b140f5a diff --git a/vendor/grammars/vscode-codeql b/vendor/grammars/vscode-codeql index 3005dacf4e..93fc90fcd0 160000 --- a/vendor/grammars/vscode-codeql +++ b/vendor/grammars/vscode-codeql @@ -1 +1 @@ -Subproject commit 3005dacf4ed480aa76e541b7d3697f5a34571faf +Subproject commit 93fc90fcd081f8d049440f3575e1bbbe74ff2db7 diff --git a/vendor/grammars/vscode-fluent b/vendor/grammars/vscode-fluent index 51ceeb59d6..cae702ed1b 160000 --- a/vendor/grammars/vscode-fluent +++ b/vendor/grammars/vscode-fluent @@ -1 +1 @@ -Subproject commit 51ceeb59d645d0d1ec9b4080175bc1ce4d709075 +Subproject commit cae702ed1b3a6a2b6a58260a8e1194bc40421218 diff --git a/vendor/grammars/vscode-go b/vendor/grammars/vscode-go index b4b68a76fa..d9015c19ed 160000 --- a/vendor/grammars/vscode-go +++ b/vendor/grammars/vscode-go @@ -1 +1 @@ -Subproject commit b4b68a76fac190ca12179ef9ef24ecffc57ea9d6 +Subproject commit d9015c19ed5be58bb51f3c53b651fe2468540086 diff --git a/vendor/grammars/vscode-hack b/vendor/grammars/vscode-hack index dd79d7146c..d75dd72a5d 160000 --- a/vendor/grammars/vscode-hack +++ b/vendor/grammars/vscode-hack @@ -1 +1 @@ -Subproject commit dd79d7146ccffb3e0a1020e9f8790242c37b6c21 +Subproject commit d75dd72a5d52436d208a627a2ead5423c94eb3e9 diff --git a/vendor/grammars/vscode-ibmi-languages b/vendor/grammars/vscode-ibmi-languages index 7ed423b793..e9d72cece7 160000 --- a/vendor/grammars/vscode-ibmi-languages +++ b/vendor/grammars/vscode-ibmi-languages @@ -1 +1 @@ -Subproject commit 7ed423b7930e652f8b3fdf36d3f77d6e122ced41 +Subproject commit e9d72cece714ae968802802f09bdc089f79d7d4b diff --git a/vendor/grammars/vscode-jest b/vendor/grammars/vscode-jest index 0c57873f91..ad719cb5b9 160000 --- a/vendor/grammars/vscode-jest +++ b/vendor/grammars/vscode-jest @@ -1 +1 @@ -Subproject commit 0c57873f91138961f90fedeb5fd1f0da9d45c601 +Subproject commit ad719cb5b9509715047f1cae8ea7204db5f8faa5 diff --git a/vendor/grammars/vscode-lean b/vendor/grammars/vscode-lean index 1589ca3a65..0696b29285 160000 --- a/vendor/grammars/vscode-lean +++ b/vendor/grammars/vscode-lean @@ -1 +1 @@ -Subproject commit 1589ca3a65e394b3789409707febbd2d166c9344 +Subproject commit 0696b292855c7cae711e00d69c79b2687c336f25 diff --git a/vendor/grammars/vscode-motoko b/vendor/grammars/vscode-motoko index 1d256baa0c..4ecca55b8d 160000 --- a/vendor/grammars/vscode-motoko +++ b/vendor/grammars/vscode-motoko @@ -1 +1 @@ -Subproject commit 1d256baa0cfec3cb3083c519d926a0453f201623 +Subproject commit 4ecca55b8dd1fcebce92c80e67f0dcaf9ac4fdf3 diff --git a/vendor/grammars/vscode-move-syntax b/vendor/grammars/vscode-move-syntax index f9436ad72c..de9244e815 160000 --- a/vendor/grammars/vscode-move-syntax +++ b/vendor/grammars/vscode-move-syntax @@ -1 +1 @@ -Subproject commit f9436ad72cc7d3c6848eb02759c808ae0663295d +Subproject commit de9244e81505ff9154dc9422dbce434ac86fb981 diff --git a/vendor/grammars/vscode-opa b/vendor/grammars/vscode-opa index dcf4476992..0282a611d1 160000 --- a/vendor/grammars/vscode-opa +++ b/vendor/grammars/vscode-opa @@ -1 +1 @@ -Subproject commit dcf447699243ef2984d8dcb0b009b35b3fe74400 +Subproject commit 0282a611d1c3e70ffc353fd2003f0027a1c9ccfe diff --git a/vendor/grammars/vscode-pddl b/vendor/grammars/vscode-pddl index 73b0cb1b72..4fc7fc6d1a 160000 --- a/vendor/grammars/vscode-pddl +++ b/vendor/grammars/vscode-pddl @@ -1 +1 @@ -Subproject commit 73b0cb1b72c72ff795f173ba18e9f4ae810b0250 +Subproject commit 4fc7fc6d1a5f1378ea5039f77e8074da1a882918 diff --git a/vendor/grammars/vscode-prisma b/vendor/grammars/vscode-prisma index bf28282fd0..0b8d95ba11 160000 --- a/vendor/grammars/vscode-prisma +++ b/vendor/grammars/vscode-prisma @@ -1 +1 @@ -Subproject commit bf28282fd07c675b79282cafb63b4912bc6c7156 +Subproject commit 0b8d95ba11276ba694b73dcf568a747d13a882e5 diff --git a/vendor/grammars/vscode-procfile b/vendor/grammars/vscode-procfile index 4e149fd5e7..b1ddcf5c34 160000 --- a/vendor/grammars/vscode-procfile +++ b/vendor/grammars/vscode-procfile @@ -1 +1 @@ -Subproject commit 4e149fd5e757352aa5867b59934acc16e19182a1 +Subproject commit b1ddcf5c349acaa005fabdcf7111f34443d552f3 diff --git a/vendor/grammars/vscode-scala-syntax b/vendor/grammars/vscode-scala-syntax index fb73e8a0bf..d9036418a5 160000 --- a/vendor/grammars/vscode-scala-syntax +++ b/vendor/grammars/vscode-scala-syntax @@ -1 +1 @@ -Subproject commit fb73e8a0bfcd9a3c45f2c1b712e00c35865a9178 +Subproject commit d9036418a5e1f5c0cb7e3225e7bd05753d4934d6 diff --git a/vendor/grammars/vscode-slice b/vendor/grammars/vscode-slice index a4aacfa8bc..45660f594c 160000 --- a/vendor/grammars/vscode-slice +++ b/vendor/grammars/vscode-slice @@ -1 +1 @@ -Subproject commit a4aacfa8bc1ec6cc925dfd891dd75dfbf9df207d +Subproject commit 45660f594c4dc3b4eaf9f14b5a6cd7a5ce01aee2 diff --git a/vendor/grammars/vscode_cobol b/vendor/grammars/vscode_cobol index b09f64b00e..8bded749c0 160000 --- a/vendor/grammars/vscode_cobol +++ b/vendor/grammars/vscode_cobol @@ -1 +1 @@ -Subproject commit b09f64b00eee66dca496a69ea68f4c2c5c78e359 +Subproject commit 8bded749c0cf77ffd665b73afbe7ff8b7bc7f938 diff --git a/vendor/grammars/wgsl-analyzer b/vendor/grammars/wgsl-analyzer index 8851962fc1..92219fd398 160000 --- a/vendor/grammars/wgsl-analyzer +++ b/vendor/grammars/wgsl-analyzer @@ -1 +1 @@ -Subproject commit 8851962fc191aa5ea85a25b0ebd10cb9f70627b3 +Subproject commit 92219fd3987158b8399d0b2c1dfb426af77f9c51 From deb5f0047c845dc5e49aaf8b8ce7ce25c74caefd Mon Sep 17 00:00:00 2001 From: robloxiandemo <76854027+robloxiandemo@users.noreply.github.com> Date: Sat, 6 Apr 2024 17:27:35 -0400 Subject: [PATCH 25/29] Patch: Update old samples and their sources. --- samples/Luau/EnumList.luau | 109 ++ samples/Luau/Maid.luau | 213 +++ samples/Luau/Option.luau | 497 +++++ samples/Luau/Promise.luau | 2243 +++++++++++++++++++++++ samples/Luau/ReactRobloxHostConfig.luau | 1366 ++++++++++++++ samples/Luau/Replicator.luau | 546 ++++++ samples/Luau/Signal.luau | 437 +++++ samples/Luau/createProcessor.luau | 22 - samples/Luau/createSignal.luau | 37 - samples/Luau/equals.luau | 17 - samples/Luau/getEnv.luau | 39 + samples/Luau/isEmpty.luau | 21 - samples/Luau/none.luau | 21 - samples/Luau/pathToRegexp.luau | 854 +++++++++ samples/Luau/replaceMacros.luau | 311 ---- samples/Luau/roblox.luau | 512 ++++++ samples/Luau/timeStepper.luau | 36 - samples/Luau/toSet.luau | 22 - 18 files changed, 6816 insertions(+), 487 deletions(-) create mode 100644 samples/Luau/EnumList.luau create mode 100644 samples/Luau/Maid.luau create mode 100644 samples/Luau/Option.luau create mode 100644 samples/Luau/Promise.luau create mode 100644 samples/Luau/ReactRobloxHostConfig.luau create mode 100644 samples/Luau/Replicator.luau create mode 100644 samples/Luau/Signal.luau delete mode 100644 samples/Luau/createProcessor.luau delete mode 100644 samples/Luau/createSignal.luau delete mode 100644 samples/Luau/equals.luau create mode 100644 samples/Luau/getEnv.luau delete mode 100644 samples/Luau/isEmpty.luau delete mode 100644 samples/Luau/none.luau create mode 100644 samples/Luau/pathToRegexp.luau delete mode 100644 samples/Luau/replaceMacros.luau create mode 100644 samples/Luau/roblox.luau delete mode 100644 samples/Luau/timeStepper.luau delete mode 100644 samples/Luau/toSet.luau diff --git a/samples/Luau/EnumList.luau b/samples/Luau/EnumList.luau new file mode 100644 index 0000000000..6b3403a9ed --- /dev/null +++ b/samples/Luau/EnumList.luau @@ -0,0 +1,109 @@ +--// Authored by @Sleitnick (https://github.com/sleitnick) +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/enum-list/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +--!optimize 2 +--!strict +--!native + +-- EnumList +-- Stephen Leitnick +-- January 08, 2021 + +type EnumNames = { string } + +--[=[ + @interface EnumItem + .Name string + .Value number + .EnumType EnumList + @within EnumList +]=] +export type EnumItem = { + Name: string, + Value: number, + EnumType: any, +} + +local LIST_KEY = newproxy() +local NAME_KEY = newproxy() + +local function CreateEnumItem(name: string, value: number, enum: any): EnumItem + local enumItem = { + Name = name, + Value = value, + EnumType = enum, + } + table.freeze(enumItem) + return enumItem +end + +--[=[ + @class EnumList + Defines a new Enum. +]=] +local EnumList = {} +EnumList.__index = EnumList + +--[=[ + @param name string + @param enums {string} + @return EnumList + Constructs a new EnumList. + + ```lua + local directions = EnumList.new("Directions", { + "Up", + "Down", + "Left", + "Right", + }) + + local direction = directions.Up + ``` +]=] +function EnumList.new(name: string, enums: EnumNames) + assert(type(name) == "string", "Name string required") + assert(type(enums) == "table", "Enums table required") + local self = {} + self[LIST_KEY] = {} + self[NAME_KEY] = name + for i, enumName in ipairs(enums) do + assert(type(enumName) == "string", "Enum name must be a string") + local enumItem = CreateEnumItem(enumName, i, self) + self[enumName] = enumItem + table.insert(self[LIST_KEY], enumItem) + end + return table.freeze(setmetatable(self, EnumList)) +end + +--[=[ + @param obj any + @return boolean + Returns `true` if `obj` belongs to the EnumList. +]=] +function EnumList:BelongsTo(obj: any): boolean + return type(obj) == "table" and obj.EnumType == self +end + +--[=[ + Returns an array of all enum items. + @return {EnumItem} + @since v2.0.0 +]=] +function EnumList:GetEnumItems() + return self[LIST_KEY] +end + +--[=[ + Get the name of the enum. + @return string + @since v2.0.0 +]=] +function EnumList:GetName() + return self[NAME_KEY] +end + +export type EnumList = typeof(EnumList.new(...)) + +return EnumList diff --git a/samples/Luau/Maid.luau b/samples/Luau/Maid.luau new file mode 100644 index 0000000000..0a6b91cd75 --- /dev/null +++ b/samples/Luau/Maid.luau @@ -0,0 +1,213 @@ +--// Authored by @Dig1t (https://github.com/dig1t) +--// Fetched from (https://github.com/dig1t/dlib/blob/main/src/Maid.luau) +--// Licensed under the MIT License (https://github.com/dig1t/dlib/blob/main/LICENSE) + +--!optimize 2 +--!strict +--!native + +local Util = require(script.Parent.Util) + +type MaidClass = { + __index: MaidClass, + new: () -> MaidType, + task: (self: MaidType, task: any, cleaner: () -> any) -> string, + removeTask: (self: MaidType, taskToRemove: MaidTask) -> nil, + clean: (self: MaidType) -> nil, + destroy: (any) -> nil, + + MaidType: MaidType +} + +type MaidInstance = { + _tasks: { [string]: MaidTask }, + [any]: any +} + +export type MaidType = typeof(setmetatable( + {} :: MaidInstance, + {} :: MaidClass +)) + +type MaidTask = { + Connected: boolean?, + Disconnect: () -> nil?, + Destroy: (any) -> nil?, + destroy: (any) -> nil?, + destructor: (task: any) -> nil?, + [any]: any +}? | () -> nil? | Instance; + +--[=[ + Task management class for cleaning up things for garbage collection. + + @class Maid +]=] +local Maid: MaidClass = {} :: MaidClass +Maid.__index = Maid + +--[=[ + Creates a new Maid instance. + + ```lua + local maid = Maid.new() + ``` + + @return Maid +]=] +function Maid.new(): MaidType + local self = setmetatable({}, Maid) + + self._tasks = {} + + return self +end + +--[=[ + Adds a task to the Maid instance. + + ```lua + local maid = Maid.new() + + maid:task(function() + print("Hello world!") + end) + ``` + + Multiple types of tasks can be added to the Maid instance. + + ```lua + local maid = Maid.new() + + -- Functions + maid:task(function() + print("Hello world!") + end) + + -- RBXScriptConnections + maid:task(workspace.ChildAdded:Connect(function() + print("Hello world!") + end)) + + -- Instances with "Destroy" methods + maid:task(Instance.new("Part")) + + -- Packages with "Destroy", "destroy", or "destructor" methods + local instance = Class.new({ + PackageVariable = "Hello world!", + Destroy = function() + -- destroy this package instance + end + }) + + maid:task(instance) + ``` + + @param _task any -- The task to add. + @return string -- The task id. +]=] +function Maid:task(_task: MaidTask): string + local taskId = Util.randomString(14) + + self._tasks[taskId] = _task + + return taskId +end + +--[=[ + Removes a task from the Maid instance. + + ```lua + local maid = Maid.new() + + local taskId = maid:task(function() + print("Hello world!") + end) + + maid:removeTask(taskId) + ``` + + @param taskToRemove any -- The task item to remove. + @return nil +]=] +function Maid:removeTask(taskToRemove: string | MaidTask): nil + -- Remove by task id + if typeof(taskToRemove) == "string" then + self._tasks[taskToRemove] = nil + + return + end + + -- Remove by task + for taskId, _task: MaidTask in pairs(self._tasks) do + if _task == taskToRemove then + self._tasks[taskId] = nil + end + end + + return +end + +--[=[ + Cleans up all tasks in the Maid instance. + + ```lua + local maid: typeof(Maid.MaidType) = Maid.new() + + maid:task(function() + print("Hello world!") + end) + + maid:clean() -- Hello world! + ``` + + @return nil +]=] +function Maid:clean(): nil + for taskId, _task: MaidTask in pairs(self._tasks) do + if typeof(_task) == "function" then + _task() -- Run cleaning _task + elseif typeof(_task) == "RBXScriptConnection" and _task.Connected then + _task:Disconnect() + elseif typeof(_task) == "Instance" or (_task and _task.Destroy) then + _task:Destroy() + elseif _task and _task.destroy then + _task:destroy() + elseif typeof(_task) == "table" then + if _task.destructor then + _task.destructor(_task.task) + end + end + + self._tasks[taskId] = nil + end + + return +end + +--[=[ + Destroys the Maid instance. + + ```lua + local maid = Maid.new() + + maid:task(function() + print("Hello world!") + end) + + maid:destroy() + + maid:clean() -- method no longer exists + ``` + + @return nil +]=] +function Maid:destroy(): nil + for key: any, _ in pairs(self) do + self[key] = nil + end + + return nil +end + +return Maid diff --git a/samples/Luau/Option.luau b/samples/Luau/Option.luau new file mode 100644 index 0000000000..726b443eac --- /dev/null +++ b/samples/Luau/Option.luau @@ -0,0 +1,497 @@ +--// Authored by @Sleitnick (https://github.com/sleitnick) +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/option/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +--!optimize 2 +--!strict +--!native + +-- Option +-- Stephen Leitnick +-- August 28, 2020 + +--[[ + + MatchTable { + Some: (value: any) -> any + None: () -> any + } + + CONSTRUCTORS: + + Option.Some(anyNonNilValue): Option + Option.Wrap(anyValue): Option + + + STATIC FIELDS: + + Option.None: Option + + + STATIC METHODS: + + Option.Is(obj): boolean + + + METHODS: + + opt:Match(): (matches: MatchTable) -> any + opt:IsSome(): boolean + opt:IsNone(): boolean + opt:Unwrap(): any + opt:Expect(errMsg: string): any + opt:ExpectNone(errMsg: string): void + opt:UnwrapOr(default: any): any + opt:UnwrapOrElse(default: () -> any): any + opt:And(opt2: Option): Option + opt:AndThen(predicate: (unwrapped: any) -> Option): Option + opt:Or(opt2: Option): Option + opt:OrElse(orElseFunc: () -> Option): Option + opt:XOr(opt2: Option): Option + opt:Contains(value: any): boolean + + -------------------------------------------------------------------- + + Options are useful for handling nil-value cases. Any time that an + operation might return nil, it is useful to instead return an + Option, which will indicate that the value might be nil, and should + be explicitly checked before using the value. This will help + prevent common bugs caused by nil values that can fail silently. + + + Example: + + local result1 = Option.Some(32) + local result2 = Option.Some(nil) + local result3 = Option.Some("Hi") + local result4 = Option.Some(nil) + local result5 = Option.None + + -- Use 'Match' to match if the value is Some or None: + result1:Match { + Some = function(value) print(value) end; + None = function() print("No value") end; + } + + -- Raw check: + if result2:IsSome() then + local value = result2:Unwrap() -- Explicitly call Unwrap + print("Value of result2:", value) + end + + if result3:IsNone() then + print("No result for result3") + end + + -- Bad, will throw error bc result4 is none: + local value = result4:Unwrap() + +--]] + +export type MatchTable = { + Some: (value: T) -> any, + None: () -> any, +} + +export type MatchFn = (matches: MatchTable) -> any + +export type DefaultFn = () -> T + +export type AndThenFn = (value: T) -> Option + +export type OrElseFn = () -> Option + +export type Option = typeof(setmetatable( + {} :: { + Match: (self: Option) -> MatchFn, + IsSome: (self: Option) -> boolean, + IsNone: (self: Option) -> boolean, + Contains: (self: Option, value: T) -> boolean, + Unwrap: (self: Option) -> T, + Expect: (self: Option, errMsg: string) -> T, + ExpectNone: (self: Option, errMsg: string) -> nil, + UnwrapOr: (self: Option, default: T) -> T, + UnwrapOrElse: (self: Option, defaultFn: DefaultFn) -> T, + And: (self: Option, opt2: Option) -> Option, + AndThen: (self: Option, predicate: AndThenFn) -> Option, + Or: (self: Option, opt2: Option) -> Option, + OrElse: (self: Option, orElseFunc: OrElseFn) -> Option, + XOr: (self: Option, opt2: Option) -> Option, + }, + {} :: { + __index: Option, + } +)) + +local CLASSNAME = "Option" + +--[=[ + @class Option + + Represents an optional value in Lua. This is useful to avoid `nil` bugs, which can + go silently undetected within code and cause hidden or hard-to-find bugs. +]=] +local Option = {} +Option.__index = Option + +function Option._new(value) + local self = setmetatable({ + ClassName = CLASSNAME, + _v = value, + _s = (value ~= nil), + }, Option) + return self +end + +--[=[ + @param value T + @return Option + + Creates an Option instance with the given value. Throws an error + if the given value is `nil`. +]=] +function Option.Some(value) + assert(value ~= nil, "Option.Some() value cannot be nil") + return Option._new(value) +end + +--[=[ + @param value T + @return Option | Option + + Safely wraps the given value as an option. If the + value is `nil`, returns `Option.None`, otherwise + returns `Option.Some(value)`. +]=] +function Option.Wrap(value) + if value == nil then + return Option.None + else + return Option.Some(value) + end +end + +--[=[ + @param obj any + @return boolean + Returns `true` if `obj` is an Option. +]=] +function Option.Is(obj) + return type(obj) == "table" and getmetatable(obj) == Option +end + +--[=[ + @param obj any + Throws an error if `obj` is not an Option. +]=] +function Option.Assert(obj) + assert(Option.Is(obj), "Result was not of type Option") +end + +--[=[ + @param data table + @return Option + Deserializes the data into an Option. This data should have come from + the `option:Serialize()` method. +]=] +function Option.Deserialize(data) -- type data = {ClassName: string, Value: any} + assert(type(data) == "table" and data.ClassName == CLASSNAME, "Invalid data for deserializing Option") + return data.Value == nil and Option.None or Option.Some(data.Value) +end + +--[=[ + @return table + Returns a serialized version of the option. +]=] +function Option:Serialize() + return { + ClassName = self.ClassName, + Value = self._v, + } +end + +--[=[ + @param matches {Some: (value: any) -> any, None: () -> any} + @return any + + Matches against the option. + + ```lua + local opt = Option.Some(32) + opt:Match { + Some = function(num) print("Number", num) end, + None = function() print("No value") end, + } + ``` +]=] +function Option:Match(matches) + local onSome = matches.Some + local onNone = matches.None + assert(type(onSome) == "function", "Missing 'Some' match") + assert(type(onNone) == "function", "Missing 'None' match") + if self:IsSome() then + return onSome(self:Unwrap()) + else + return onNone() + end +end + +--[=[ + @return boolean + Returns `true` if the option has a value. +]=] +function Option:IsSome() + return self._s +end + +--[=[ + @return boolean + Returns `true` if the option is None. +]=] +function Option:IsNone() + return not self._s +end + +--[=[ + @param msg string + @return value: any + Unwraps the value in the option, otherwise throws an error with `msg` as the error message. + ```lua + local opt = Option.Some(10) + print(opt:Expect("No number")) -> 10 + print(Option.None:Expect("No number")) -- Throws an error "No number" + ``` +]=] +function Option:Expect(msg) + assert(self:IsSome(), msg) + return self._v +end + +--[=[ + @param msg string + Throws an error with `msg` as the error message if the value is _not_ None. +]=] +function Option:ExpectNone(msg) + assert(self:IsNone(), msg) +end + +--[=[ + @return value: any + Returns the value in the option, or throws an error if the option is None. +]=] +function Option:Unwrap() + return self:Expect("Cannot unwrap option of None type") +end + +--[=[ + @param default any + @return value: any + If the option holds a value, returns the value. Otherwise, returns `default`. +]=] +function Option:UnwrapOr(default) + if self:IsSome() then + return self:Unwrap() + else + return default + end +end + +--[=[ + @param defaultFn () -> any + @return value: any + If the option holds a value, returns the value. Otherwise, returns the + result of the `defaultFn` function. +]=] +function Option:UnwrapOrElse(defaultFn) + if self:IsSome() then + return self:Unwrap() + else + return defaultFn() + end +end + +--[=[ + @param optionB Option + @return Option + Returns `optionB` if the calling option has a value, + otherwise returns None. + + ```lua + local optionA = Option.Some(32) + local optionB = Option.Some(64) + local opt = optionA:And(optionB) + -- opt == optionB + + local optionA = Option.None + local optionB = Option.Some(64) + local opt = optionA:And(optionB) + -- opt == Option.None + ``` +]=] +function Option:And(optionB) + if self:IsSome() then + return optionB + else + return Option.None + end +end + +--[=[ + @param andThenFn (value: any) -> Option + @return value: Option + If the option holds a value, then the `andThenFn` + function is called with the held value of the option, + and then the resultant Option returned by the `andThenFn` + is returned. Otherwise, None is returned. + + ```lua + local optA = Option.Some(32) + local optB = optA:AndThen(function(num) + return Option.Some(num * 2) + end) + print(optB:Expect("Expected number")) --> 64 + ``` +]=] +function Option:AndThen(andThenFn) + if self:IsSome() then + local result = andThenFn(self:Unwrap()) + Option.Assert(result) + return result + else + return Option.None + end +end + +--[=[ + @param optionB Option + @return Option + If caller has a value, returns itself. Otherwise, returns `optionB`. +]=] +function Option:Or(optionB) + if self:IsSome() then + return self + else + return optionB + end +end + +--[=[ + @param orElseFn () -> Option + @return Option + If caller has a value, returns itself. Otherwise, returns the + option generated by the `orElseFn` function. +]=] +function Option:OrElse(orElseFn) + if self:IsSome() then + return self + else + local result = orElseFn() + Option.Assert(result) + return result + end +end + +--[=[ + @param optionB Option + @return Option + If both `self` and `optionB` have values _or_ both don't have a value, + then this returns None. Otherwise, it returns the option that does have + a value. +]=] +function Option:XOr(optionB) + local someOptA = self:IsSome() + local someOptB = optionB:IsSome() + if someOptA == someOptB then + return Option.None + elseif someOptA then + return self + else + return optionB + end +end + +--[=[ + @param predicate (value: any) -> boolean + @return Option + Returns `self` if this option has a value and the predicate returns `true. + Otherwise, returns None. +]=] +function Option:Filter(predicate) + if self:IsNone() or not predicate(self._v) then + return Option.None + else + return self + end +end + +--[=[ + @param value any + @return boolean + Returns `true` if this option contains `value`. +]=] +function Option:Contains(value) + return self:IsSome() and self._v == value +end + +--[=[ + @return string + Metamethod to transform the option into a string. + ```lua + local optA = Option.Some(64) + local optB = Option.None + print(optA) --> Option + print(optB) --> Option + ``` +]=] +function Option:__tostring() + if self:IsSome() then + return ("Option<" .. typeof(self._v) .. ">") + else + return "Option" + end +end + +--[=[ + @return boolean + @param opt Option + Metamethod to check equality between two options. Returns `true` if both + options hold the same value _or_ both options are None. + ```lua + local o1 = Option.Some(32) + local o2 = Option.Some(32) + local o3 = Option.Some(64) + local o4 = Option.None + local o5 = Option.None + + print(o1 == o2) --> true + print(o1 == o3) --> false + print(o1 == o4) --> false + print(o4 == o5) --> true + ``` +]=] +function Option:__eq(opt) + if Option.Is(opt) then + if self:IsSome() and opt:IsSome() then + return (self:Unwrap() == opt:Unwrap()) + elseif self:IsNone() and opt:IsNone() then + return true + end + end + return false +end + +--[=[ + @prop None Option + @within Option + Represents no value. +]=] +Option.None = Option._new() + +return (Option :: any) :: { + Some: (value: T) -> Option, + Wrap: (value: T) -> Option, + + Is: (obj: any) -> boolean, + + None: Option, +} diff --git a/samples/Luau/Promise.luau b/samples/Luau/Promise.luau new file mode 100644 index 0000000000..fb7c265b43 --- /dev/null +++ b/samples/Luau/Promise.luau @@ -0,0 +1,2243 @@ +--// Coauthored by @ArxkDev (https://github.com/arxkdev) and @Evaera (https://github.com/evaera) +--// Fetched from (https://github.com/arxkdev/Leaderboard/blob/main/src/Leaderboard/Promise.luau) +--// Licensed under the MIT License (https://github.com/arxkdev/Leaderboard/blob/main/LICENSE) + +--!strict +--[[ + An implementation of Promises similar to Promise/A+. +]] + +export type Status = "Started" | "Resolved" | "Rejected" | "Cancelled" + +export type Promise = { + andThen: ( + self: Promise, + successHandler: (...any) -> ...any, + failureHandler: ((...any) -> ...any)? + ) -> Promise, + andThenCall: (self: Promise, callback: (TArgs...) -> ...any, TArgs...) -> any, + andThenReturn: (self: Promise, ...any) -> Promise, + + await: (self: Promise) -> (boolean, ...any), + awaitStatus: (self: Promise) -> (Status, ...any), + + cancel: (self: Promise) -> (), + catch: (self: Promise, failureHandler: (...any) -> ...any) -> Promise, + expect: (self: Promise) -> ...any, + + finally: (self: Promise, finallyHandler: (status: Status) -> ...any) -> Promise, + finallyCall: (self: Promise, callback: (TArgs...) -> ...any, TArgs...) -> Promise, + finallyReturn: (self: Promise, ...any) -> Promise, + + getStatus: (self: Promise) -> Status, + now: (self: Promise, rejectionValue: any?) -> Promise, + tap: (self: Promise, tapHandler: (...any) -> ...any) -> Promise, + timeout: (self: Promise, seconds: number, rejectionValue: any?) -> Promise, +} + +export type TypedPromise = { + andThen: ( + self: TypedPromise, + successHandler: (result: T) -> ...any, + failureHandler: ((...any) -> ...any)? + ) -> TypedPromise, + andThenReturn: (self: TypedPromise, ...any) -> TypedPromise, + + await: (self: TypedPromise) -> (boolean, ...T), + awaitStatus: (self: TypedPromise) -> (Status, ...T), + + cancel: (self: TypedPromise) -> (), + catch: (self: TypedPromise, failureHandler: (...any) -> ...any) -> TypedPromise, + expect: (self: TypedPromise) -> ...any, + finally: (self: TypedPromise, finallyHandler: (status: Status) -> ...any) -> TypedPromise, + + finallyReturn: (self: TypedPromise, ...any) -> TypedPromise, + finallyCall: (self: TypedPromise, callback: (TArgs...) -> ...any, TArgs...) -> TypedPromise, + + getStatus: (self: TypedPromise) -> Status, + now: (self: TypedPromise, rejectionValue: any?) -> TypedPromise, + tap: (self: TypedPromise, tapHandler: (...any) -> ...any) -> TypedPromise, + timeout: (self: TypedPromise, seconds: number, rejectionValue: any?) -> TypedPromise, +} + +type Signal = { + Connect: (self: Signal, callback: (T...) -> ...any) -> SignalConnection, +} + +type SignalConnection = { + Disconnect: (self: SignalConnection) -> ...any, + [any]: any, +} + +local ERROR_NON_PROMISE_IN_LIST = "Non-promise value passed into %s at index %s" +local ERROR_NON_LIST = "Please pass a list of promises to %s" +local ERROR_NON_FUNCTION = "Please pass a handler function to %s!" +local MODE_KEY_METATABLE = { __mode = "k" } + +local function isCallable(value) + if type(value) == "function" then + return true + end + + if type(value) == "table" then + local metatable = getmetatable(value) + if metatable and type(rawget(metatable, "__call")) == "function" then + return true + end + end + + return false +end + +--[[ + Creates an enum dictionary with some metamethods to prevent common mistakes. +]] +local function makeEnum(enumName, members) + local enum = {} + + for _, memberName in ipairs(members) do + enum[memberName] = memberName + end + + return setmetatable(enum, { + __index = function(_, k) + error(string.format("%s is not in %s!", k, enumName), 2) + end, + __newindex = function() + error(string.format("Creating new members in %s is not allowed!", enumName), 2) + end, + }) +end + +--[=[ + An object to represent runtime errors that occur during execution. + Promises that experience an error like this will be rejected with + an instance of this object. + + @class Error +]=] +local Error +do + Error = { + Kind = makeEnum("Promise.Error.Kind", { + "ExecutionError", + "AlreadyCancelled", + "NotResolvedInTime", + "TimedOut", + }), + } + Error.__index = Error + + function Error.new(options, parent) + options = options or {} + return setmetatable({ + error = tostring(options.error) or "[This error has no error text.]", + trace = options.trace, + context = options.context, + kind = options.kind, + parent = parent, + createdTick = os.clock(), + createdTrace = debug.traceback(), + }, Error) + end + + function Error.is(anything) + if type(anything) == "table" then + local metatable = getmetatable(anything) + + if type(metatable) == "table" then + return rawget(anything, "error") ~= nil and type(rawget(metatable, "extend")) == "function" + end + end + + return false + end + + function Error.isKind(anything, kind) + assert(kind ~= nil, "Argument #2 to Promise.Error.isKind must not be nil") + + return Error.is(anything) and anything.kind == kind + end + + function Error:extend(options) + options = options or {} + + options.kind = options.kind or self.kind + + return Error.new(options, self) + end + + function Error:getErrorChain() + local runtimeErrors = { self } + + while runtimeErrors[#runtimeErrors].parent do + table.insert(runtimeErrors, runtimeErrors[#runtimeErrors].parent) + end + + return runtimeErrors + end + + function Error:__tostring() + local errorStrings = { + string.format("-- Promise.Error(%s) --", self.kind or "?"), + } + + for _, runtimeError in ipairs(self:getErrorChain()) do + table.insert( + errorStrings, + table.concat({ + runtimeError.trace or runtimeError.error, + runtimeError.context, + }, "\n") + ) + end + + return table.concat(errorStrings, "\n") + end +end + +--[[ + Packs a number of arguments into a table and returns its length. + + Used to cajole varargs without dropping sparse values. +]] +local function pack(...) + return select("#", ...), { ... } +end + +--[[ + Returns first value (success), and packs all following values. +]] +local function packResult(success, ...) + return success, select("#", ...), { ... } +end + +local function makeErrorHandler(traceback) + assert(traceback ~= nil, "traceback is nil") + + return function(err) + -- If the error object is already a table, forward it directly. + -- Should we extend the error here and add our own trace? + + if type(err) == "table" then + return err + end + + return Error.new({ + error = err, + kind = Error.Kind.ExecutionError, + trace = debug.traceback(tostring(err), 2), + context = "Promise created at:\n\n" .. traceback, + }) + end +end + +--[[ + Calls a Promise executor with error handling. +]] +local function runExecutor(traceback, callback, ...) + return packResult(xpcall(callback, makeErrorHandler(traceback), ...)) +end + +--[[ + Creates a function that invokes a callback with correct error handling and + resolution mechanisms. +]] +local function createAdvancer(traceback, callback, resolve, reject) + return function(...) + local ok, resultLength, result = runExecutor(traceback, callback, ...) + + if ok then + resolve(unpack(result, 1, resultLength)) + else + reject(result[1]) + end + end +end + +local function isEmpty(t) + return next(t) == nil +end + +--[=[ + An enum value used to represent the Promise's status. + @interface Status + @tag enum + @within Promise + .Started "Started" -- The Promise is executing, and not settled yet. + .Resolved "Resolved" -- The Promise finished successfully. + .Rejected "Rejected" -- The Promise was rejected. + .Cancelled "Cancelled" -- The Promise was cancelled before it finished. +]=] +--[=[ + @prop Status Status + @within Promise + @readonly + @tag enums + A table containing all members of the `Status` enum, e.g., `Promise.Status.Resolved`. +]=] +--[=[ + A Promise is an object that represents a value that will exist in the future, but doesn't right now. + Promises allow you to then attach callbacks that can run once the value becomes available (known as *resolving*), + or if an error has occurred (known as *rejecting*). + + @class Promise + @__index prototype +]=] +local Promise = { + Error = Error, + Status = makeEnum("Promise.Status", { "Started", "Resolved", "Rejected", "Cancelled" }), + _getTime = os.clock, + _timeEvent = game:GetService("RunService").Heartbeat, + _unhandledRejectionCallbacks = {}, +} +Promise.prototype = {} +Promise.__index = Promise.prototype + +function Promise._new(traceback, callback, parent) + if parent ~= nil and not Promise.is(parent) then + error("Argument #2 to Promise.new must be a promise or nil", 2) + end + + local self = { + -- The executor thread. + _thread = nil, + + -- Used to locate where a promise was created + _source = traceback, + + _status = Promise.Status.Started, + + -- A table containing a list of all results, whether success or failure. + -- Only valid if _status is set to something besides Started + _values = nil, + + -- Lua doesn't like sparse arrays very much, so we explicitly store the + -- length of _values to handle middle nils. + _valuesLength = -1, + + -- Tracks if this Promise has no error observers.. + _unhandledRejection = true, + + -- Queues representing functions we should invoke when we update! + _queuedResolve = {}, + _queuedReject = {}, + _queuedFinally = {}, + + -- The function to run when/if this promise is cancelled. + _cancellationHook = nil, + + -- The "parent" of this promise in a promise chain. Required for + -- cancellation propagation upstream. + _parent = parent, + + -- Consumers are Promises that have chained onto this one. + -- We track them for cancellation propagation downstream. + _consumers = setmetatable({}, MODE_KEY_METATABLE), + } + + if parent and parent._status == Promise.Status.Started then + parent._consumers[self] = true + end + + setmetatable(self, Promise) + + local function resolve(...) + self:_resolve(...) + end + + local function reject(...) + self:_reject(...) + end + + local function onCancel(cancellationHook) + if cancellationHook then + if self._status == Promise.Status.Cancelled then + cancellationHook() + else + self._cancellationHook = cancellationHook + end + end + + return self._status == Promise.Status.Cancelled + end + + self._thread = coroutine.create(function() + local ok, _, result = runExecutor(self._source, callback, resolve, reject, onCancel) + + if not ok then + reject(result[1]) + end + end) + + task.spawn(self._thread) + + return self +end + +--[=[ + Construct a new Promise that will be resolved or rejected with the given callbacks. + + If you `resolve` with a Promise, it will be chained onto. + + You can safely yield within the executor function and it will not block the creating thread. + + ```lua + local myFunction() + return Promise.new(function(resolve, reject, onCancel) + wait(1) + resolve("Hello world!") + end) + end + + myFunction():andThen(print) + ``` + + You do not need to use `pcall` within a Promise. Errors that occur during execution will be caught and turned into a rejection automatically. If `error()` is called with a table, that table will be the rejection value. Otherwise, string errors will be converted into `Promise.Error(Promise.Error.Kind.ExecutionError)` objects for tracking debug information. + + You may register an optional cancellation hook by using the `onCancel` argument: + + * This should be used to abort any ongoing operations leading up to the promise being settled. + * Call the `onCancel` function with a function callback as its only argument to set a hook which will in turn be called when/if the promise is cancelled. + * `onCancel` returns `true` if the Promise was already cancelled when you called `onCancel`. + * Calling `onCancel` with no argument will not override a previously set cancellation hook, but it will still return `true` if the Promise is currently cancelled. + * You can set the cancellation hook at any time before resolving. + * When a promise is cancelled, calls to `resolve` or `reject` will be ignored, regardless of if you set a cancellation hook or not. + + :::caution + If the Promise is cancelled, the `executor` thread is closed with `coroutine.close` after the cancellation hook is called. + + You must perform any cleanup code in the cancellation hook: any time your executor yields, it **may never resume**. + ::: + + @param executor (resolve: (...: any) -> (), reject: (...: any) -> (), onCancel: (abortHandler?: () -> ()) -> boolean) -> () + @return Promise +]=] +function Promise.new(executor) + return Promise._new(debug.traceback(nil, 2), executor) +end + +function Promise:__tostring() + return string.format("Promise(%s)", self._status) +end + +--[=[ + The same as [Promise.new](/api/Promise#new), except execution begins after the next `Heartbeat` event. + + This is a spiritual replacement for `spawn`, but it does not suffer from the same [issues](https://eryn.io/gist/3db84579866c099cdd5bb2ff37947cec) as `spawn`. + + ```lua + local function waitForChild(instance, childName, timeout) + return Promise.defer(function(resolve, reject) + local child = instance:WaitForChild(childName, timeout) + + ;(child and resolve or reject)(child) + end) + end + ``` + + @param executor (resolve: (...: any) -> (), reject: (...: any) -> (), onCancel: (abortHandler?: () -> ()) -> boolean) -> () + @return Promise +]=] +function Promise.defer(executor) + local traceback = debug.traceback(nil, 2) + local promise + promise = Promise._new(traceback, function(resolve, reject, onCancel) + local connection + connection = Promise._timeEvent:Connect(function() + connection:Disconnect() + local ok, _, result = runExecutor(traceback, executor, resolve, reject, onCancel) + + if not ok then + reject(result[1]) + end + end) + end) + + return promise +end + +-- Backwards compatibility +Promise.async = Promise.defer + +--[=[ + Creates an immediately resolved Promise with the given value. + + ```lua + -- Example using Promise.resolve to deliver cached values: + function getSomething(name) + if cache[name] then + return Promise.resolve(cache[name]) + else + return Promise.new(function(resolve, reject) + local thing = getTheThing() + cache[name] = thing + + resolve(thing) + end) + end + end + ``` + + @param ... any + @return Promise<...any> +]=] +function Promise.resolve(...) + local length, values = pack(...) + return Promise._new(debug.traceback(nil, 2), function(resolve) + resolve(unpack(values, 1, length)) + end) +end + +--[=[ + Creates an immediately rejected Promise with the given value. + + :::caution + Something needs to consume this rejection (i.e. `:catch()` it), otherwise it will emit an unhandled Promise rejection warning on the next frame. Thus, you should not create and store rejected Promises for later use. Only create them on-demand as needed. + ::: + + @param ... any + @return Promise<...any> +]=] +function Promise.reject(...) + local length, values = pack(...) + return Promise._new(debug.traceback(nil, 2), function(_, reject) + reject(unpack(values, 1, length)) + end) +end + +--[[ + Runs a non-promise-returning function as a Promise with the + given arguments. +]] +function Promise._try(traceback, callback, ...) + local valuesLength, values = pack(...) + + return Promise._new(traceback, function(resolve) + resolve(callback(unpack(values, 1, valuesLength))) + end) +end + +--[=[ + Begins a Promise chain, calling a function and returning a Promise resolving with its return value. If the function errors, the returned Promise will be rejected with the error. You can safely yield within the Promise.try callback. + + :::info + `Promise.try` is similar to [Promise.promisify](#promisify), except the callback is invoked immediately instead of returning a new function. + ::: + + ```lua + Promise.try(function() + return math.random(1, 2) == 1 and "ok" or error("Oh an error!") + end) + :andThen(function(text) + print(text) + end) + :catch(function(err) + warn("Something went wrong") + end) + ``` + + @param callback (...: T...) -> ...any + @param ... T... -- Additional arguments passed to `callback` + @return Promise +]=] +function Promise.try(callback, ...) + return Promise._try(debug.traceback(nil, 2), callback, ...) +end + +--[[ + Returns a new promise that: + * is resolved when all input promises resolve + * is rejected if ANY input promises reject +]] +function Promise._all(traceback, promises, amount) + if type(promises) ~= "table" then + error(string.format(ERROR_NON_LIST, "Promise.all"), 3) + end + + -- We need to check that each value is a promise here so that we can produce + -- a proper error rather than a rejected promise with our error. + for i, promise in pairs(promises) do + if not Promise.is(promise) then + error(string.format(ERROR_NON_PROMISE_IN_LIST, "Promise.all", tostring(i)), 3) + end + end + + -- If there are no values then return an already resolved promise. + if #promises == 0 or amount == 0 then + return Promise.resolve({}) + end + + return Promise._new(traceback, function(resolve, reject, onCancel) + -- An array to contain our resolved values from the given promises. + local resolvedValues = {} + local newPromises = {} + + -- Keep a count of resolved promises because just checking the resolved + -- values length wouldn't account for promises that resolve with nil. + local resolvedCount = 0 + local rejectedCount = 0 + local done = false + + local function cancel() + for _, promise in ipairs(newPromises) do + promise:cancel() + end + end + + -- Called when a single value is resolved and resolves if all are done. + local function resolveOne(i, ...) + if done then + return + end + + resolvedCount = resolvedCount + 1 + + if amount == nil then + resolvedValues[i] = ... + else + resolvedValues[resolvedCount] = ... + end + + if resolvedCount >= (amount or #promises) then + done = true + resolve(resolvedValues) + cancel() + end + end + + onCancel(cancel) + + -- We can assume the values inside `promises` are all promises since we + -- checked above. + for i, promise in ipairs(promises) do + newPromises[i] = promise:andThen(function(...) + resolveOne(i, ...) + end, function(...) + rejectedCount = rejectedCount + 1 + + if amount == nil or #promises - rejectedCount < amount then + cancel() + done = true + + reject(...) + end + end) + end + + if done then + cancel() + end + end) +end + +--[=[ + Accepts an array of Promises and returns a new promise that: + * is resolved after all input promises resolve. + * is rejected if *any* input promises reject. + + :::info + Only the first return value from each promise will be present in the resulting array. + ::: + + After any input Promise rejects, all other input Promises that are still pending will be cancelled if they have no other consumers. + + ```lua + local promises = { + returnsAPromise("example 1"), + returnsAPromise("example 2"), + returnsAPromise("example 3"), + } + + return Promise.all(promises) + ``` + + @param promises {Promise} + @return Promise<{T}> +]=] +function Promise.all(promises) + return Promise._all(debug.traceback(nil, 2), promises) +end + +--[=[ + Folds an array of values or promises into a single value. The array is traversed sequentially. + + The reducer function can return a promise or value directly. Each iteration receives the resolved value from the previous, and the first receives your defined initial value. + + The folding will stop at the first rejection encountered. + ```lua + local basket = {"blueberry", "melon", "pear", "melon"} + Promise.fold(basket, function(cost, fruit) + if fruit == "blueberry" then + return cost -- blueberries are free! + else + -- call a function that returns a promise with the fruit price + return fetchPrice(fruit):andThen(function(fruitCost) + return cost + fruitCost + end) + end + end, 0) + ``` + + @since v3.1.0 + @param list {T | Promise} + @param reducer (accumulator: U, value: T, index: number) -> U | Promise + @param initialValue U +]=] +function Promise.fold(list, reducer, initialValue) + assert(type(list) == "table", "Bad argument #1 to Promise.fold: must be a table") + assert(isCallable(reducer), "Bad argument #2 to Promise.fold: must be a function") + + local accumulator = Promise.resolve(initialValue) + return Promise.each(list, function(resolvedElement, i) + accumulator = accumulator:andThen(function(previousValueResolved) + return reducer(previousValueResolved, resolvedElement, i) + end) + end):andThen(function() + return accumulator + end) +end + +--[=[ + Accepts an array of Promises and returns a Promise that is resolved as soon as `count` Promises are resolved from the input array. The resolved array values are in the order that the Promises resolved in. When this Promise resolves, all other pending Promises are cancelled if they have no other consumers. + + `count` 0 results in an empty array. The resultant array will never have more than `count` elements. + + ```lua + local promises = { + returnsAPromise("example 1"), + returnsAPromise("example 2"), + returnsAPromise("example 3"), + } + + return Promise.some(promises, 2) -- Only resolves with first 2 promises to resolve + ``` + + @param promises {Promise} + @param count number + @return Promise<{T}> +]=] +function Promise.some(promises, count) + assert(type(count) == "number", "Bad argument #2 to Promise.some: must be a number") + + return Promise._all(debug.traceback(nil, 2), promises, count) +end + +--[=[ + Accepts an array of Promises and returns a Promise that is resolved as soon as *any* of the input Promises resolves. It will reject only if *all* input Promises reject. As soon as one Promises resolves, all other pending Promises are cancelled if they have no other consumers. + + Resolves directly with the value of the first resolved Promise. This is essentially [[Promise.some]] with `1` count, except the Promise resolves with the value directly instead of an array with one element. + + ```lua + local promises = { + returnsAPromise("example 1"), + returnsAPromise("example 2"), + returnsAPromise("example 3"), + } + + return Promise.any(promises) -- Resolves with first value to resolve (only rejects if all 3 rejected) + ``` + + @param promises {Promise} + @return Promise +]=] +function Promise.any(promises) + return Promise._all(debug.traceback(nil, 2), promises, 1):andThen(function(values) + return values[1] + end) +end + +--[=[ + Accepts an array of Promises and returns a new Promise that resolves with an array of in-place Statuses when all input Promises have settled. This is equivalent to mapping `promise:finally` over the array of Promises. + + ```lua + local promises = { + returnsAPromise("example 1"), + returnsAPromise("example 2"), + returnsAPromise("example 3"), + } + + return Promise.allSettled(promises) + ``` + + @param promises {Promise} + @return Promise<{Status}> +]=] +function Promise.allSettled(promises) + if type(promises) ~= "table" then + error(string.format(ERROR_NON_LIST, "Promise.allSettled"), 2) + end + + -- We need to check that each value is a promise here so that we can produce + -- a proper error rather than a rejected promise with our error. + for i, promise in pairs(promises) do + if not Promise.is(promise) then + error(string.format(ERROR_NON_PROMISE_IN_LIST, "Promise.allSettled", tostring(i)), 2) + end + end + + -- If there are no values then return an already resolved promise. + if #promises == 0 then + return Promise.resolve({}) + end + + return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel) + -- An array to contain our resolved values from the given promises. + local fates = {} + local newPromises = {} + + -- Keep a count of resolved promises because just checking the resolved + -- values length wouldn't account for promises that resolve with nil. + local finishedCount = 0 + + -- Called when a single value is resolved and resolves if all are done. + local function resolveOne(i, ...) + finishedCount = finishedCount + 1 + + fates[i] = ... + + if finishedCount >= #promises then + resolve(fates) + end + end + + onCancel(function() + for _, promise in ipairs(newPromises) do + promise:cancel() + end + end) + + -- We can assume the values inside `promises` are all promises since we + -- checked above. + for i, promise in ipairs(promises) do + newPromises[i] = promise:finally(function(...) + resolveOne(i, ...) + end) + end + end) +end + +--[=[ + Accepts an array of Promises and returns a new promise that is resolved or rejected as soon as any Promise in the array resolves or rejects. + + :::warning + If the first Promise to settle from the array settles with a rejection, the resulting Promise from `race` will reject. + + If you instead want to tolerate rejections, and only care about at least one Promise resolving, you should use [Promise.any](#any) or [Promise.some](#some) instead. + ::: + + All other Promises that don't win the race will be cancelled if they have no other consumers. + + ```lua + local promises = { + returnsAPromise("example 1"), + returnsAPromise("example 2"), + returnsAPromise("example 3"), + } + + return Promise.race(promises) -- Only returns 1st value to resolve or reject + ``` + + @param promises {Promise} + @return Promise +]=] +function Promise.race(promises) + assert(type(promises) == "table", string.format(ERROR_NON_LIST, "Promise.race")) + + for i, promise in pairs(promises) do + assert(Promise.is(promise), string.format(ERROR_NON_PROMISE_IN_LIST, "Promise.race", tostring(i))) + end + + return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel) + local newPromises = {} + local finished = false + + local function cancel() + for _, promise in ipairs(newPromises) do + promise:cancel() + end + end + + local function finalize(callback) + return function(...) + cancel() + finished = true + return callback(...) + end + end + + if onCancel(finalize(reject)) then + return + end + + for i, promise in ipairs(promises) do + newPromises[i] = promise:andThen(finalize(resolve), finalize(reject)) + end + + if finished then + cancel() + end + end) +end + +--[=[ + Iterates serially over the given an array of values, calling the predicate callback on each value before continuing. + + If the predicate returns a Promise, we wait for that Promise to resolve before moving on to the next item + in the array. + + :::info + `Promise.each` is similar to `Promise.all`, except the Promises are ran in order instead of all at once. + + But because Promises are eager, by the time they are created, they're already running. Thus, we need a way to defer creation of each Promise until a later time. + + The predicate function exists as a way for us to operate on our data instead of creating a new closure for each Promise. If you would prefer, you can pass in an array of functions, and in the predicate, call the function and return its return value. + ::: + + ```lua + Promise.each({ + "foo", + "bar", + "baz", + "qux" + }, function(value, index) + return Promise.delay(1):andThen(function() + print(("%d) Got %s!"):format(index, value)) + end) + end) + + --[[ + (1 second passes) + > 1) Got foo! + (1 second passes) + > 2) Got bar! + (1 second passes) + > 3) Got baz! + (1 second passes) + > 4) Got qux! + ]] + ``` + + If the Promise a predicate returns rejects, the Promise from `Promise.each` is also rejected with the same value. + + If the array of values contains a Promise, when we get to that point in the list, we wait for the Promise to resolve before calling the predicate with the value. + + If a Promise in the array of values is already Rejected when `Promise.each` is called, `Promise.each` rejects with that value immediately (the predicate callback will never be called even once). If a Promise in the list is already Cancelled when `Promise.each` is called, `Promise.each` rejects with `Promise.Error(Promise.Error.Kind.AlreadyCancelled`). If a Promise in the array of values is Started at first, but later rejects, `Promise.each` will reject with that value and iteration will not continue once iteration encounters that value. + + Returns a Promise containing an array of the returned/resolved values from the predicate for each item in the array of values. + + If this Promise returned from `Promise.each` rejects or is cancelled for any reason, the following are true: + - Iteration will not continue. + - Any Promises within the array of values will now be cancelled if they have no other consumers. + - The Promise returned from the currently active predicate will be cancelled if it hasn't resolved yet. + + @since 3.0.0 + @param list {T | Promise} + @param predicate (value: T, index: number) -> U | Promise + @return Promise<{U}> +]=] +function Promise.each(list, predicate) + assert(type(list) == "table", string.format(ERROR_NON_LIST, "Promise.each")) + assert(isCallable(predicate), string.format(ERROR_NON_FUNCTION, "Promise.each")) + + return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel) + local results = {} + local promisesToCancel = {} + + local cancelled = false + + local function cancel() + for _, promiseToCancel in ipairs(promisesToCancel) do + promiseToCancel:cancel() + end + end + + onCancel(function() + cancelled = true + + cancel() + end) + + -- We need to preprocess the list of values and look for Promises. + -- If we find some, we must register our andThen calls now, so that those Promises have a consumer + -- from us registered. If we don't do this, those Promises might get cancelled by something else + -- before we get to them in the series because it's not possible to tell that we plan to use it + -- unless we indicate it here. + + local preprocessedList = {} + + for index, value in ipairs(list) do + if Promise.is(value) then + if value:getStatus() == Promise.Status.Cancelled then + cancel() + return reject(Error.new({ + error = "Promise is cancelled", + kind = Error.Kind.AlreadyCancelled, + context = string.format( + "The Promise that was part of the array at index %d passed into Promise.each was already cancelled when Promise.each began.\n\nThat Promise was created at:\n\n%s", + index, + value._source + ), + })) + elseif value:getStatus() == Promise.Status.Rejected then + cancel() + return reject(select(2, value:await())) + end + + -- Chain a new Promise from this one so we only cancel ours + local ourPromise = value:andThen(function(...) + return ... + end) + + table.insert(promisesToCancel, ourPromise) + preprocessedList[index] = ourPromise + else + preprocessedList[index] = value + end + end + + for index, value in ipairs(preprocessedList) do + if Promise.is(value) then + local success + success, value = value:await() + + if not success then + cancel() + return reject(value) + end + end + + if cancelled then + return + end + + local predicatePromise = Promise.resolve(predicate(value, index)) + + table.insert(promisesToCancel, predicatePromise) + + local success, result = predicatePromise:await() + + if not success then + cancel() + return reject(result) + end + + results[index] = result + end + + resolve(results) + end) +end + +--[=[ + Checks whether the given object is a Promise via duck typing. This only checks if the object is a table and has an `andThen` method. + + @param object any + @return boolean -- `true` if the given `object` is a Promise. +]=] +function Promise.is(object) + if type(object) ~= "table" then + return false + end + + local objectMetatable = getmetatable(object) + + if objectMetatable == Promise then + -- The Promise came from this library. + return true + elseif objectMetatable == nil then + -- No metatable, but we should still chain onto tables with andThen methods + return isCallable(object.andThen) + elseif + type(objectMetatable) == "table" + and type(rawget(objectMetatable, "__index")) == "table" + and isCallable(rawget(rawget(objectMetatable, "__index"), "andThen")) + then + -- Maybe this came from a different or older Promise library. + return true + end + + return false +end + +--[=[ + Wraps a function that yields into one that returns a Promise. + + Any errors that occur while executing the function will be turned into rejections. + + :::info + `Promise.promisify` is similar to [Promise.try](#try), except the callback is returned as a callable function instead of being invoked immediately. + ::: + + ```lua + local sleep = Promise.promisify(wait) + + sleep(1):andThen(print) + ``` + + ```lua + local isPlayerInGroup = Promise.promisify(function(player, groupId) + return player:IsInGroup(groupId) + end) + ``` + + @param callback (...: any) -> ...any + @return (...: any) -> Promise +]=] +function Promise.promisify(callback) + return function(...) + return Promise._try(debug.traceback(nil, 2), callback, ...) + end +end + +--[=[ + Returns a Promise that resolves after `seconds` seconds have passed. The Promise resolves with the actual amount of time that was waited. + + This function is **not** a wrapper around `wait`. `Promise.delay` uses a custom scheduler which provides more accurate timing. As an optimization, cancelling this Promise instantly removes the task from the scheduler. + + :::warning + Passing `NaN`, infinity, or a number less than 1/60 is equivalent to passing 1/60. + ::: + + ```lua + Promise.delay(5):andThenCall(print, "This prints after 5 seconds") + ``` + + @function delay + @within Promise + @param seconds number + @return Promise +]=] +do + -- uses a sorted doubly linked list (queue) to achieve O(1) remove operations and O(n) for insert + + -- the initial node in the linked list + local first + local connection + + function Promise.delay(seconds) + assert(type(seconds) == "number", "Bad argument #1 to Promise.delay, must be a number.") + -- If seconds is -INF, INF, NaN, or less than 1 / 60, assume seconds is 1 / 60. + -- This mirrors the behavior of wait() + if not (seconds >= 1 / 60) or seconds == math.huge then + seconds = 1 / 60 + end + + return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel) + local startTime = Promise._getTime() + local endTime = startTime + seconds + + local node = { + resolve = resolve, + startTime = startTime, + endTime = endTime, + } + + if connection == nil then -- first is nil when connection is nil + first = node + connection = Promise._timeEvent:Connect(function() + local threadStart = Promise._getTime() + + while first ~= nil and first.endTime < threadStart do + local current = first + first = current.next + + if first == nil then + connection:Disconnect() + connection = nil + else + first.previous = nil + end + + current.resolve(Promise._getTime() - current.startTime) + end + end) + else -- first is non-nil + if first.endTime < endTime then -- if `node` should be placed after `first` + -- we will insert `node` between `current` and `next` + -- (i.e. after `current` if `next` is nil) + local current = first + local next = current.next + + while next ~= nil and next.endTime < endTime do + current = next + next = current.next + end + + -- `current` must be non-nil, but `next` could be `nil` (i.e. last item in list) + current.next = node + node.previous = current + + if next ~= nil then + node.next = next + next.previous = node + end + else + -- set `node` to `first` + node.next = first + first.previous = node + first = node + end + end + + onCancel(function() + -- remove node from queue + local next = node.next + + if first == node then + if next == nil then -- if `node` is the first and last + connection:Disconnect() + connection = nil + else -- if `node` is `first` and not the last + next.previous = nil + end + first = next + else + local previous = node.previous + -- since `node` is not `first`, then we know `previous` is non-nil + previous.next = next + + if next ~= nil then + next.previous = previous + end + end + end) + end) + end +end + +--[=[ + Returns a new Promise that resolves if the chained Promise resolves within `seconds` seconds, or rejects if execution time exceeds `seconds`. The chained Promise will be cancelled if the timeout is reached. + + Rejects with `rejectionValue` if it is non-nil. If a `rejectionValue` is not given, it will reject with a `Promise.Error(Promise.Error.Kind.TimedOut)`. This can be checked with [[Error.isKind]]. + + ```lua + getSomething():timeout(5):andThen(function(something) + -- got something and it only took at max 5 seconds + end):catch(function(e) + -- Either getting something failed or the time was exceeded. + + if Promise.Error.isKind(e, Promise.Error.Kind.TimedOut) then + warn("Operation timed out!") + else + warn("Operation encountered an error!") + end + end) + ``` + + Sugar for: + + ```lua + Promise.race({ + Promise.delay(seconds):andThen(function() + return Promise.reject( + rejectionValue == nil + and Promise.Error.new({ kind = Promise.Error.Kind.TimedOut }) + or rejectionValue + ) + end), + promise + }) + ``` + + @param seconds number + @param rejectionValue? any -- The value to reject with if the timeout is reached + @return Promise +]=] +function Promise.prototype:timeout(seconds, rejectionValue) + local traceback = debug.traceback(nil, 2) + + return Promise.race({ + Promise.delay(seconds):andThen(function() + return Promise.reject(rejectionValue == nil and Error.new({ + kind = Error.Kind.TimedOut, + error = "Timed out", + context = string.format( + "Timeout of %d seconds exceeded.\n:timeout() called at:\n\n%s", + seconds, + traceback + ), + }) or rejectionValue) + end), + self, + }) +end + +--[=[ + Returns the current Promise status. + + @return Status +]=] +function Promise.prototype:getStatus() + return self._status +end + +--[[ + Creates a new promise that receives the result of this promise. + + The given callbacks are invoked depending on that result. +]] +function Promise.prototype:_andThen(traceback, successHandler, failureHandler) + self._unhandledRejection = false + + -- If we are already cancelled, we return a cancelled Promise + if self._status == Promise.Status.Cancelled then + local promise = Promise.new(function() end) + promise:cancel() + + return promise + end + + -- Create a new promise to follow this part of the chain + return Promise._new(traceback, function(resolve, reject, onCancel) + -- Our default callbacks just pass values onto the next promise. + -- This lets success and failure cascade correctly! + + local successCallback = resolve + if successHandler then + successCallback = createAdvancer(traceback, successHandler, resolve, reject) + end + + local failureCallback = reject + if failureHandler then + failureCallback = createAdvancer(traceback, failureHandler, resolve, reject) + end + + if self._status == Promise.Status.Started then + -- If we haven't resolved yet, put ourselves into the queue + table.insert(self._queuedResolve, successCallback) + table.insert(self._queuedReject, failureCallback) + + onCancel(function() + -- These are guaranteed to exist because the cancellation handler is guaranteed to only + -- be called at most once + if self._status == Promise.Status.Started then + table.remove(self._queuedResolve, table.find(self._queuedResolve, successCallback)) + table.remove(self._queuedReject, table.find(self._queuedReject, failureCallback)) + end + end) + elseif self._status == Promise.Status.Resolved then + -- This promise has already resolved! Trigger success immediately. + successCallback(unpack(self._values, 1, self._valuesLength)) + elseif self._status == Promise.Status.Rejected then + -- This promise died a terrible death! Trigger failure immediately. + failureCallback(unpack(self._values, 1, self._valuesLength)) + end + end, self) +end + +--[=[ + Chains onto an existing Promise and returns a new Promise. + + :::warning + Within the failure handler, you should never assume that the rejection value is a string. Some rejections within the Promise library are represented by [[Error]] objects. If you want to treat it as a string for debugging, you should call `tostring` on it first. + ::: + + You can return a Promise from the success or failure handler and it will be chained onto. + + Calling `andThen` on a cancelled Promise returns a cancelled Promise. + + :::tip + If the Promise returned by `andThen` is cancelled, `successHandler` and `failureHandler` will not run. + + To run code no matter what, use [Promise:finally]. + ::: + + @param successHandler (...: any) -> ...any + @param failureHandler? (...: any) -> ...any + @return Promise<...any> +]=] +function Promise.prototype:andThen(successHandler, failureHandler) + assert(successHandler == nil or isCallable(successHandler), string.format(ERROR_NON_FUNCTION, "Promise:andThen")) + assert(failureHandler == nil or isCallable(failureHandler), string.format(ERROR_NON_FUNCTION, "Promise:andThen")) + + return self:_andThen(debug.traceback(nil, 2), successHandler, failureHandler) +end + +--[=[ + Shorthand for `Promise:andThen(nil, failureHandler)`. + + Returns a Promise that resolves if the `failureHandler` worked without encountering an additional error. + + :::warning + Within the failure handler, you should never assume that the rejection value is a string. Some rejections within the Promise library are represented by [[Error]] objects. If you want to treat it as a string for debugging, you should call `tostring` on it first. + ::: + + Calling `catch` on a cancelled Promise returns a cancelled Promise. + + :::tip + If the Promise returned by `catch` is cancelled, `failureHandler` will not run. + + To run code no matter what, use [Promise:finally]. + ::: + + @param failureHandler (...: any) -> ...any + @return Promise<...any> +]=] +function Promise.prototype:catch(failureHandler) + assert(failureHandler == nil or isCallable(failureHandler), string.format(ERROR_NON_FUNCTION, "Promise:catch")) + return self:_andThen(debug.traceback(nil, 2), nil, failureHandler) +end + +--[=[ + Similar to [Promise.andThen](#andThen), except the return value is the same as the value passed to the handler. In other words, you can insert a `:tap` into a Promise chain without affecting the value that downstream Promises receive. + + ```lua + getTheValue() + :tap(print) + :andThen(function(theValue) + print("Got", theValue, "even though print returns nil!") + end) + ``` + + If you return a Promise from the tap handler callback, its value will be discarded but `tap` will still wait until it resolves before passing the original value through. + + @param tapHandler (...: any) -> ...any + @return Promise<...any> +]=] +function Promise.prototype:tap(tapHandler) + assert(isCallable(tapHandler), string.format(ERROR_NON_FUNCTION, "Promise:tap")) + return self:_andThen(debug.traceback(nil, 2), function(...) + local callbackReturn = tapHandler(...) + + if Promise.is(callbackReturn) then + local length, values = pack(...) + return callbackReturn:andThen(function() + return unpack(values, 1, length) + end) + end + + return ... + end) +end + +--[=[ + Attaches an `andThen` handler to this Promise that calls the given callback with the predefined arguments. The resolved value is discarded. + + ```lua + promise:andThenCall(someFunction, "some", "arguments") + ``` + + This is sugar for + + ```lua + promise:andThen(function() + return someFunction("some", "arguments") + end) + ``` + + @param callback (...: any) -> any + @param ...? any -- Additional arguments which will be passed to `callback` + @return Promise +]=] +function Promise.prototype:andThenCall(callback, ...) + assert(isCallable(callback), string.format(ERROR_NON_FUNCTION, "Promise:andThenCall")) + local length, values = pack(...) + return self:_andThen(debug.traceback(nil, 2), function() + return callback(unpack(values, 1, length)) + end) +end + +--[=[ + Attaches an `andThen` handler to this Promise that discards the resolved value and returns the given value from it. + + ```lua + promise:andThenReturn("some", "values") + ``` + + This is sugar for + + ```lua + promise:andThen(function() + return "some", "values" + end) + ``` + + :::caution + Promises are eager, so if you pass a Promise to `andThenReturn`, it will begin executing before `andThenReturn` is reached in the chain. Likewise, if you pass a Promise created from [[Promise.reject]] into `andThenReturn`, it's possible that this will trigger the unhandled rejection warning. If you need to return a Promise, it's usually best practice to use [[Promise.andThen]]. + ::: + + @param ... any -- Values to return from the function + @return Promise +]=] +function Promise.prototype:andThenReturn(...) + local length, values = pack(...) + return self:_andThen(debug.traceback(nil, 2), function() + return unpack(values, 1, length) + end) +end + +--[=[ + Cancels this promise, preventing the promise from resolving or rejecting. Does not do anything if the promise is already settled. + + Cancellations will propagate upwards and downwards through chained promises. + + Promises will only be cancelled if all of their consumers are also cancelled. This is to say that if you call `andThen` twice on the same promise, and you cancel only one of the child promises, it will not cancel the parent promise until the other child promise is also cancelled. + + ```lua + promise:cancel() + ``` +]=] +function Promise.prototype:cancel() + if self._status ~= Promise.Status.Started then + return + end + + self._status = Promise.Status.Cancelled + + if self._cancellationHook then + self._cancellationHook() + end + + coroutine.close(self._thread) + + if self._parent then + self._parent:_consumerCancelled(self) + end + + for child in pairs(self._consumers) do + child:cancel() + end + + self:_finalize() +end + +--[[ + Used to decrease the number of consumers by 1, and if there are no more, + cancel this promise. +]] +function Promise.prototype:_consumerCancelled(consumer) + if self._status ~= Promise.Status.Started then + return + end + + self._consumers[consumer] = nil + + if next(self._consumers) == nil then + self:cancel() + end +end + +--[[ + Used to set a handler for when the promise resolves, rejects, or is + cancelled. +]] +function Promise.prototype:_finally(traceback, finallyHandler) + self._unhandledRejection = false + + local promise = Promise._new(traceback, function(resolve, reject, onCancel) + local handlerPromise + + onCancel(function() + -- The finally Promise is not a proper consumer of self. We don't care about the resolved value. + -- All we care about is running at the end. Therefore, if self has no other consumers, it's safe to + -- cancel. We don't need to hold out cancelling just because there's a finally handler. + self:_consumerCancelled(self) + + if handlerPromise then + handlerPromise:cancel() + end + end) + + local finallyCallback = resolve + if finallyHandler then + finallyCallback = function(...) + local callbackReturn = finallyHandler(...) + + if Promise.is(callbackReturn) then + handlerPromise = callbackReturn + + callbackReturn + :finally(function(status) + if status ~= Promise.Status.Rejected then + resolve(self) + end + end) + :catch(function(...) + reject(...) + end) + else + resolve(self) + end + end + end + + if self._status == Promise.Status.Started then + -- The promise is not settled, so queue this. + table.insert(self._queuedFinally, finallyCallback) + else + -- The promise already settled or was cancelled, run the callback now. + finallyCallback(self._status) + end + end) + + return promise +end + +--[=[ + Set a handler that will be called regardless of the promise's fate. The handler is called when the promise is + resolved, rejected, *or* cancelled. + + Returns a new Promise that: + - resolves with the same values that this Promise resolves with. + - rejects with the same values that this Promise rejects with. + - is cancelled if this Promise is cancelled. + + If the value you return from the handler is a Promise: + - We wait for the Promise to resolve, but we ultimately discard the resolved value. + - If the returned Promise rejects, the Promise returned from `finally` will reject with the rejected value from the + *returned* promise. + - If the `finally` Promise is cancelled, and you returned a Promise from the handler, we cancel that Promise too. + + Otherwise, the return value from the `finally` handler is entirely discarded. + + :::note Cancellation + As of Promise v4, `Promise:finally` does not count as a consumer of the parent Promise for cancellation purposes. + This means that if all of a Promise's consumers are cancelled and the only remaining callbacks are finally handlers, + the Promise is cancelled and the finally callbacks run then and there. + + Cancellation still propagates through the `finally` Promise though: if you cancel the `finally` Promise, it can cancel + its parent Promise if it had no other consumers. Likewise, if the parent Promise is cancelled, the `finally` Promise + will also be cancelled. + ::: + + ```lua + local thing = createSomething() + + doSomethingWith(thing) + :andThen(function() + print("It worked!") + -- do something.. + end) + :catch(function() + warn("Oh no it failed!") + end) + :finally(function() + -- either way, destroy thing + + thing:Destroy() + end) + + ``` + + @param finallyHandler (status: Status) -> ...any + @return Promise<...any> +]=] +function Promise.prototype:finally(finallyHandler) + assert(finallyHandler == nil or isCallable(finallyHandler), string.format(ERROR_NON_FUNCTION, "Promise:finally")) + return self:_finally(debug.traceback(nil, 2), finallyHandler) +end + +--[=[ + Same as `andThenCall`, except for `finally`. + + Attaches a `finally` handler to this Promise that calls the given callback with the predefined arguments. + + @param callback (...: any) -> any + @param ...? any -- Additional arguments which will be passed to `callback` + @return Promise +]=] +function Promise.prototype:finallyCall(callback, ...) + assert(isCallable(callback), string.format(ERROR_NON_FUNCTION, "Promise:finallyCall")) + local length, values = pack(...) + return self:_finally(debug.traceback(nil, 2), function() + return callback(unpack(values, 1, length)) + end) +end + +--[=[ + Attaches a `finally` handler to this Promise that discards the resolved value and returns the given value from it. + + ```lua + promise:finallyReturn("some", "values") + ``` + + This is sugar for + + ```lua + promise:finally(function() + return "some", "values" + end) + ``` + + @param ... any -- Values to return from the function + @return Promise +]=] +function Promise.prototype:finallyReturn(...) + local length, values = pack(...) + return self:_finally(debug.traceback(nil, 2), function() + return unpack(values, 1, length) + end) +end + +--[=[ + Yields the current thread until the given Promise completes. Returns the Promise's status, followed by the values that the promise resolved or rejected with. + + @yields + @return Status -- The Status representing the fate of the Promise + @return ...any -- The values the Promise resolved or rejected with. +]=] +function Promise.prototype:awaitStatus() + self._unhandledRejection = false + + if self._status == Promise.Status.Started then + local thread = coroutine.running() + + self + :finally(function() + task.spawn(thread) + end) + -- The finally promise can propagate rejections, so we attach a catch handler to prevent the unhandled + -- rejection warning from appearing + :catch( + function() end + ) + + coroutine.yield() + end + + if self._status == Promise.Status.Resolved then + return self._status, unpack(self._values, 1, self._valuesLength) + elseif self._status == Promise.Status.Rejected then + return self._status, unpack(self._values, 1, self._valuesLength) + end + + return self._status +end + +local function awaitHelper(status, ...) + return status == Promise.Status.Resolved, ... +end + +--[=[ + Yields the current thread until the given Promise completes. Returns true if the Promise resolved, followed by the values that the promise resolved or rejected with. + + :::caution + If the Promise gets cancelled, this function will return `false`, which is indistinguishable from a rejection. If you need to differentiate, you should use [[Promise.awaitStatus]] instead. + ::: + + ```lua + local worked, value = getTheValue():await() + + if worked then + print("got", value) + else + warn("it failed") + end + ``` + + @yields + @return boolean -- `true` if the Promise successfully resolved + @return ...any -- The values the Promise resolved or rejected with. +]=] +function Promise.prototype:await() + return awaitHelper(self:awaitStatus()) +end + +local function expectHelper(status, ...) + if status ~= Promise.Status.Resolved then + error((...) == nil and "Expected Promise rejected with no value." or (...), 3) + end + + return ... +end + +--[=[ + Yields the current thread until the given Promise completes. Returns the values that the promise resolved with. + + ```lua + local worked = pcall(function() + print("got", getTheValue():expect()) + end) + + if not worked then + warn("it failed") + end + ``` + + This is essentially sugar for: + + ```lua + select(2, assert(promise:await())) + ``` + + **Errors** if the Promise rejects or gets cancelled. + + @error any -- Errors with the rejection value if this Promise rejects or gets cancelled. + @yields + @return ...any -- The values the Promise resolved with. +]=] +function Promise.prototype:expect() + return expectHelper(self:awaitStatus()) +end + +-- Backwards compatibility +Promise.prototype.awaitValue = Promise.prototype.expect + +--[[ + Intended for use in tests. + + Similar to await(), but instead of yielding if the promise is unresolved, + _unwrap will throw. This indicates an assumption that a promise has + resolved. +]] +function Promise.prototype:_unwrap() + if self._status == Promise.Status.Started then + error("Promise has not resolved or rejected.", 2) + end + + local success = self._status == Promise.Status.Resolved + + return success, unpack(self._values, 1, self._valuesLength) +end + +function Promise.prototype:_resolve(...) + if self._status ~= Promise.Status.Started then + if Promise.is((...)) then + (...):_consumerCancelled(self) + end + return + end + + -- If the resolved value was a Promise, we chain onto it! + if Promise.is((...)) then + -- Without this warning, arguments sometimes mysteriously disappear + if select("#", ...) > 1 then + local message = string.format( + "When returning a Promise from andThen, extra arguments are " .. "discarded! See:\n\n%s", + self._source + ) + warn(message) + end + + local chainedPromise = ... + + local promise = chainedPromise:andThen(function(...) + self:_resolve(...) + end, function(...) + local maybeRuntimeError = chainedPromise._values[1] + + -- Backwards compatibility < v2 + if chainedPromise._error then + maybeRuntimeError = Error.new({ + error = chainedPromise._error, + kind = Error.Kind.ExecutionError, + context = "[No stack trace available as this Promise originated from an older version of the Promise library (< v2)]", + }) + end + + if Error.isKind(maybeRuntimeError, Error.Kind.ExecutionError) then + return self:_reject(maybeRuntimeError:extend({ + error = "This Promise was chained to a Promise that errored.", + trace = "", + context = string.format( + "The Promise at:\n\n%s\n...Rejected because it was chained to the following Promise, which encountered an error:\n", + self._source + ), + })) + end + + self:_reject(...) + end) + + if promise._status == Promise.Status.Cancelled then + self:cancel() + elseif promise._status == Promise.Status.Started then + -- Adopt ourselves into promise for cancellation propagation. + self._parent = promise + promise._consumers[self] = true + end + + return + end + + self._status = Promise.Status.Resolved + self._valuesLength, self._values = pack(...) + + -- We assume that these callbacks will not throw errors. + for _, callback in ipairs(self._queuedResolve) do + coroutine.wrap(callback)(...) + end + + self:_finalize() +end + +function Promise.prototype:_reject(...) + if self._status ~= Promise.Status.Started then + return + end + + self._status = Promise.Status.Rejected + self._valuesLength, self._values = pack(...) + + -- If there are any rejection handlers, call those! + if not isEmpty(self._queuedReject) then + -- We assume that these callbacks will not throw errors. + for _, callback in ipairs(self._queuedReject) do + coroutine.wrap(callback)(...) + end + else + -- At this point, no one was able to observe the error. + -- An error handler might still be attached if the error occurred + -- synchronously. We'll wait one tick, and if there are still no + -- observers, then we should put a message in the console. + + local err = tostring((...)) + + coroutine.wrap(function() + Promise._timeEvent:Wait() + + -- Someone observed the error, hooray! + if not self._unhandledRejection then + return + end + + -- Build a reasonable message + local message = string.format("Unhandled Promise rejection:\n\n%s\n\n%s", err, self._source) + + for _, callback in ipairs(Promise._unhandledRejectionCallbacks) do + task.spawn(callback, self, unpack(self._values, 1, self._valuesLength)) + end + + if Promise.TEST then + -- Don't spam output when we're running tests. + return + end + + warn(message) + end)() + end + + self:_finalize() +end + +--[[ + Calls any :finally handlers. We need this to be a separate method and + queue because we must call all of the finally callbacks upon a success, + failure, *and* cancellation. +]] +function Promise.prototype:_finalize() + for _, callback in ipairs(self._queuedFinally) do + -- Purposefully not passing values to callbacks here, as it could be the + -- resolved values, or rejected errors. If the developer needs the values, + -- they should use :andThen or :catch explicitly. + coroutine.wrap(callback)(self._status) + end + + self._queuedFinally = nil + self._queuedReject = nil + self._queuedResolve = nil + + -- Clear references to other Promises to allow gc + if not Promise.TEST then + self._parent = nil + self._consumers = nil + end + + task.defer(coroutine.close, self._thread) +end + +--[=[ + Chains a Promise from this one that is resolved if this Promise is already resolved, and rejected if it is not resolved at the time of calling `:now()`. This can be used to ensure your `andThen` handler occurs on the same frame as the root Promise execution. + + ```lua + doSomething() + :now() + :andThen(function(value) + print("Got", value, "synchronously.") + end) + ``` + + If this Promise is still running, Rejected, or Cancelled, the Promise returned from `:now()` will reject with the `rejectionValue` if passed, otherwise with a `Promise.Error(Promise.Error.Kind.NotResolvedInTime)`. This can be checked with [[Error.isKind]]. + + @param rejectionValue? any -- The value to reject with if the Promise isn't resolved + @return Promise +]=] +function Promise.prototype:now(rejectionValue) + local traceback = debug.traceback(nil, 2) + if self._status == Promise.Status.Resolved then + return self:_andThen(traceback, function(...) + return ... + end) + else + return Promise.reject(rejectionValue == nil and Error.new({ + kind = Error.Kind.NotResolvedInTime, + error = "This Promise was not resolved in time for :now()", + context = ":now() was called at:\n\n" .. traceback, + }) or rejectionValue) + end +end + +--[=[ + Repeatedly calls a Promise-returning function up to `times` number of times, until the returned Promise resolves. + + If the amount of retries is exceeded, the function will return the latest rejected Promise. + + ```lua + local function canFail(a, b, c) + return Promise.new(function(resolve, reject) + -- do something that can fail + + local failed, thing = doSomethingThatCanFail(a, b, c) + + if failed then + reject("it failed") + else + resolve(thing) + end + end) + end + + local MAX_RETRIES = 10 + local value = Promise.retry(canFail, MAX_RETRIES, "foo", "bar", "baz") -- args to send to canFail + ``` + + @since 3.0.0 + @param callback (...: P) -> Promise + @param times number + @param ...? P + @return Promise +]=] +function Promise.retry(callback, times, ...) + assert(isCallable(callback), "Parameter #1 to Promise.retry must be a function") + assert(type(times) == "number", "Parameter #2 to Promise.retry must be a number") + + local args, length = { ... }, select("#", ...) + + return Promise.resolve(callback(...)):catch(function(...) + if times > 0 then + return Promise.retry(callback, times - 1, unpack(args, 1, length)) + else + return Promise.reject(...) + end + end) +end + +--[=[ + Repeatedly calls a Promise-returning function up to `times` number of times, waiting `seconds` seconds between each + retry, until the returned Promise resolves. + + If the amount of retries is exceeded, the function will return the latest rejected Promise. + + @since v3.2.0 + @param callback (...: P) -> Promise + @param times number + @param seconds number + @param ...? P + @return Promise +]=] +function Promise.retryWithDelay(callback, times, seconds, ...) + assert(isCallable(callback), "Parameter #1 to Promise.retryWithDelay must be a function") + assert(type(times) == "number", "Parameter #2 (times) to Promise.retryWithDelay must be a number") + assert(type(seconds) == "number", "Parameter #3 (seconds) to Promise.retryWithDelay must be a number") + + local args, length = { ... }, select("#", ...) + + return Promise.resolve(callback(...)):catch(function(...) + if times > 0 then + Promise.delay(seconds):await() + + return Promise.retryWithDelay(callback, times - 1, seconds, unpack(args, 1, length)) + else + return Promise.reject(...) + end + end) +end + + +--[=[ + Repeatedly calls a Promise-returning function up to `times` number of times, with a variable delay between retries + based on the specified equation ("Linear", "Exponential", or "Quadratic"), until the returned Promise resolves. + + If the amount of retries is exceeded, the function will return the latest rejected Promise. + + @param callback (...: P) -> Promise + @param times number + @param seconds number + @param equation string + @param ...? P + @return Promise +]=] +function Promise.retryWithVariableDelay(callback, times, seconds, equation, ...) + assert(isCallable(callback), "Parameter #1 to Promise.retryWithVariableDelay must be a function") + assert(type(times) == "number", "Parameter #2 (times) to Promise.retryWithVariableDelay must be a number") + assert(type(seconds) == "number", "Parameter #3 (seconds) to Promise.retryWithVariableDelay must be a number") + assert(type(equation) == "string", "Parameter #3 (seconds) to Promise.retryWithVariableDelay must be a string") + + local attempts = 0 + local args, length = { ... }, select("#", ...) + + local function attempt() + attempts += 1 + return Promise.resolve(callback(unpack(args, 1, length))):catch(function(...) + if attempts < times then + + local delaySeconds = seconds + if equation == "Exponential" then + delaySeconds = seconds * math.pow(2, attempts) + elseif equation == "Quadratic" then + delaySeconds = seconds * math.pow(attempts, 2) + end + Promise.delay(delaySeconds):await() + + return attempt() + else + warn("Return reject") + return Promise.reject(...) + end + end) + end + + return Promise.new(function(resolve, reject) + attempt():andThen(resolve):catch(reject) + end) +end + +--[=[ + Converts an event into a Promise which resolves the next time the event fires. + + The optional `predicate` callback, if passed, will receive the event arguments and should return `true` or `false`, based on if this fired event should resolve the Promise or not. If `true`, the Promise resolves. If `false`, nothing happens and the predicate will be rerun the next time the event fires. + + The Promise will resolve with the event arguments. + + :::tip + This function will work given any object with a `Connect` method. This includes all Roblox events. + ::: + + ```lua + -- Creates a Promise which only resolves when `somePart` is touched + -- by a part named `"Something specific"`. + return Promise.fromEvent(somePart.Touched, function(part) + return part.Name == "Something specific" + end) + ``` + + @since 3.0.0 + @param event Event -- Any object with a `Connect` method. This includes all Roblox events. + @param predicate? (...: P) -> boolean -- A function which determines if the Promise should resolve with the given value, or wait for the next event to check again. + @return Promise

+]=] +function Promise.fromEvent(event, predicate) + predicate = predicate or function() + return true + end + + return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel) + local connection + local shouldDisconnect = false + + local function disconnect() + connection:Disconnect() + connection = nil + end + + -- We use shouldDisconnect because if the callback given to Connect is called before + -- Connect returns, connection will still be nil. This happens with events that queue up + -- events when there's nothing connected, such as RemoteEvents + + connection = event:Connect(function(...) + local callbackValue = predicate(...) + + if callbackValue == true then + resolve(...) + + if connection then + disconnect() + else + shouldDisconnect = true + end + elseif type(callbackValue) ~= "boolean" then + error("Promise.fromEvent predicate should always return a boolean") + end + end) + + if shouldDisconnect and connection then + return disconnect() + end + + onCancel(disconnect) + end) +end + +--[=[ + Registers a callback that runs when an unhandled rejection happens. An unhandled rejection happens when a Promise + is rejected, and the rejection is not observed with `:catch`. + + The callback is called with the actual promise that rejected, followed by the rejection values. + + @since v3.2.0 + @param callback (promise: Promise, ...: any) -- A callback that runs when an unhandled rejection happens. + @return () -> () -- Function that unregisters the `callback` when called +]=] +function Promise.onUnhandledRejection(callback) + table.insert(Promise._unhandledRejectionCallbacks, callback) + + return function() + local index = table.find(Promise._unhandledRejectionCallbacks, callback) + + if index then + table.remove(Promise._unhandledRejectionCallbacks, index) + end + end +end + +return Promise :: { + Error: any, + + all: (promises: { TypedPromise }) -> TypedPromise<{ T }>, + allSettled: (promise: { TypedPromise }) -> TypedPromise<{ Status }>, + any: (promise: { TypedPromise }) -> TypedPromise, + defer: ( + executor: ( + resolve: (TReturn...) -> (), + reject: (...any) -> (), + onCancel: (abortHandler: (() -> ())?) -> boolean + ) -> () + ) -> TypedPromise, + delay: (seconds: number) -> TypedPromise, + each: ( + list: { T | TypedPromise }, + predicate: (value: T, index: number) -> TReturn | TypedPromise + ) -> TypedPromise<{ TReturn }>, + fold: ( + list: { T | TypedPromise }, + reducer: (accumulator: TReturn, value: T, index: number) -> TReturn | TypedPromise + ) -> TypedPromise, + fromEvent: ( + event: Signal, + predicate: ((TReturn...) -> boolean)? + ) -> TypedPromise, + is: (object: any) -> boolean, + new: ( + executor: ( + resolve: (TReturn...) -> (), + reject: (...any) -> (), + onCancel: (abortHandler: (() -> ())?) -> boolean + ) -> () + ) -> TypedPromise, + onUnhandledRejection: (callback: (promise: TypedPromise, ...any) -> ()) -> () -> (), + promisify: (callback: (TArgs...) -> TReturn...) -> (TArgs...) -> TypedPromise, + race: (promises: { TypedPromise }) -> TypedPromise, + reject: (...any) -> TypedPromise<...any>, + resolve: (TReturn...) -> TypedPromise, + retry: ( + callback: (TArgs...) -> TypedPromise, + times: number, + TArgs... + ) -> TypedPromise, + retryWithDelay: ( + callback: (TArgs...) -> TypedPromise, + times: number, + seconds: number, + TArgs... + ) -> TypedPromise, + retryWithVariableDelay: ( + callback: (TArgs...) -> TypedPromise, + times: number, + seconds: number, + equation: "Exponential" | "Quadratic", + TArgs... + ) -> TypedPromise, + some: (promise: { TypedPromise }, count: number) -> TypedPromise<{ T }>, + try: (callback: (TArgs...) -> TReturn..., TArgs...) -> TypedPromise, +} diff --git a/samples/Luau/ReactRobloxHostConfig.luau b/samples/Luau/ReactRobloxHostConfig.luau new file mode 100644 index 0000000000..53ca831b4f --- /dev/null +++ b/samples/Luau/ReactRobloxHostConfig.luau @@ -0,0 +1,1366 @@ +--// Authored by @JSDotLua (https://github.com/jsdotlua) +--// Fetched from (https://github.com/jsdotlua/react-lua/blob/main/modules/react-roblox/src/client/ReactRobloxHostConfig.lua) +--// Licensed under the MIT License (https://github.com/jsdotlua/react-lua/blob/main/LICENSE) + +--!strict +-- ROBLOX upstream: https://github.com/facebook/react/blob/8e5adfbd7e605bda9c5e96c10e015b3dc0df688e/packages/react-dom/src/client/ReactDOMHostConfig.js +-- ROBLOX upstream: https://github.com/facebook/react/blob/efd8f6442d1aa7c4566fe812cba03e7e83aaccc3/packages/react-native-renderer/src/ReactNativeHostConfig.js +--[[* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow +]] +-- FIXME (roblox): remove this when our unimplemented +local function unimplemented(message: string) + print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + print("UNIMPLEMENTED ERROR: " .. tostring(message)) + error("FIXME (roblox): " .. message .. " is unimplemented", 2) +end + +local CollectionService = game:GetService("CollectionService") +local LuauPolyfill = require("@pkg/@jsdotlua/luau-polyfill") +local inspect = LuauPolyfill.util.inspect +local console = require("@pkg/@jsdotlua/shared").console +local Object = LuauPolyfill.Object +local setTimeout = LuauPolyfill.setTimeout +local clearTimeout = LuauPolyfill.clearTimeout + +-- local type {DOMEventName} = require(Packages.../events/DOMEventNames') +-- local type {Fiber, FiberRoot} = require(Packages.react-reconciler/src/ReactInternalTypes') +-- local type { +-- BoundingRect, +-- IntersectionObserverOptions, +-- ObserveVisibleRectsCallback, +-- } = require(Packages.react-reconciler/src/ReactTestSelectors') +local ReactRobloxHostTypes = require("./ReactRobloxHostTypes.roblox.lua") +type RootType = ReactRobloxHostTypes.RootType +type Container = ReactRobloxHostTypes.Container +type HostInstance = ReactRobloxHostTypes.HostInstance +type SuspenseInstance = ReactRobloxHostTypes.SuspenseInstance +type TextInstance = ReactRobloxHostTypes.TextInstance +type Props = ReactRobloxHostTypes.Props +type Type = ReactRobloxHostTypes.Type +type HostContext = ReactRobloxHostTypes.HostContext + +-- local type {ReactScopeInstance} = require(Packages.shared/ReactTypes') +-- local type {ReactDOMFundamentalComponentInstance} = require(Packages.../shared/ReactDOMTypes') + +local ReactRobloxComponentTree = require("./ReactRobloxComponentTree") +local precacheFiberNode = ReactRobloxComponentTree.precacheFiberNode +local uncacheFiberNode = ReactRobloxComponentTree.uncacheFiberNode +local updateFiberProps = ReactRobloxComponentTree.updateFiberProps +-- local getClosestInstanceFromNode = ReactRobloxComponentTree.getClosestInstanceFromNode +-- local getFiberFromScopeInstance = ReactRobloxComponentTree.getFiberFromScopeInstance +-- local getInstanceFromNodeDOMTree = ReactRobloxComponentTree.getInstanceFromNode +-- local isContainerMarkedAsRoot = ReactRobloxComponentTree.isContainerMarkedAsRoot + +-- local {hasRole} = require(Packages../DOMAccessibilityRoles') +local ReactRobloxComponent = require("./ReactRobloxComponent") +-- local createElement = ReactRobloxComponent.createElement +-- local createTextNode = ReactRobloxComponent.createTextNode +local setInitialProperties = ReactRobloxComponent.setInitialProperties +local diffProperties = ReactRobloxComponent.diffProperties +local updateProperties = ReactRobloxComponent.updateProperties +local cleanupHostComponent = ReactRobloxComponent.cleanupHostComponent +-- local diffHydratedProperties = ReactRobloxComponent.diffHydratedProperties +-- local diffHydratedText = ReactRobloxComponent.diffHydratedText +-- local trapClickOnNonInteractiveElement = ReactRobloxComponent.trapClickOnNonInteractiveElement +-- local warnForUnmatchedText = ReactRobloxComponent.warnForUnmatchedText +-- local warnForDeletedHydratableElement = ReactRobloxComponent.warnForDeletedHydratableElement +-- local warnForDeletedHydratableText = ReactRobloxComponent.warnForDeletedHydratableText +-- local warnForInsertedHydratedElement = ReactRobloxComponent.warnForInsertedHydratedElement +-- local warnForInsertedHydratedText = ReactRobloxComponent.warnForInsertedHydratedText +-- local {getSelectionInformation, restoreSelection} = require(Packages../ReactInputSelection') +-- local setTextContent = require(Packages../setTextContent') +-- local {validateDOMNesting, updatedAncestorInfo} = require(Packages../validateDOMNesting') +-- local { +-- isEnabled as ReactBrowserEventEmitterIsEnabled, +-- setEnabled as ReactBrowserEventEmitterSetEnabled, +-- } = require(Packages.../events/ReactDOMEventListener') +-- local {getChildNamespace} = require(Packages.../shared/DOMNamespaces') +-- local { +-- ELEMENT_NODE, +-- TEXT_NODE, +-- COMMENT_NODE, +-- DOCUMENT_NODE, +-- DOCUMENT_FRAGMENT_NODE, +-- } = require(Packages.../shared/HTMLNodeType') +-- local dangerousStyleValue = require(Packages.../shared/dangerousStyleValue') + +-- local {REACT_OPAQUE_ID_TYPE} = require(Packages.shared/ReactSymbols') +-- local {retryIfBlockedOn} = require(Packages.../events/ReactDOMEventReplaying') + +local ReactFeatureFlags = require("@pkg/@jsdotlua/shared").ReactFeatureFlags +-- local enableSuspenseServerRenderer = ReactFeatureFlags.enableSuspenseServerRenderer +-- local enableFundamentalAPI = ReactFeatureFlags.enableFundamentalAPI +local enableCreateEventHandleAPI = ReactFeatureFlags.enableCreateEventHandleAPI +-- local enableScopeAPI = ReactFeatureFlags.enableScopeAPI +-- local enableEagerRootListeners = ReactFeatureFlags.enableEagerRootListeners + +-- local {HostComponent, HostText} = require(Packages.react-reconciler/src/ReactWorkTags') +-- local { +-- listenToReactEvent, +-- listenToAllSupportedEvents, +-- } = require(Packages.../events/DOMPluginEventSystem') + +type Array = { [number]: T } +type Object = { [any]: any } + +-- ROBLOX deviation: Moved to ReactRobloxHostTypes +-- export type Type = string; +-- export type Props = { +-- autoFocus: boolean?, +-- children: any, +-- disabled: boolean?, +-- hidden: boolean?, +-- suppressHydrationWarning: boolean?, +-- dangerouslySetInnerHTML: any, +-- style: { display: string, [any]: any }?, +-- bottom: number?, +-- left: number?, +-- right: number?, +-- top: number?, +-- -- ... +-- [any]: any, +-- }; +-- export type EventTargetChildElement = { +-- type: string, +-- props: nil | { +-- style?: { +-- position?: string, +-- zIndex?: number, +-- bottom?: string, +-- left?: string, +-- right?: string, +-- top?: string, +-- ... +-- }, +-- ... +-- }, +-- ... +-- end + +-- ROBLOX deviation: Moved to ReactRobloxHostTypes +-- export type SuspenseInstance = Comment & {_reactRetry?: () => void, ...} +-- export type HydratableInstance = Instance | TextInstance | SuspenseInstance + +-- ROBLOX deviation: Moved to ReactRobloxHostTypes +-- export type PublicInstance = Element | Text +-- type HostContextDev = { +-- namespace: string, +-- ancestorInfo: any, +-- -- ... +-- [any]: any, +-- } +-- type HostContextProd = string +-- export type HostContext = HostContextDev | HostContextProd + +-- export type UpdatePayload = Array +-- ROBLOX FIXME: cannot create type equal to void +-- export type ChildSet = void; -- Unused +-- export type TimeoutHandle = TimeoutID +-- export type NoTimeout = -1 +-- export type RendererInspectionConfig = $ReadOnly<{or}> + +-- export opaque type OpaqueIDType = +-- | string +-- | { +-- toString: () => string | void, +-- valueOf: () => string | void, +-- end + +-- type SelectionInformation = {| +-- focusedElem: nil | HTMLElement, +-- selectionRange: mixed, +-- |} + +-- local SUPPRESS_HYDRATION_WARNING +-- if __DEV__) +-- SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning' +-- end + +-- local SUSPENSE_START_DATA = '$' +-- local SUSPENSE_END_DATA = '/$' +-- local SUSPENSE_PENDING_START_DATA = '$?' +-- local SUSPENSE_FALLBACK_START_DATA = '$!' + +-- local STYLE = 'style' + +-- local eventsEnabled: boolean? = nil +-- local selectionInformation: nil | SelectionInformation = nil + +-- function shouldAutoFocusHostComponent(type: string, props: Props): boolean { +-- switch (type) +-- case 'button': +-- case 'input': +-- case 'select': +-- case 'textarea': +-- return !!props.autoFocus +-- end +-- return false +-- end + +-- ROBLOX deviation: Use GetDescendants rather than recursion +local function recursivelyUncacheFiberNode(node: HostInstance) + -- ROBLOX https://jira.rbx.com/browse/LUAFDN-713: Tables are somehow ending up + -- in this function that expects Instances. In that case, we won't be able to + -- iterate through its descendants. + if typeof(node :: any) ~= "Instance" then + return + end + + uncacheFiberNode(node) + + for _, child in node:GetDescendants() do + uncacheFiberNode(child) + end +end + +local exports: { [any]: any } = {} +Object.assign( + exports, + require("@pkg/@jsdotlua/shared").ReactFiberHostConfig.WithNoPersistence +) + +exports.getRootHostContext = function(rootContainerInstance: Container): HostContext + -- ROBLOX deviation: This is a lot of HTML-DOM specific logic; I'm not clear on + -- whether there'll be an equivalent of `namespaceURI` for our use cases, but + -- we may want to provide other kinds of context for host objects. + + -- For now, as a guess, we'll return the kind of instance we're attached to + return rootContainerInstance.ClassName + + -- local type + -- local namespace + -- local nodeType = rootContainerInstance.nodeType + -- switch (nodeType) + -- case DOCUMENT_NODE: + -- case DOCUMENT_FRAGMENT_NODE: { + -- type = nodeType == DOCUMENT_NODE ? '#document' : '#fragment' + -- local root = (rootContainerInstance: any).documentElement + -- namespace = root ? root.namespaceURI : getChildNamespace(null, '') + -- break + -- end + -- default: { + -- local container: any = + -- nodeType == COMMENT_NODE + -- ? rootContainerInstance.parentNode + -- : rootContainerInstance + -- local ownNamespace = container.namespaceURI or nil + -- type = container.tagName + -- namespace = getChildNamespace(ownNamespace, type) + -- break + -- end + -- end + -- if _G.__DEV__ then + -- local validatedTag = type.toLowerCase() + -- local ancestorInfo = updatedAncestorInfo(null, validatedTag) + -- return {namespace, ancestorInfo} + -- end + -- return namespace +end + +exports.getChildHostContext = function( + parentHostContext: HostContext, + type: string, + rootContainerInstance: Container +): HostContext + -- ROBLOX deviation: unclear on the purpose here just yet, might be fine to + -- just return parent's hostContext for now + return parentHostContext + -- if _G.__DEV__ then + -- local parentHostContextDev = ((parentHostContext: any): HostContextDev) + -- local namespace = getChildNamespace(parentHostContextDev.namespace, type) + -- local ancestorInfo = updatedAncestorInfo( + -- parentHostContextDev.ancestorInfo, + -- type, + -- ) + -- return {namespace, ancestorInfo} + -- end + -- local parentNamespace = ((parentHostContext: any): HostContextProd) + -- return getChildNamespace(parentNamespace, type) +end + +exports.getPublicInstance = function(instance: Instance): any + return instance +end + +exports.prepareForCommit = function(containerInfo: Container): Object? + -- eventsEnabled = ReactBrowserEventEmitterIsEnabled() + -- selectionInformation = getSelectionInformation() + local activeInstance = nil + if enableCreateEventHandleAPI then + unimplemented("enableCreateEventHandleAPI") + -- local focusedElem = selectionInformation.focusedElem + -- if focusedElem ~= nil then + -- activeInstance = getClosestInstanceFromNode(focusedElem) + -- end + end + -- ReactBrowserEventEmitterSetEnabled(false) + return activeInstance +end + +exports.beforeActiveInstanceBlur = function() + if enableCreateEventHandleAPI then + unimplemented("enableCreateEventHandleAPI") + -- ReactBrowserEventEmitterSetEnabled(true) + -- dispatchBeforeDetachedBlur((selectionInformation: any).focusedElem) + -- ReactBrowserEventEmitterSetEnabled(false) + end +end + +exports.afterActiveInstanceBlur = function() + if enableCreateEventHandleAPI then + unimplemented("enableCreateEventHandleAPI") + -- ReactBrowserEventEmitterSetEnabled(true) + -- dispatchAfterDetachedBlur((selectionInformation: any).focusedElem) + -- ReactBrowserEventEmitterSetEnabled(false) + end +end + +exports.resetAfterCommit = function(containerInfo: Container) + -- warn("Skip unimplemented: resetAfterCommit") + -- restoreSelection(selectionInformation) + -- ReactBrowserEventEmitterSetEnabled(eventsEnabled) + -- eventsEnabled = nil + -- selectionInformation = nil +end + +exports.createInstance = function( + type_: string, + props: Props, + rootContainerInstance: Container, + hostContext: HostContext, + internalInstanceHandle: Object +): HostInstance + -- local hostKey = virtualNode.hostKey + + local domElement = Instance.new(type_) + -- ROBLOX deviation: compatibility with old Roact where instances have their name + -- set to the key value + if internalInstanceHandle.key then + domElement.Name = internalInstanceHandle.key + else + local currentHandle = internalInstanceHandle.return_ + while currentHandle do + if currentHandle.key then + domElement.Name = currentHandle.key + break + end + currentHandle = currentHandle.return_ + end + end + + precacheFiberNode(internalInstanceHandle, domElement) + updateFiberProps(domElement, props) + + -- TODO: Support refs (does that actually happen here, or later?) + -- applyRef(element.props[Ref], instance) + + -- Will have to be managed outside of createInstance + -- if virtualNode.eventManager ~= nil then + -- virtualNode.eventManager:resume() + -- end + + return domElement + + -- return Instance.new("Frame") + -- local parentNamespace: string + -- if __DEV__) + -- -- TODO: take namespace into account when validating. + -- local hostContextDev = ((hostContext: any): HostContextDev) + -- validateDOMNesting(type, nil, hostContextDev.ancestorInfo) + -- if + -- typeof props.children == 'string' or + -- typeof props.children == 'number' + -- ) + -- local string = '' + props.children + -- local ownAncestorInfo = updatedAncestorInfo( + -- hostContextDev.ancestorInfo, + -- type, + -- ) + -- validateDOMNesting(null, string, ownAncestorInfo) + -- end + -- parentNamespace = hostContextDev.namespace + -- } else { + -- parentNamespace = ((hostContext: any): HostContextProd) + -- end + -- local domElement: Instance = createElement( + -- type, + -- props, + -- rootContainerInstance, + -- parentNamespace, + -- ) +end + +exports.appendInitialChild = function(parentInstance: Instance, child: Instance) + -- ROBLOX deviation: Establish hierarchy with Parent property + child.Parent = parentInstance +end + +exports.finalizeInitialChildren = function( + domElement: HostInstance, + type_: string, + props: Props, + rootContainerInstance: Container, + hostContext: HostContext +): boolean + setInitialProperties(domElement, type_, props, rootContainerInstance) + return false + -- return shouldAutoFocusHostComponent(type_, props) +end + +local function prepareUpdate( + domElement: Instance, + type_: string, + oldProps: Props, + newProps: Props, + rootContainerInstance: Container, + hostContext: HostContext +): nil | Array + -- if _G.__DEV__ then + -- local hostContextDev = ((hostContext: any): HostContextDev) + -- if + -- typeof newProps.children ~= typeof oldProps.children and + -- (typeof newProps.children == 'string' or + -- typeof newProps.children == 'number') + -- ) + -- local string = '' + newProps.children + -- local ownAncestorInfo = updatedAncestorInfo( + -- hostContextDev.ancestorInfo, + -- type, + -- ) + -- validateDOMNesting(null, string, ownAncestorInfo) + -- end + -- end + return diffProperties(domElement, type_, oldProps, newProps, rootContainerInstance) +end +exports.prepareUpdate = prepareUpdate + +exports.shouldSetTextContent = function(_type: string, _props: Props): boolean + -- ROBLOX deviation: Ignore TextInstance logic, which isn't applicable to Roblox + return false + -- return ( + -- type == 'textarea' or + -- type == 'option' or + -- type == 'noscript' or + -- typeof props.children == 'string' or + -- typeof props.children == 'number' or + -- (typeof props.dangerouslySetInnerHTML == 'table’' and + -- props.dangerouslySetInnerHTML ~= nil and + -- props.dangerouslySetInnerHTML.__html ~= nil) + -- ) +end + +-- ROBLOX deviation: Text nodes aren't supported in Roblox renderer, so error so that tests fail immediately +exports.createTextInstance = function( + text: string, + rootContainerInstance: Container, + hostContext: HostContext, + internalInstanceHandle: Object +): any + unimplemented("createTextInstance") + return nil +end + +exports.isPrimaryRenderer = true +exports.warnsIfNotActing = true +-- This initialization code may run even on server environments +-- if a component just imports ReactDOM (e.g. for findDOMNode). +-- Some environments might not have setTimeout or clearTimeout. +-- ROBLOX deviation: We're only dealing with client right now, so these always populate +exports.scheduleTimeout = setTimeout +exports.cancelTimeout = clearTimeout +exports.noTimeout = -1 + +-- ------------------- +-- Mutation +-- ------------------- + +exports.supportsMutation = true + +exports.commitMount = function( + domElement: Instance, + type: string, + newProps: Props, + internalInstanceHandle: Object +) + unimplemented("commitMount") + -- -- Despite the naming that might imply otherwise, this method only + -- -- fires if there is an `Update` effect scheduled during mounting. + -- -- This happens if `finalizeInitialChildren` returns `true` (which it + -- -- does to implement the `autoFocus` attribute on the client). But + -- -- there are also other cases when this might happen (such as patching + -- -- up text content during hydration mismatch). So we'll check this again. + -- if shouldAutoFocusHostComponent(type, newProps)) + -- ((domElement: any): + -- | HTMLButtonElement + -- | HTMLInputElement + -- | HTMLSelectElement + -- | HTMLTextAreaElement).focus() + -- end +end + +exports.commitUpdate = function( + domElement: Instance, + updatePayload: Array, + type_: string, + oldProps: Props, + newProps: Props, + internalInstanceHandle: Object +) + -- Update the props handle so that we know which props are the ones with + -- with current event handlers. + updateFiberProps(domElement, newProps) + -- Apply the diff to the DOM node. + updateProperties(domElement, updatePayload, oldProps) +end + +-- ROBLOX deviation: Ignore TextInstance logic, which isn't applicable to Roblox +-- exports.resetTextContent(domElement: Instance): void { +-- setTextContent(domElement, '') +-- end + +-- ROBLOX deviation: Ignore TextInstance logic, which isn't applicable to Roblox +-- exports.commitTextUpdate( +-- textInstance: TextInstance, +-- oldText: string, +-- newText: string, +-- ): void { +-- textInstance.nodeValue = newText +-- end + +local function checkTags(instance: Instance) + if typeof(instance :: any) ~= "Instance" then + console.warn("Could not check tags on non-instance %s.", inspect(instance)) + return + end + if not instance:IsDescendantOf(game) then + if #CollectionService:GetTags(instance) > 0 then + console.warn( + 'Tags applied to orphaned %s "%s" cannot be accessed via' + .. " CollectionService:GetTagged. If you're relying on tag" + .. " behavior in a unit test, consider mounting your test " + .. "root into the DataModel.", + instance.ClassName, + instance.Name + ) + end + end +end + +exports.appendChild = function(parentInstance: Instance, child: Instance) + -- ROBLOX deviation: Roblox's DOM is based on child->parent references + child.Parent = parentInstance + -- parentInstance.appendChild(child) + if _G.__DEV__ then + checkTags(child) + end +end + +exports.appendChildToContainer = function(container: Container, child: Instance) + -- ROBLOX TODO: Some of this logic may come back; for now, keep it simple + local parentNode = container + exports.appendChild(parentNode, child) + + -- if container.nodeType == COMMENT_NODE) + -- parentNode = (container.parentNode: any) + -- parentNode.insertBefore(child, container) + -- } else { + -- parentNode = container + -- parentNode.appendChild(child) + -- end + -- -- This container might be used for a portal. + -- -- If something inside a portal is clicked, that click should bubble + -- -- through the React tree. However, on Mobile Safari the click would + -- -- never bubble through the *DOM* tree unless an ancestor with onclick + -- -- event exists. So we wouldn't see it and dispatch it. + -- -- This is why we ensure that non React root containers have inline onclick + -- -- defined. + -- -- https://github.com/facebook/react/issues/11918 + -- local reactRootContainer = container._reactRootContainer + -- if + -- reactRootContainer == nil and parentNode.onclick == nil + -- then + -- -- TODO: This cast may not be sound for SVG, MathML or custom elements. + -- trapClickOnNonInteractiveElement(((parentNode: any): HTMLElement)) + -- end +end + +exports.insertBefore = + function(parentInstance: Instance, child: Instance, _beforeChild: Instance) + -- ROBLOX deviation: Roblox's DOM is based on child->parent references + child.Parent = parentInstance + -- parentInstance.insertBefore(child, beforeChild) + if _G.__DEV__ then + checkTags(child) + end + end + +exports.insertInContainerBefore = + function(container: Container, child: Instance, beforeChild: Instance) + -- ROBLOX deviation: use our container definition + local parentNode = container + exports.insertBefore(parentNode, child, beforeChild) + -- if container.nodeType == COMMENT_NODE) + -- (container.parentNode: any).insertBefore(child, beforeChild) + -- } else { + -- container.insertBefore(child, beforeChild) + -- end + end + +-- function createEvent(type: DOMEventName, bubbles: boolean): Event { +-- local event = document.createEvent('Event') +-- event.initEvent(((type: any): string), bubbles, false) +-- return event +-- end + +-- function dispatchBeforeDetachedBlur(target: HTMLElement): void { +-- if enableCreateEventHandleAPI) +-- local event = createEvent('beforeblur', true) +-- -- Dispatch "beforeblur" directly on the target, +-- -- so it gets picked up by the event system and +-- -- can propagate through the React internal tree. +-- target.dispatchEvent(event) +-- end +-- end + +-- function dispatchAfterDetachedBlur(target: HTMLElement): void { +-- if enableCreateEventHandleAPI) +-- local event = createEvent('afterblur', false) +-- -- So we know what was detached, make the relatedTarget the +-- -- detached target on the "afterblur" event. +-- (event: any).relatedTarget = target +-- -- Dispatch the event on the document. +-- document.dispatchEvent(event) +-- end +-- end + +exports.removeChild = function(_parentInstance: Instance, child: Instance) + recursivelyUncacheFiberNode(child) + -- ROBLOX deviation: The roblox renderer tracks bindings and event managers + -- for instances, so make sure we clean those up when we remove the instance + cleanupHostComponent(child) + -- ROBLOX deviation: Roblox's DOM is based on child->parent references + child.Parent = nil + -- parentInstance.removeChild(child) + -- ROBLOX deviation: Guard against misuse by locking parent and forcing external cleanup via Destroy + child:Destroy() +end + +exports.removeChildFromContainer = function(_container: Container, child: Instance) + -- ROBLOX deviation: Containers don't have special behavior and comment nodes + -- have no datamodel equivalent, so just forward to the removeChild logic + exports.removeChild(_container, child) + -- if container.nodeType == COMMENT_NODE) + -- (container.parentNode: any).removeChild(child) + -- } else { + -- container.removeChild(child) + -- end +end + +exports.clearSuspenseBoundary = + function(parentInstance: Instance, suspenseInstance: SuspenseInstance) + -- ROBLOX FIXME: this is a major thing we need to fix for Suspense to work as a feature + unimplemented("clearSuspenseBoundary") + -- local node = suspenseInstance + -- -- Delete all nodes within this suspense boundary. + -- -- There might be nested nodes so we need to keep track of how + -- -- deep we are and only break out when we're back on top. + -- local depth = 0 + -- do { + -- local nextNode = node.nextSibling + -- parentInstance.removeChild(node) + -- if nextNode and nextNode.nodeType == COMMENT_NODE) + -- local data = ((nextNode: any).data: string) + -- if data == SUSPENSE_END_DATA) + -- if depth == 0) + -- parentInstance.removeChild(nextNode) + -- -- Retry if any event replaying was blocked on this. + -- retryIfBlockedOn(suspenseInstance) + -- return + -- } else { + -- depth-- + -- end + -- } else if + -- data == SUSPENSE_START_DATA or + -- data == SUSPENSE_PENDING_START_DATA or + -- data == SUSPENSE_FALLBACK_START_DATA + -- ) + -- depth++ + -- end + -- end + -- node = nextNode + -- } while (node) + -- -- TODO: Warn, we didn't find the end comment boundary. + -- -- Retry if any event replaying was blocked on this. + -- retryIfBlockedOn(suspenseInstance) + end + +exports.clearSuspenseBoundaryFromContainer = + function(container: Container, suspenseInstance: SuspenseInstance) + -- ROBLOX FIXME: this is a major thing we need to fix for Suspense to work as a feature + unimplemented("clearSuspenseBoundaryFromContainer") + -- if container.nodeType == COMMENT_NODE) + -- clearSuspenseBoundary((container.parentNode: any), suspenseInstance) + -- } else if container.nodeType == ELEMENT_NODE) + -- clearSuspenseBoundary((container: any), suspenseInstance) + -- } else { + -- -- Document nodes should never contain suspense boundaries. + -- end + -- -- Retry if any event replaying was blocked on this. + -- retryIfBlockedOn(container) + end + +exports.hideInstance = function(instance: Instance) + unimplemented("hideInstance") + -- -- TODO: Does this work for all element types? What about MathML? Should we + -- -- pass host context to this method? + -- instance = ((instance: any): HTMLElement) + -- local style = instance.style + -- if typeof style.setProperty == 'function') + -- style.setProperty('display', 'none', 'important') + -- } else { + -- style.display = 'none' + -- end +end + +-- ROBLOX deviation: error on TextInstance logic, which isn't applicable to Roblox +exports.hideTextInstance = function(textInstance: TextInstance): () + unimplemented("hideTextInstance") + -- textInstance.nodeValue = '' +end + +exports.unhideInstance = function(instance: Instance, props: Props) + unimplemented("unhideInstance") + -- instance = ((instance: any): HTMLElement) + -- local styleProp = props[STYLE] + -- local display = + -- styleProp ~= undefined and + -- styleProp ~= nil and + -- styleProp.hasOwnProperty('display') + -- ? styleProp.display + -- : nil + -- instance.style.display = dangerousStyleValue('display', display) +end + +-- ROBLOX deviation: error on TextInstance logic, which isn't applicable to Roblox +exports.unhideTextInstance = function(textInstance: TextInstance, text: string): () + unimplemented("unhideTextInstance") + -- textInstance.nodeValue = text +end + +exports.clearContainer = function(container: Container) + -- ROBLOX deviation: with Roblox, we can simply enumerate and remove the children + local parentInstance = container + for _, child in parentInstance:GetChildren() do + exports.removeChild(parentInstance, child) + end + -- if container.nodeType == ELEMENT_NODE) + -- ((container: any): Element).textContent = '' + -- } else if container.nodeType == DOCUMENT_NODE) + -- local body = ((container: any): Document).body + -- if body ~= nil) + -- body.textContent = '' + -- end + -- end +end + +-- -- ------------------- +-- -- Hydration +-- -- ------------------- + +-- export local supportsHydration = true + +-- exports.canHydrateInstance( +-- instance: HydratableInstance, +-- type: string, +-- props: Props, +-- ): nil | Instance { +-- if +-- instance.nodeType ~= ELEMENT_NODE or +-- type.toLowerCase() ~= instance.nodeName.toLowerCase() +-- ) +-- return nil +-- end +-- -- This has now been refined to an element node. +-- return ((instance: any): Instance) +-- end + +-- exports.canHydrateTextInstance( +-- instance: HydratableInstance, +-- text: string, +-- ): nil | TextInstance { +-- if text == '' or instance.nodeType ~= TEXT_NODE) +-- -- Empty strings are not parsed by HTML so there won't be a correct match here. +-- return nil +-- end +-- -- This has now been refined to a text node. +-- return ((instance: any): TextInstance) +-- end + +-- exports.canHydrateSuspenseInstance( +-- instance: HydratableInstance, +-- ): nil | SuspenseInstance { +-- if instance.nodeType ~= COMMENT_NODE) +-- -- Empty strings are not parsed by HTML so there won't be a correct match here. +-- return nil +-- end +-- -- This has now been refined to a suspense node. +-- return ((instance: any): SuspenseInstance) +-- end + +-- exports.isSuspenseInstanceFallback(instance: SuspenseInstance) +-- return instance.data == SUSPENSE_FALLBACK_START_DATA +-- end + +-- exports.registerSuspenseInstanceRetry( +-- instance: SuspenseInstance, +-- callback: () => void, +-- ) +-- instance._reactRetry = callback +-- end + +-- function getNextHydratable(node) +-- -- Skip non-hydratable nodes. +-- for (; node ~= nil; node = node.nextSibling) +-- local nodeType = node.nodeType +-- if nodeType == ELEMENT_NODE or nodeType == TEXT_NODE) +-- break +-- end +-- if enableSuspenseServerRenderer) +-- if nodeType == COMMENT_NODE) +-- local nodeData = (node: any).data +-- if +-- nodeData == SUSPENSE_START_DATA or +-- nodeData == SUSPENSE_FALLBACK_START_DATA or +-- nodeData == SUSPENSE_PENDING_START_DATA +-- ) +-- break +-- end +-- end +-- end +-- end +-- return (node: any) +-- end + +-- exports.getNextHydratableSibling( +-- instance: HydratableInstance, +-- ): nil | HydratableInstance { +-- return getNextHydratable(instance.nextSibling) +-- end + +-- exports.getFirstHydratableChild( +-- parentInstance: Container | Instance, +-- ): nil | HydratableInstance { +-- return getNextHydratable(parentInstance.firstChild) +-- end + +-- exports.hydrateInstance( +-- instance: Instance, +-- type: string, +-- props: Props, +-- rootContainerInstance: Container, +-- hostContext: HostContext, +-- internalInstanceHandle: Object, +-- ): nil | Array { +-- precacheFiberNode(internalInstanceHandle, instance) +-- -- TODO: Possibly defer this until the commit phase where all the events +-- -- get attached. +-- updateFiberProps(instance, props) +-- local parentNamespace: string +-- if __DEV__) +-- local hostContextDev = ((hostContext: any): HostContextDev) +-- parentNamespace = hostContextDev.namespace +-- } else { +-- parentNamespace = ((hostContext: any): HostContextProd) +-- end +-- return diffHydratedProperties( +-- instance, +-- type, +-- props, +-- parentNamespace, +-- rootContainerInstance, +-- ) +-- end + +-- exports.hydrateTextInstance( +-- textInstance: TextInstance, +-- text: string, +-- internalInstanceHandle: Object, +-- ): boolean { +-- precacheFiberNode(internalInstanceHandle, textInstance) +-- return diffHydratedText(textInstance, text) +-- end + +-- exports.hydrateSuspenseInstance( +-- suspenseInstance: SuspenseInstance, +-- internalInstanceHandle: Object, +-- ) +-- precacheFiberNode(internalInstanceHandle, suspenseInstance) +-- end + +-- exports.getNextHydratableInstanceAfterSuspenseInstance( +-- suspenseInstance: SuspenseInstance, +-- ): nil | HydratableInstance { +-- local node = suspenseInstance.nextSibling +-- -- Skip past all nodes within this suspense boundary. +-- -- There might be nested nodes so we need to keep track of how +-- -- deep we are and only break out when we're back on top. +-- local depth = 0 +-- while (node) +-- if node.nodeType == COMMENT_NODE) +-- local data = ((node: any).data: string) +-- if data == SUSPENSE_END_DATA) +-- if depth == 0) +-- return getNextHydratableSibling((node: any)) +-- } else { +-- depth-- +-- end +-- } else if +-- data == SUSPENSE_START_DATA or +-- data == SUSPENSE_FALLBACK_START_DATA or +-- data == SUSPENSE_PENDING_START_DATA +-- ) +-- depth++ +-- end +-- end +-- node = node.nextSibling +-- end +-- -- TODO: Warn, we didn't find the end comment boundary. +-- return nil +-- end + +-- -- Returns the SuspenseInstance if this node is a direct child of a +-- -- SuspenseInstance. I.e. if its previous sibling is a Comment with +-- -- SUSPENSE_x_START_DATA. Otherwise, nil. +-- exports.getParentSuspenseInstance( +-- targetInstance: Node, +-- ): nil | SuspenseInstance { +-- local node = targetInstance.previousSibling +-- -- Skip past all nodes within this suspense boundary. +-- -- There might be nested nodes so we need to keep track of how +-- -- deep we are and only break out when we're back on top. +-- local depth = 0 +-- while (node) +-- if node.nodeType == COMMENT_NODE) +-- local data = ((node: any).data: string) +-- if +-- data == SUSPENSE_START_DATA or +-- data == SUSPENSE_FALLBACK_START_DATA or +-- data == SUSPENSE_PENDING_START_DATA +-- ) +-- if depth == 0) +-- return ((node: any): SuspenseInstance) +-- } else { +-- depth-- +-- end +-- } else if data == SUSPENSE_END_DATA) +-- depth++ +-- end +-- end +-- node = node.previousSibling +-- end +-- return nil +-- end + +-- exports.commitHydratedContainer(container: Container): void { +-- -- Retry if any event replaying was blocked on this. +-- retryIfBlockedOn(container) +-- end + +-- exports.commitHydratedSuspenseInstance( +-- suspenseInstance: SuspenseInstance, +-- ): void { +-- -- Retry if any event replaying was blocked on this. +-- retryIfBlockedOn(suspenseInstance) +-- end + +-- exports.didNotMatchHydratedContainerTextInstance( +-- parentContainer: Container, +-- textInstance: TextInstance, +-- text: string, +-- ) +-- if __DEV__) +-- warnForUnmatchedText(textInstance, text) +-- end +-- end + +-- exports.didNotMatchHydratedTextInstance( +-- parentType: string, +-- parentProps: Props, +-- parentInstance: Instance, +-- textInstance: TextInstance, +-- text: string, +-- ) +-- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) +-- warnForUnmatchedText(textInstance, text) +-- end +-- end + +-- exports.didNotHydrateContainerInstance( +-- parentContainer: Container, +-- instance: HydratableInstance, +-- ) +-- if __DEV__) +-- if instance.nodeType == ELEMENT_NODE) +-- warnForDeletedHydratableElement(parentContainer, (instance: any)) +-- } else if instance.nodeType == COMMENT_NODE) +-- -- TODO: warnForDeletedHydratableSuspenseBoundary +-- } else { +-- warnForDeletedHydratableText(parentContainer, (instance: any)) +-- end +-- end +-- end + +-- exports.didNotHydrateInstance( +-- parentType: string, +-- parentProps: Props, +-- parentInstance: Instance, +-- instance: HydratableInstance, +-- ) +-- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) +-- if instance.nodeType == ELEMENT_NODE) +-- warnForDeletedHydratableElement(parentInstance, (instance: any)) +-- } else if instance.nodeType == COMMENT_NODE) +-- -- TODO: warnForDeletedHydratableSuspenseBoundary +-- } else { +-- warnForDeletedHydratableText(parentInstance, (instance: any)) +-- end +-- end +-- end + +-- exports.didNotFindHydratableContainerInstance( +-- parentContainer: Container, +-- type: string, +-- props: Props, +-- ) +-- if __DEV__) +-- warnForInsertedHydratedElement(parentContainer, type, props) +-- end +-- end + +-- exports.didNotFindHydratableContainerTextInstance( +-- parentContainer: Container, +-- text: string, +-- ) +-- if __DEV__) +-- warnForInsertedHydratedText(parentContainer, text) +-- end +-- end + +-- exports.didNotFindHydratableContainerSuspenseInstance( +-- parentContainer: Container, +-- ) +-- if __DEV__) +-- -- TODO: warnForInsertedHydratedSuspense(parentContainer) +-- end +-- end + +-- exports.didNotFindHydratableInstance( +-- parentType: string, +-- parentProps: Props, +-- parentInstance: Instance, +-- type: string, +-- props: Props, +-- ) +-- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) +-- warnForInsertedHydratedElement(parentInstance, type, props) +-- end +-- end + +-- exports.didNotFindHydratableTextInstance( +-- parentType: string, +-- parentProps: Props, +-- parentInstance: Instance, +-- text: string, +-- ) +-- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) +-- warnForInsertedHydratedText(parentInstance, text) +-- end +-- end + +-- exports.didNotFindHydratableSuspenseInstance( +-- parentType: string, +-- parentProps: Props, +-- parentInstance: Instance, +-- ) +-- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) +-- -- TODO: warnForInsertedHydratedSuspense(parentInstance) +-- end +-- end + +-- exports.getFundamentalComponentInstance( +-- fundamentalInstance: ReactDOMFundamentalComponentInstance, +-- ): Instance { +-- if enableFundamentalAPI) +-- local {currentFiber, impl, props, state} = fundamentalInstance +-- local instance = impl.getInstance(null, props, state) +-- precacheFiberNode(currentFiber, instance) +-- return instance +-- end +-- -- Because of the flag above, this gets around the Flow error +-- return (null: any) +-- end + +-- exports.mountFundamentalComponent( +-- fundamentalInstance: ReactDOMFundamentalComponentInstance, +-- ): void { +-- if enableFundamentalAPI) +-- local {impl, instance, props, state} = fundamentalInstance +-- local onMount = impl.onMount +-- if onMount ~= undefined) +-- onMount(null, instance, props, state) +-- end +-- end +-- end + +-- exports.shouldUpdateFundamentalComponent( +-- fundamentalInstance: ReactDOMFundamentalComponentInstance, +-- ): boolean { +-- if enableFundamentalAPI) +-- local {impl, prevProps, props, state} = fundamentalInstance +-- local shouldUpdate = impl.shouldUpdate +-- if shouldUpdate ~= undefined) +-- return shouldUpdate(null, prevProps, props, state) +-- end +-- end +-- return true +-- end + +-- exports.updateFundamentalComponent( +-- fundamentalInstance: ReactDOMFundamentalComponentInstance, +-- ): void { +-- if enableFundamentalAPI) +-- local {impl, instance, prevProps, props, state} = fundamentalInstance +-- local onUpdate = impl.onUpdate +-- if onUpdate ~= undefined) +-- onUpdate(null, instance, prevProps, props, state) +-- end +-- end +-- end + +-- exports.unmountFundamentalComponent( +-- fundamentalInstance: ReactDOMFundamentalComponentInstance, +-- ): void { +-- if enableFundamentalAPI) +-- local {impl, instance, props, state} = fundamentalInstance +-- local onUnmount = impl.onUnmount +-- if onUnmount ~= undefined) +-- onUnmount(null, instance, props, state) +-- end +-- end +-- end + +-- exports.getInstanceFromNode(node: HTMLElement): nil | Object { +-- return getClosestInstanceFromNode(node) or nil +-- end + +-- local clientId: number = 0 +-- exports.makeClientId(): OpaqueIDType { +-- return 'r:' + (clientId++).toString(36) +-- end + +-- exports.makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType { +-- local id = 'r:' + (clientId++).toString(36) +-- return { +-- toString() +-- warnOnAccessInDEV() +-- return id +-- }, +-- valueOf() +-- warnOnAccessInDEV() +-- return id +-- }, +-- end +-- end + +-- exports.isOpaqueHydratingObject(value: mixed): boolean { +-- return ( +-- value ~= nil and +-- typeof value == 'table’' and +-- value.$$typeof == REACT_OPAQUE_ID_TYPE +-- ) +-- end + +-- exports.makeOpaqueHydratingObject( +-- attemptToReadValue: () => void, +-- ): OpaqueIDType { +-- return { +-- $$typeof: REACT_OPAQUE_ID_TYPE, +-- toString: attemptToReadValue, +-- valueOf: attemptToReadValue, +-- end +-- end + +exports.preparePortalMount = function(portalInstance: Instance): () + -- ROBLOX TODO: Revisit this logic and see if any of it applies + -- if enableEagerRootListeners then + -- listenToAllSupportedEvents(portalInstance) + -- else + -- listenToReactEvent('onMouseEnter', portalInstance) + -- end +end + +-- exports.prepareScopeUpdate( +-- scopeInstance: ReactScopeInstance, +-- internalInstanceHandle: Object, +-- ): void { +-- if enableScopeAPI) +-- precacheFiberNode(internalInstanceHandle, scopeInstance) +-- end +-- end + +-- exports.getInstanceFromScope( +-- scopeInstance: ReactScopeInstance, +-- ): nil | Object { +-- if enableScopeAPI) +-- return getFiberFromScopeInstance(scopeInstance) +-- end +-- return nil +-- end + +-- export local supportsTestSelectors = true + +-- exports.findFiberRoot(node: Instance): nil | FiberRoot { +-- local stack = [node] +-- local index = 0 +-- while (index < stack.length) +-- local current = stack[index++] +-- if isContainerMarkedAsRoot(current)) +-- return ((getInstanceFromNodeDOMTree(current): any): FiberRoot) +-- end +-- stack.push(...current.children) +-- end +-- return nil +-- end + +-- exports.getBoundingRect(node: Instance): BoundingRect { +-- local rect = node.getBoundingClientRect() +-- return { +-- x: rect.left, +-- y: rect.top, +-- width: rect.width, +-- height: rect.height, +-- end +-- end + +-- exports.matchAccessibilityRole(node: Instance, role: string): boolean { +-- if hasRole(node, role)) +-- return true +-- end + +-- return false +-- end + +-- exports.getTextContent(fiber: Fiber): string | nil { +-- switch (fiber.tag) +-- case HostComponent: +-- local textContent = '' +-- local childNodes = fiber.stateNode.childNodes +-- for (local i = 0; i < childNodes.length; i++) +-- local childNode = childNodes[i] +-- if childNode.nodeType == Node.TEXT_NODE) +-- textContent += childNode.textContent +-- end +-- end +-- return textContent +-- case HostText: +-- return fiber.stateNode.textContent +-- end + +-- return nil +-- end + +-- exports.isHiddenSubtree(fiber: Fiber): boolean { +-- return fiber.tag == HostComponent and fiber.memoizedProps.hidden == true +-- end + +-- exports.setFocusIfFocusable(node: Instance): boolean { +-- -- The logic for determining if an element is focusable is kind of complex, +-- -- and since we want to actually change focus anyway- we can just skip it. +-- -- Instead we'll just listen for a "focus" event to verify that focus was set. +-- -- +-- -- We could compare the node to document.activeElement after focus, +-- -- but this would not handle the case where application code managed focus to automatically blur. +-- local didFocus = false +-- local handleFocus = () => { +-- didFocus = true +-- end + +-- local element = ((node: any): HTMLElement) +-- try { +-- element.addEventListener('focus', handleFocus) +-- (element.focus or HTMLElement.prototype.focus).call(element) +-- } finally { +-- element.removeEventListener('focus', handleFocus) +-- end + +-- return didFocus +-- end + +-- type RectRatio = { +-- ratio: number, +-- rect: BoundingRect, +-- end + +-- exports.setupIntersectionObserver( +-- targets: Array, +-- callback: ObserveVisibleRectsCallback, +-- options?: IntersectionObserverOptions, +-- ): {| +-- disconnect: () => void, +-- observe: (instance: Instance) => void, +-- unobserve: (instance: Instance) => void, +-- |} { +-- local rectRatioCache: Map = new Map() +-- targets.forEach(target => { +-- rectRatioCache.set(target, { +-- rect: getBoundingRect(target), +-- ratio: 0, +-- }) +-- }) + +-- local handleIntersection = (entries: Array) => { +-- entries.forEach(entry => { +-- local {boundingClientRect, intersectionRatio, target} = entry +-- rectRatioCache.set(target, { +-- rect: { +-- x: boundingClientRect.left, +-- y: boundingClientRect.top, +-- width: boundingClientRect.width, +-- height: boundingClientRect.height, +-- }, +-- ratio: intersectionRatio, +-- }) +-- }) + +-- callback(Array.from(rectRatioCache.values())) +-- end + +-- local observer = new IntersectionObserver(handleIntersection, options) +-- targets.forEach(target => { +-- observer.observe((target: any)) +-- }) + +-- return { +-- disconnect: () => observer.disconnect(), +-- observe: target => { +-- rectRatioCache.set(target, { +-- rect: getBoundingRect(target), +-- ratio: 0, +-- }) +-- observer.observe((target: any)) +-- }, +-- unobserve: target => { +-- rectRatioCache.delete(target) +-- observer.unobserve((target: any)) +-- }, +-- end +-- end + +return exports diff --git a/samples/Luau/Replicator.luau b/samples/Luau/Replicator.luau new file mode 100644 index 0000000000..69d3012104 --- /dev/null +++ b/samples/Luau/Replicator.luau @@ -0,0 +1,546 @@ +--// Authored by @ColRealPro (https://github.com/colrealpro) +--// Fetched from (https://github.com/ColRealPro/SimplyReplicate/blob/main/lib/Replicator.luau) +--// Licensed under the MIT License (https://github.com/ColRealPro/SimplyReplicate/blob/main/LICENSE) + +--!strict + +-- // Services +local Players = game:GetService("Players") +local RunService = game:GetService("RunService") + +-- // Modules +local NetworkManager = require(script.Parent.NetworkManager) +local console = require(script.Parent.console) +local Maid = require(script.Parent.Maid) +local Signal = require(script.Parent.Signal) + +-- console.WriteEnv() +local print = console.log + +-- // Internal Types +type dataType = T +type data = { [string]: dataType } +type DataStructure = typeof(setmetatable({} :: data, {})) + +type ReplicatorImpl = { + __index: ReplicatorImpl, + __tostring: (self: Replicator) -> string, + + new: (identifier: string | Instance, data: data) -> Replicator, + changeStates: (self: Replicator, changedStates: data, players: ({ Player } | Player)?) -> (), + syncPlayer: (self: Replicator, player: Player) -> (), + _prepareReplication: (self: Replicator) -> (), + _setState: (self: Replicator, state: string, value: any, players: { Player }?) -> (), + _createDataStructure: (self: Replicator, data: data) -> DataStructure, + _fetchFromServer: (self: Replicator) -> (), + _recieveUpdate: (self: Replicator, data: { syncData: { Remove: { string }?, Modify: { { Index: string, NewValue: any } }? }?, updateData: { [string]: any } }) -> (), + get: (self: Replicator, index: string?) -> { [string]: any } | any, + getForPlayer: (self: Replicator, player: Player, index: string?) -> { [string]: any } | any, + Destroy: (self: Replicator) -> () +} + +type Replicator = typeof(setmetatable({} :: { + identifier: string | Instance, + _maid: typeof(Maid), + _networkManager: NetworkManager.NetworkManager, + _isPlayer: boolean, + _context: "Server" | "Client" | string, + _deferringReplication: boolean, + _replicationNeeded: { Updates: { { Index: string, NewValue: any, Players: { Player }?, updateOrderTime: number } }, Syncs: { { Remove: { string }, Modify: { { Index: string, NewValue: any } }, Player: Player, SyncOrderTime: number } } }, + -- _dataStructure: DataStructure, + _localDataCopy: DataStructure, + _userStateCopies: { [Player]: DataStructure }, + _originalDataStructure: { [string]: any }, + StateChanged: Signal.Signal +}, {} :: ReplicatorImpl)) + +--[=[ + @class Replicator + + This is the main class of SimplyReplicate + Replicators are used to replicate data from the server to the client with ease + + Here is an example implementation of a replicator: + + **Server:** + ```lua + local Replicator = require(path.to.module) + + local GameStateReplicator = Replicator.new("GameState", { + Status = "Waiting", + RoundStarted = false, + }) + + task.wait(3) + + GameStateReplicator:changeStates({ + Status = "Game starts soon", + }) + + task.wait(3) + + GameStateReplicator:changeStates({ + Status = "Game started", + RoundStarted = true, + }) + ``` + + **Client:** + ```lua + local Replicator = require(path.to.module) + + local GameStateReplicator = Replicator.new("GameState") + + GameStateReplicator.StateChanged:Connect(function(state, value) + print("State changed", state, value) + end) + ``` +]=] +local Replicator: ReplicatorImpl = {} :: ReplicatorImpl +Replicator.__index = Replicator +Replicator.__tostring = function(self) + return `Replicator({tostring(self.identifier)})` +end + +--[=[ + Constructs a new replicator + + @yields + @function new + @within Replicator + @param identifier string | Instance + @param data { [string]: any } -- The data that will be replicated to clients + @return Replicator + + :::warning + When creating a replicator on the client, this function will yield until the initial data has been fetched from the server + ::: + + :::info + Note that if you are using strict luau, you will want to specify a type for the data parameter, + if you let Luau infer the type, when you go to change the state, it will want you to include every state as it wont have an option to be nil. + ::: +]=] +function Replicator.new(identifier: string | Instance, data: T?) + local self = setmetatable({}, Replicator) :: Replicator + + self.identifier = identifier + self._maid = Maid.new() + self._networkManager = NetworkManager.new(identifier) + self._isPlayer = typeof(identifier) == "Instance" and identifier:IsA("Player") + self._context = RunService:IsServer() and "Server" or "Client" + self._userStateCopies = {} + + -- // Signals + self.StateChanged = Signal.new() + + if self._context == "Server" then + if not data then + error("Data must be provided when creating a replicator on the server.", 2) + end + + if typeof(data) ~= "table" then + error("Data must be a table.", 2) + end + + self._replicationNeeded = { + Updates = {}, + Syncs = {} + } + self._deferringReplication = false + self._originalDataStructure = table.freeze(table.clone(data)) + self._localDataCopy = self:_createDataStructure(data) + -- self._dataStructure = self:_createDataStructure(data) + + self._networkManager:listenToFetch("FETCH_REPLICATOR", function(player) + if self._isPlayer and player ~= identifier then + return + end + + return self:get() + end) + else + self:_fetchFromServer() + self._networkManager:listen("UPDATE_REPLICATOR", function(data) + if data.identifier == self.identifier then + print("Recieved update from server") + self:_recieveUpdate(data :: { syncData: { Remove: { string }?, Modify: { { Index: string, NewValue: any } }? }?, updateData: { [string]: any } }) + end + end) + end + + return self +end + +--[=[ + Changes states in the replicator and replicate whatever changes to the clients + + @param changedStates { [string]: any? } -- The states that have been changed and their new values + @error "Invalid state" -- Thrown when the state does not exist in the data structure +]=] +function Replicator:changeStates(changedStates: data, players: ({ Player } | Player)?) + if self._context == "Client" then + error("This function can only be called on the server", 2) + end + + local playersTbl = type(players) == "table" and players or { players :: Player } + + for state, value in changedStates do + self:_setState(state, value, players and playersTbl) + end +end + +--[=[ + Syncs a player's state with the server's state + + @param player Player +]=] +function Replicator:syncPlayer(player: Player) + if self._context == "Client" then + error("This function can only be called on the server", 2) + end + + local userStateCopy = self._userStateCopies[player] + + if not userStateCopy then + print(`{player.Name} is already in sync with the server`) + return + end + + local remove = {} + local modify = {} + + for state, value in next, userStateCopy do + if not rawget(self._localDataCopy :: any, state) then + table.insert(remove, state) + elseif self._localDataCopy[state] ~= value then + -- FIXME: If a value is modified in the same frame before a sync, it will be put in the sync table even though it has not replication + -- this isn't a huge issue but it leads to wasted data being sent to the client as the original value will be dropped during replication + -- meaning that replicating the value as part of a sync is now useless + table.insert(modify, { + Index = state, + NewValue = self._localDataCopy[state] + }) + end + end + + table.insert(self._replicationNeeded.Syncs, { + Remove = remove, + Modify = modify, + Player = player, + + SyncOrderTime = os.clock() + }) + + self:_prepareReplication() +end + +function Replicator:_prepareReplication() + print("Preparing replication") + self._deferringReplication = true + + task.defer(function() + self._deferringReplication = false + + local replicationNeeded = self._replicationNeeded + self._replicationNeeded = { + Updates = {}, + Syncs = {} + } + + local replicationData = { + ["$all"] = { + updateData = {}, + } + } + + type PlayerReplicationData = { + syncData: { Remove: { string }?, Modify: { { Index: string, NewValue: any } }? }, + updateData: { [string]: any }, + internalData: { syncOrderTime: number? } + } + + local playerReplicationDataTemplate = { + syncData = {}, + updateData = {}, + internalData = {} + } + + -- Evaluate syncs first to make sure that we have enough data to keep the replication in order + -- and have the client in sync with the server + for _, v in replicationNeeded.Syncs do + local player = v.Player + local remove = v.Remove + local modify = v.Modify + local syncOrderTime = v.SyncOrderTime + + if not replicationData[player] then + replicationData[player] = table.clone(playerReplicationDataTemplate) :: PlayerReplicationData + end + + local previousSyncOrderTime = replicationData[player].internalData.syncOrderTime + if previousSyncOrderTime and syncOrderTime < previousSyncOrderTime then + print(`Dropping sync for player {player.Name} as a newer sync has already been evaluated`) + continue + end + + replicationData[player].syncData.Remove = remove + replicationData[player].syncData.Modify = modify + + replicationData[player].internalData.syncOrderTime = syncOrderTime + end + + for _, v in replicationNeeded.Updates do + local players = v.Players + local index = v.Index + local newValue = v.NewValue + + if players then + for _, player in players do + if not replicationData[player] then + replicationData[player] = table.clone(playerReplicationDataTemplate) :: PlayerReplicationData + end + + local syncOrderTime = replicationData[player].internalData.syncOrderTime + if syncOrderTime and v.updateOrderTime < syncOrderTime then + print(`Dropping update for state {v.Index} for player {player.Name} as a sync has already been evaluated after this update`) + continue + end + + replicationData[player].updateData[index] = newValue + end + else + replicationData["$all"].updateData[index] = newValue + end + end + + print("About to replicate data, sending out:", replicationData, "created from:", replicationNeeded) + + self._networkManager:send("UPDATE_REPLICATOR", { + identifier = self.identifier, + updateData = replicationData["$all"].updateData, + }) + + for player: Player | string, data in replicationData do + -- if player == "$all" then + -- self._networkManager:send("UPDATE_REPLICATOR", { + -- identifier = self.identifier, + -- updateData = data.updateData, + -- }) + -- continue + -- end + + if player == "$all" then + continue + end + + self._networkManager:send("UPDATE_REPLICATOR", { + identifier = self.identifier, + syncData = data.syncData, + updateData = data.updateData, + }, {player :: Player}) + end + end) +end + +function Replicator:_setState(state: string, value: any, players: { Player }?) + -- self._dataStructure[state] = value + -- self._replicationNeeded[state] = value + + table.insert(self._replicationNeeded.Updates, { + Index = state, + NewValue = value, + Players = players, + updateOrderTime = os.clock() + }) + + if not players then + self._localDataCopy[state] = value + players = Players:GetPlayers() + + task.defer(function() + self.StateChanged:Fire(state, value) + end) + end + + for _, player in players :: { Player } do + if not self._userStateCopies[player] then + self._userStateCopies[player] = self:_createDataStructure(self._originalDataStructure) + end + + self._userStateCopies[player][state] = value + end + + if not self._deferringReplication then + self:_prepareReplication() + end + + print(`Successfully changed state {state} to {value}`) +end + +function Replicator:_createDataStructure(data: { [string]: any }) : DataStructure + local dataStructure = setmetatable(table.clone(data), { + __index = function(self, key) + error(string.format("Invalid state %s, does not exist in the data structure.", + tostring(key)), 4) + end, + __newindex = function(self, key, value) + error(string.format("Invalid state %s, does not exist in the data structure.", + tostring(key)), 4) + end + }) + + return dataStructure +end + +function Replicator:_fetchFromServer() + local data = self._networkManager:fetch("FETCH_REPLICATOR", { + identifier = self.identifier, + }) :: { [string]: any } + + self._localDataCopy = self:_createDataStructure(data) + + print("Successfully fetched initial data from server") +end + +function Replicator:_recieveUpdate(data: { syncData: { Remove: { string }?, Modify: { { Index: string, NewValue: any } }? }?, updateData: { [string]: any } }) + -- for state, value in data do + -- self._localDataCopy[state] = value + + -- task.defer(function() + -- self.StateChanged:Fire(state, value) + -- end) + -- end + + -- print("Handled update from server") + + local syncData = data.syncData + local updateData = data.updateData + + if syncData then + local remove = syncData.Remove + local modify = syncData.Modify + + if remove then + for _, state in remove do + self._localDataCopy[state] = nil + + task.defer(function() + if self._localDataCopy[state] then + return + end + + self.StateChanged:Fire(state, nil) + end) + end + end + + if modify then + for _, v in modify do + local index = v.Index + local newValue = v.NewValue + + if self._localDataCopy[index] == newValue then + print(`Droping update for state {index} as the value is already the same`) + continue + end + + self._localDataCopy[index] = newValue + + task.defer(function() + if self._localDataCopy[index] ~= newValue then + return + end + + self.StateChanged:Fire(index, newValue) + end) + end + end + end + + for state, value in updateData do + self._localDataCopy[state] = value + + task.defer(function() + if self._localDataCopy[state] ~= value then + return + end + + self.StateChanged:Fire(state, value) + end) + end +end + +--[=[ + Returns the current state stored in the replicator + + @error "Invalid state" -- Thrown when the state does not exist in the data structure + @param index string? -- If provided, returns the state given, otherwise returns all states +]=] +function Replicator:get(index: string?): { [string]: any } | any + if index then + return self._localDataCopy[index] + end + + local dataStructure = self._localDataCopy + local data = {} + + -- Manually clone the table to remove the metatable + for state, value in next, dataStructure do + data[state] = value + end + + return data +end + +--[=[ + Returns the current state stored in the replicator for a specific player + + @error "Invalid state" -- Thrown when the state does not exist in the data structure + @param player Player + @param index string? -- If provided, returns the state given, otherwise returns all states +]=] +function Replicator:getForPlayer(player: Player, index: string?): { [string]: any } | any + if self._context == "Client" then + error("This function can only be called on the server", 2) + end + + if not self._userStateCopies[player] then + return self:get(index) + end + + if index then + return self._userStateCopies[player][index] + end + + local dataStructure = self._userStateCopies[player] + local data = {} + + -- Manually clone the table to remove the metatable + for state, value in next, dataStructure do + data[state] = value + end + + return data +end + +--[=[ + This function should be called when you are done using the replicator +]=] +function Replicator:Destroy() + self._networkManager:Destroy() + self._maid:DoCleaning() + + table.clear(self :: any) + setmetatable(self :: any, { + __metatable = "The metatable is locked", + __index = function() + error("This replicator has been destroyed", 2) + end, + __newindex = function() + error("This replicator has been destroyed", 2) + end + }) +end + +return Replicator diff --git a/samples/Luau/Signal.luau b/samples/Luau/Signal.luau new file mode 100644 index 0000000000..ccf7aebc4f --- /dev/null +++ b/samples/Luau/Signal.luau @@ -0,0 +1,437 @@ +--// Coauthored by @Sleitnick (https://github.com/sleitnick) and @Stravant (https://github.com/stravant) +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/signal/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +--!optimize 2 +--!strict +--!native + +-- ----------------------------------------------------------------------------- +-- Batched Yield-Safe Signal Implementation -- +-- This is a Signal class which has effectively identical behavior to a -- +-- normal RBXScriptSignal, with the only difference being a couple extra -- +-- stack frames at the bottom of the stack trace when an error is thrown. -- +-- This implementation caches runner coroutines, so the ability to yield in -- +-- the signal handlers comes at minimal extra cost over a naive signal -- +-- implementation that either always or never spawns a thread. -- +-- -- +-- License: -- +-- Licensed under the MIT license. -- +-- -- +-- Authors: -- +-- stravant - July 31st, 2021 - Created the file. -- +-- sleitnick - August 3rd, 2021 - Modified for Knit. -- +-- ----------------------------------------------------------------------------- + +-- Signal types +export type Connection = { + Disconnect: (self: Connection) -> (), + Destroy: (self: Connection) -> (), + Connected: boolean, +} + +export type Signal = { + Fire: (self: Signal, T...) -> (), + FireDeferred: (self: Signal, T...) -> (), + Connect: (self: Signal, fn: (T...) -> ()) -> Connection, + Once: (self: Signal, fn: (T...) -> ()) -> Connection, + DisconnectAll: (self: Signal) -> (), + GetConnections: (self: Signal) -> { Connection }, + Destroy: (self: Signal) -> (), + Wait: (self: Signal) -> T..., +} + +-- The currently idle thread to run the next handler on +local freeRunnerThread = nil + +-- Function which acquires the currently idle handler runner thread, runs the +-- function fn on it, and then releases the thread, returning it to being the +-- currently idle one. +-- If there was a currently idle runner thread already, that's okay, that old +-- one will just get thrown and eventually GCed. +local function acquireRunnerThreadAndCallEventHandler(fn, ...) + local acquiredRunnerThread = freeRunnerThread + freeRunnerThread = nil + fn(...) + -- The handler finished running, this runner thread is free again. + freeRunnerThread = acquiredRunnerThread +end + +-- Coroutine runner that we create coroutines of. The coroutine can be +-- repeatedly resumed with functions to run followed by the argument to run +-- them with. +local function runEventHandlerInFreeThread(...) + acquireRunnerThreadAndCallEventHandler(...) + while true do + acquireRunnerThreadAndCallEventHandler(coroutine.yield()) + end +end + +--[=[ + @within Signal + @interface SignalConnection + .Connected boolean + .Disconnect (SignalConnection) -> () + + Represents a connection to a signal. + ```lua + local connection = signal:Connect(function() end) + print(connection.Connected) --> true + connection:Disconnect() + print(connection.Connected) --> false + ``` +]=] + +-- Connection class +local Connection = {} +Connection.__index = Connection + +function Connection:Disconnect() + if not self.Connected then + return + end + self.Connected = false + + -- Unhook the node, but DON'T clear it. That way any fire calls that are + -- currently sitting on this node will be able to iterate forwards off of + -- it, but any subsequent fire calls will not hit it, and it will be GCed + -- when no more fire calls are sitting on it. + if self._signal._handlerListHead == self then + self._signal._handlerListHead = self._next + else + local prev = self._signal._handlerListHead + while prev and prev._next ~= self do + prev = prev._next + end + if prev then + prev._next = self._next + end + end +end + +Connection.Destroy = Connection.Disconnect + +-- Make Connection strict +setmetatable(Connection, { + __index = function(_tb, key) + error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2) + end, + __newindex = function(_tb, key, _value) + error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2) + end, +}) + +--[=[ + @within Signal + @type ConnectionFn (...any) -> () + + A function connected to a signal. +]=] + +--[=[ + @class Signal + + A Signal is a data structure that allows events to be dispatched + and observed. + + This implementation is a direct copy of the de facto standard, [GoodSignal](https://devforum.roblox.com/t/lua-signal-class-comparison-optimal-goodsignal-class/1387063), + with some added methods and typings. + + For example: + ```lua + local signal = Signal.new() + + -- Subscribe to a signal: + signal:Connect(function(msg) + print("Got message:", msg) + end) + + -- Dispatch an event: + signal:Fire("Hello world!") + ``` +]=] +local Signal = {} +Signal.__index = Signal + +--[=[ + Constructs a new Signal + + @return Signal +]=] +function Signal.new(): Signal + local self = setmetatable({ + _handlerListHead = false, + _proxyHandler = nil, + _yieldedThreads = nil, + }, Signal) + + return self +end + +--[=[ + Constructs a new Signal that wraps around an RBXScriptSignal. + + @param rbxScriptSignal RBXScriptSignal -- Existing RBXScriptSignal to wrap + @return Signal + + For example: + ```lua + local signal = Signal.Wrap(workspace.ChildAdded) + signal:Connect(function(part) print(part.Name .. " added") end) + Instance.new("Part").Parent = workspace + ``` +]=] +function Signal.Wrap(rbxScriptSignal: RBXScriptSignal): Signal + assert( + typeof(rbxScriptSignal) == "RBXScriptSignal", + "Argument #1 to Signal.Wrap must be a RBXScriptSignal; got " .. typeof(rbxScriptSignal) + ) + + local signal = Signal.new() + signal._proxyHandler = rbxScriptSignal:Connect(function(...) + signal:Fire(...) + end) + + return signal +end + +--[=[ + Checks if the given object is a Signal. + + @param obj any -- Object to check + @return boolean -- `true` if the object is a Signal. +]=] +function Signal.Is(obj: any): boolean + return type(obj) == "table" and getmetatable(obj) == Signal +end + +--[=[ + @param fn ConnectionFn + @return SignalConnection + + Connects a function to the signal, which will be called anytime the signal is fired. + ```lua + signal:Connect(function(msg, num) + print(msg, num) + end) + + signal:Fire("Hello", 25) + ``` +]=] +function Signal:Connect(fn) + local connection = setmetatable({ + Connected = true, + _signal = self, + _fn = fn, + _next = false, + }, Connection) + + if self._handlerListHead then + connection._next = self._handlerListHead + self._handlerListHead = connection + else + self._handlerListHead = connection + end + + return connection +end + +--[=[ + @deprecated v1.3.0 -- Use `Signal:Once` instead. + @param fn ConnectionFn + @return SignalConnection +]=] +function Signal:ConnectOnce(fn) + return self:Once(fn) +end + +--[=[ + @param fn ConnectionFn + @return SignalConnection + + Connects a function to the signal, which will be called the next time the signal fires. Once + the connection is triggered, it will disconnect itself. + ```lua + signal:Once(function(msg, num) + print(msg, num) + end) + + signal:Fire("Hello", 25) + signal:Fire("This message will not go through", 10) + ``` +]=] +function Signal:Once(fn) + local connection + local done = false + + connection = self:Connect(function(...) + if done then + return + end + + done = true + connection:Disconnect() + fn(...) + end) + + return connection +end + +function Signal:GetConnections() + local items = {} + + local item = self._handlerListHead + while item do + table.insert(items, item) + item = item._next + end + + return items +end + +-- Disconnect all handlers. Since we use a linked list it suffices to clear the +-- reference to the head handler. +--[=[ + Disconnects all connections from the signal. + ```lua + signal:DisconnectAll() + ``` +]=] +function Signal:DisconnectAll() + local item = self._handlerListHead + while item do + item.Connected = false + item = item._next + end + self._handlerListHead = false + + local yieldedThreads = rawget(self, "_yieldedThreads") + if yieldedThreads then + for thread in yieldedThreads do + if coroutine.status(thread) == "suspended" then + warn(debug.traceback(thread, "signal disconnected; yielded thread cancelled", 2)) + task.cancel(thread) + end + end + table.clear(self._yieldedThreads) + end +end + +-- Signal:Fire(...) implemented by running the handler functions on the +-- coRunnerThread, and any time the resulting thread yielded without returning +-- to us, that means that it yielded to the Roblox scheduler and has been taken +-- over by Roblox scheduling, meaning we have to make a new coroutine runner. +--[=[ + @param ... any + + Fire the signal, which will call all of the connected functions with the given arguments. + ```lua + signal:Fire("Hello") + + -- Any number of arguments can be fired: + signal:Fire("Hello", 32, {Test = "Test"}, true) + ``` +]=] +function Signal:Fire(...) + local item = self._handlerListHead + while item do + if item.Connected then + if not freeRunnerThread then + freeRunnerThread = coroutine.create(runEventHandlerInFreeThread) + end + task.spawn(freeRunnerThread, item._fn, ...) + end + item = item._next + end +end + +--[=[ + @param ... any + + Same as `Fire`, but uses `task.defer` internally & doesn't take advantage of thread reuse. + ```lua + signal:FireDeferred("Hello") + ``` +]=] +function Signal:FireDeferred(...) + local item = self._handlerListHead + while item do + local conn = item + task.defer(function(...) + if conn.Connected then + conn._fn(...) + end + end, ...) + item = item._next + end +end + +--[=[ + @return ... any + @yields + + Yields the current thread until the signal is fired, and returns the arguments fired from the signal. + Yielding the current thread is not always desirable. If the desire is to only capture the next event + fired, using `Once` might be a better solution. + ```lua + task.spawn(function() + local msg, num = signal:Wait() + print(msg, num) --> "Hello", 32 + end) + signal:Fire("Hello", 32) + ``` +]=] +function Signal:Wait() + local yieldedThreads = rawget(self, "_yieldedThreads") + if not yieldedThreads then + yieldedThreads = {} + rawset(self, "_yieldedThreads", yieldedThreads) + end + + local thread = coroutine.running() + yieldedThreads[thread] = true + + self:Once(function(...) + yieldedThreads[thread] = nil + task.spawn(thread, ...) + end) + + return coroutine.yield() +end + +--[=[ + Cleans up the signal. + + Technically, this is only necessary if the signal is created using + `Signal.Wrap`. Connections should be properly GC'd once the signal + is no longer referenced anywhere. However, it is still good practice + to include ways to strictly clean up resources. Calling `Destroy` + on a signal will also disconnect all connections immediately. + ```lua + signal:Destroy() + ``` +]=] +function Signal:Destroy() + self:DisconnectAll() + + local proxyHandler = rawget(self, "_proxyHandler") + if proxyHandler then + proxyHandler:Disconnect() + end +end + +-- Make signal strict +setmetatable(Signal, { + __index = function(_tb, key) + error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2) + end, + __newindex = function(_tb, key, _value) + error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2) + end, +}) + +return table.freeze({ + new = Signal.new, + Wrap = Signal.Wrap, + Is = Signal.Is, +}) diff --git a/samples/Luau/createProcessor.luau b/samples/Luau/createProcessor.luau deleted file mode 100644 index d55aba076e..0000000000 --- a/samples/Luau/createProcessor.luau +++ /dev/null @@ -1,22 +0,0 @@ ---// Authored by @sinlerdev (https://github.com/sinlerdev) ---// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/createProcessor.luau) ---// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) - ---!strict -local function createProcessor(valueChecker: (a: any, b: any) -> boolean, cleaner: (taskItem: any) -> ()) - return function(old: T, new: T, isDestroying: boolean) - if isDestroying then - cleaner(old) - return false - end - - if valueChecker(old, new) then - return false - end - - cleaner(old) - return true - end -end - -return createProcessor diff --git a/samples/Luau/createSignal.luau b/samples/Luau/createSignal.luau deleted file mode 100644 index 5ffd461fff..0000000000 --- a/samples/Luau/createSignal.luau +++ /dev/null @@ -1,37 +0,0 @@ ---// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) ---// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/createSignal.luau) ---// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) - ---!strict - -local function createSignal() - local listeners = {} - local signal = {} - - function signal:Connect(listener) - listeners[listener] = true - - local connection = { - Connected = true, - } - - function connection.Disconnect() - connection.Connected = false - listeners[listener] = nil - end - connection.disconnect = connection.Disconnect - - return connection - end - signal.connect = signal.Connect - - local function fire(...) - for listener, _ in pairs(listeners) do - spawn(listener, ...) - end - end - - return signal, fire -end - -return createSignal diff --git a/samples/Luau/equals.luau b/samples/Luau/equals.luau deleted file mode 100644 index 6b259e0359..0000000000 --- a/samples/Luau/equals.luau +++ /dev/null @@ -1,17 +0,0 @@ ---// Authored by @sinlerdev (https://github.com/sinlerdev) ---// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/equals.luau) ---// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) - ---!strict - -local function equals(a: any, b: any): boolean - -- INFO: Vinum doesn't officially support immutability currently- so, we just assume - -- that every new table entry is not equal. See issue 26 - if type(a) == "table" then - return false - end - - return a == b -end - -return equals diff --git a/samples/Luau/getEnv.luau b/samples/Luau/getEnv.luau new file mode 100644 index 0000000000..d4877c3d17 --- /dev/null +++ b/samples/Luau/getEnv.luau @@ -0,0 +1,39 @@ +--// Authored by @Vocksel (https://github.com/vocksel) +--// Fetched from (https://github.com/flipbook-labs/module-loader/blob/main/src/init.lua) +--// Licensed under the MIT License (https://github.com/flipbook-labs/module-loader/blob/main/LICENSE) + +--!optimize 2 +--!strict +--!native + +local baseEnv = getfenv() + +local function getEnv(scriptRelativeTo: LuaSourceContainer?, globals: { [any]: any }?) + local newEnv = {} + + setmetatable(newEnv, { + __index = function(_, key) + if key ~= "plugin" then + return baseEnv[key] + else + return nil + end + end, + }) + + newEnv._G = globals + newEnv.script = scriptRelativeTo + + local realDebug = debug + + newEnv.debug = setmetatable({ + traceback = function(message) + -- Block traces to prevent overly verbose TestEZ output + return message or "" + end, + }, { __index = realDebug }) + + return newEnv +end + +return getEnv diff --git a/samples/Luau/isEmpty.luau b/samples/Luau/isEmpty.luau deleted file mode 100644 index c90e654801..0000000000 --- a/samples/Luau/isEmpty.luau +++ /dev/null @@ -1,21 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/isEmpty.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---!strict ---[=[ - Returns true if the collection is empty. - - ```lua - Freeze.isEmpty({}) - -- true - ``` - - @within Freeze - @function isEmpty - @return boolean -]=] - -return function(collection) - return next(collection) == nil -end diff --git a/samples/Luau/none.luau b/samples/Luau/none.luau deleted file mode 100644 index 118a75d8ce..0000000000 --- a/samples/Luau/none.luau +++ /dev/null @@ -1,21 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/None.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---[=[ - @prop None None - @within Freeze - - Since lua tables cannot distinguish between values not being present and a value of nil, - `Freeze.None` exists to represent values that should be interpreted as `nil`. - - This is useful when removing values with functions such as [`Freeze.Dictionary.merge`](../api/Dictionary#merge). -]=] - -local None = newproxy(true) - -getmetatable(None).__tostring = function() - return "Freeze.None" -end - -return None diff --git a/samples/Luau/pathToRegexp.luau b/samples/Luau/pathToRegexp.luau new file mode 100644 index 0000000000..37b528c051 --- /dev/null +++ b/samples/Luau/pathToRegexp.luau @@ -0,0 +1,854 @@ +--// Authored by @Roblox (https://github.com/Roblox) +--// Fetched from (https://github.com/Roblox/roact-navigation/blob/master/src/routers/pathToRegexp.lua) +--// Licensed under the MIT License (https://github.com/Roblox/roact-navigation/blob/master/LICENSE) + +-- upstream: https://github.com/pillarjs/path-to-regexp/blob/feddb3d3391d843f21ea9cde195f066149dba0be/src/index.ts +--[[ +The MIT License (MIT) + +Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +]] + +--!strict +--!nolint LocalShadow +local root = script.Parent.Parent +local Packages = root.Parent +local LuauPolyfill = require(Packages.LuauPolyfill) +local Array = LuauPolyfill.Array +local RegExp = require(Packages.RegExp) + +type Record = { [T]: U } + +local function TypeError(message) + return message +end + +local exports = {} + +-- Roblox deviation: predeclare function variables +local escapeString +local flags + +--[[ + * Tokenizer results. + ]] +type LexToken = { + type: string, + -- | "OPEN" + -- | "CLOSE" + -- | "PATTERN" + -- | "NAME" + -- | "CHAR" + -- | "ESCAPED_CHAR" + -- | "MODIFIER" + -- | "END", + index: number, + value: string, +} + +--[[ + * Tokenize input string. + ]] +local function lexer(str: string): { LexToken } + local tokens: { LexToken } = {} + local i = 1 + + -- Roblox deviation: the original JavaScript contains a lot of `i++`, which + -- does not translate really well to Luau. This function mimic the operation + -- while still being an expression (because it's a function call) + local function preIncrement_i(value) + i += 1 + return value + end + -- Roblox deviation: use this function to translate `str[n]` directly + local function getChar(n) + return string.sub(str, n, n) + end + + local strLength = string.len(str) + + while i <= strLength do + local char = string.sub(str, i, i) + + if char == "*" or char == "+" or char == "?" then + table.insert(tokens, { + type = "MODIFIER", + index = i, + value = getChar(preIncrement_i(i)), + }) + continue + end + + if char == "\\" then + table.insert(tokens, { + type = "ESCAPED_CHAR", + index = preIncrement_i(i), + value = getChar(preIncrement_i(i)), + }) + continue + end + + if char == "{" then + table.insert(tokens, { + type = "OPEN", + index = i, + value = getChar(preIncrement_i(i)), + }) + continue + end + + if char == "}" then + table.insert(tokens, { + type = "CLOSE", + index = i, + value = getChar(preIncrement_i(i)), + }) + continue + end + + if char == ":" then + local name = "" + local j = i + 1 + + -- Roblox deviation: the original JavaScript contains a lot of `j++`, which + -- does not translate really well to Luau. This function mimic the operation + -- while still being an expression (because it's a function call) + local function preIncrement_j(value) + j += 1 + return value + end + + while j <= strLength do + local code = string.byte(str, j) + + if + -- // `0-9` + (code >= 48 and code <= 57) + -- // `A-Z` + or (code >= 65 and code <= 90) + -- // `a-z` + or (code >= 97 and code <= 122) + -- // `_` + or code == 95 + then + name = name .. getChar(preIncrement_j(j)) + continue + end + + break + end + + if name == "" then + error(TypeError(("Missing parameter name at %d"):format(i))) + end + + table.insert(tokens, { + type = "NAME", + index = i, + value = name, + }) + i = j + continue + end + + if char == "(" then + local count = 1 + local pattern = "" + local j = i + 1 + + if getChar(j) == "?" then + error(TypeError(('Pattern cannot start with "?" at %d'):format(j))) + end + + -- Roblox deviation: the original JavaScript contains a lot of `j++`, which + -- does not translate really well to Luau. This function mimic the operation + -- while still being an expression (because it's a function call) + local function preIncrement_j(value) + j += 1 + return value + end + + while j <= strLength do + if getChar(j) == "\\" then + pattern = pattern .. (getChar(preIncrement_j(j)) .. getChar(preIncrement_j(j))) + end + + if getChar(j) == ")" then + count = count - 1 + if count == 0 then + j = j + 1 + break + end + elseif getChar(j) == "(" then + count = count + 1 + + if getChar(j + 1) ~= "?" then + error(TypeError(("Capturing groups are not allowed at %d"):format(j))) + end + end + + pattern = pattern .. getChar(preIncrement_j(j)) + end + + if count ~= 0 then + error(TypeError(("Unbalanced pattern at %d"):format(i))) + end + if pattern == "" then + error(TypeError(("Missing pattern at %d"):format(i))) + end + + table.insert(tokens, { + type = "PATTERN", + index = i, + value = pattern, + }) + i = j + continue + end + + table.insert(tokens, { + type = "CHAR", + index = i, + value = getChar(preIncrement_i(i)), + }) + end + + table.insert(tokens, { + type = "END", + index = i, + value = "", + }) + + return tokens +end + +export type ParseOptions = { + --[[ + * Set the default delimiter for repeat parameters. (default: `'/'`) + ]] + delimiter: string?, + --[[ + * List of characters to automatically consider prefixes when parsing. + ]] + prefixes: string?, +} + +--[[ + * Parse a string for the raw tokens. + ]] +function exports.parse(str: string, optionalOptions: ParseOptions?): { Token } + local options = optionalOptions or {} + local tokens = lexer(str) + + local prefixes = "./" + if options.prefixes ~= nil and options.prefixes ~= "" then + prefixes = options.prefixes + end + local defaultPattern = string.format("[^%s]+?", escapeString(options.delimiter or "/#?")) + local result: { Token } = {} + local key = 0 + local i = 1 + local path = "" + + -- Roblox deviation: the original JavaScript contains a lot of `i++`, which + -- does not translate really well to Luau. This function mimic the operation + -- while still being an expression (because it's a function call) + local function preIncrement_i(value) + i += 1 + return value + end + + -- Roblox deviation: the original JavaScript contains a lot of `key++`, which + -- does not translate really well to Luau. This function mimic the operation + -- while still being an expression (because it's a function call) + local function preIncrement_key(value) + key += 1 + return value + end + + local function tryConsume(type_): string? + if i <= #tokens and tokens[i].type == type_ then + local v = tokens[preIncrement_i(i)].value + return v + end + return nil + end + + local function mustConsume(type_): string + local value = tryConsume(type_) + if value ~= nil then + return value + end + local token = tokens[i] + if token == nil then + error(TypeError(("Expected token %s, got nil"):format(type_))) + end + local nextType = token.type + local index = token.index + error(TypeError(("Unexpected %s at %d, expected %s"):format(nextType, index, type_))) + end + + local function consumeText(): string + local result = "" + local value: string? = tryConsume("CHAR") or tryConsume("ESCAPED_CHAR") + while value and value ~= "" do + result ..= value + value = tryConsume("CHAR") or tryConsume("ESCAPED_CHAR") + end + return result + end + + while i <= #tokens do + local char = tryConsume("CHAR") + local name = tryConsume("NAME") + local pattern = tryConsume("PATTERN") + + if (name and name ~= "") or (pattern and pattern ~= "") then + local prefix = char or "" + + if string.find(prefixes, prefix) == nil then + path = path .. prefix + prefix = "" + end + + if path ~= nil and path ~= "" then + table.insert(result, path) + path = "" + end + + local resultName = name + if name == nil or name == "" then + resultName = preIncrement_key(key) + end + local resultPattern = pattern + if pattern == nil or pattern == "" then + resultPattern = defaultPattern + end + + table.insert(result, { + name = resultName, + prefix = prefix, + suffix = "", + pattern = resultPattern, + modifier = tryConsume("MODIFIER") or "", + }) + continue + end + + local value = char or tryConsume("ESCAPED_CHAR") + + if value and value ~= "" then + path ..= value + continue + end + + if path and path ~= "" then + table.insert(result, path) + path = "" + end + + local open = tryConsume("OPEN") + + if open and open ~= "" then + local prefix = consumeText() + local name = tryConsume("NAME") or "" + local pattern = tryConsume("PATTERN") or "" + local suffix = consumeText() + + mustConsume("CLOSE") + + if name == "" and pattern ~= "" then + name = preIncrement_key(key) + end + -- Roblox deviation: we need to check if name is not 0, because 0 is false in JavaScript, and + -- it could be number because it could be assigned to the key value + if (name ~= "" and name ~= 0) and (pattern == nil or pattern == "") then + pattern = defaultPattern + end + + table.insert(result, { + name = name, + pattern = pattern, + prefix = prefix, + suffix = suffix, + modifier = tryConsume("MODIFIER") or "", + }) + continue + end + + mustConsume("END") + end + + return result +end + +export type TokensToFunctionOptions = { + --[[ + * When `true` the regexp will be case sensitive. (default: `false`) + ]] + sensitive: boolean?, + --[[ + * Function for encoding input strings for output. + ]] + encode: nil | (string, Key) -> string, + --[[ + * When `false` the function can produce an invalid (unmatched) path. (default: `true`) + ]] + validate: boolean?, +} + +--[[ + * Compile a string to a template function for the path. + ]] +function exports.compile(str: string, options: (ParseOptions & TokensToFunctionOptions)?) + return exports.tokensToFunction(exports.parse(str, options), options) +end + +export type PathFunction

= (P?) -> string + +--[[ + * Expose a method for transforming tokens into the path function. + ]] +function exports.tokensToFunction(tokens: { Token }, optionalOptions: TokensToFunctionOptions?) + if optionalOptions == nil then + optionalOptions = {} + end + local options = optionalOptions :: TokensToFunctionOptions + local reFlags = flags(options) + local encode = options.encode or function(x: string): string + return x + end + local validate = options.validate + if validate == nil then + validate = true + end + + -- Compile all the tokens into regexps. + local matches = Array.map(tokens, function(token) + if type(token) == "table" then + return RegExp(("^(?:%s)$"):format(token.pattern), reFlags) + end + return nil + end) + + return function(data: Record?) + local path = "" + + for i, token in tokens do + if type(token) == "string" then + path ..= token + continue + end + + -- Roblox deviation: in JavaScript, indexing an object with a number will coerce the number + -- value into a string + local value = if data then data[tostring(token.name)] else nil + local optional = token.modifier == "?" or token.modifier == "*" + local repeat_ = token.modifier == "*" or token.modifier == "+" + + if Array.isArray(value) then + if not repeat_ then + error(TypeError(('Expected "%s" to not repeat, but got an array'):format(token.name))) + end + + if #value == 0 then + if optional then + continue + end + + error(TypeError(('Expected "%s" to not be empty'):format(token.name))) + end + + for _, element in value do + local segment = encode(element, token) + + if validate and not matches[i]:test(segment) then + error( + TypeError( + ('Expected all "%s" to match "%s", but got "%s"'):format( + token.name, + token.pattern, + segment + ) + ) + ) + end + + path ..= token.prefix .. segment .. token.suffix + end + + continue + end + + local valueType = type(value) + if valueType == "string" or valueType == "number" then + local segment = encode(tostring(value), token) + + if validate and not matches[i]:test(segment) then + error( + TypeError( + ('Expected "%s" to match "%s", but got "%s"'):format(token.name, token.pattern, segment) + ) + ) + end + + path ..= token.prefix .. segment .. token.suffix + continue + end + + if optional then + continue + end + + local typeOfMessage = if repeat_ then "an array" else "a string" + error(TypeError(('Expected "%s" to be %s'):format(tostring(token.name), typeOfMessage))) + end + + return path + end +end + +export type RegexpToFunctionOptions = { + --[[ + * Function for decoding strings for params. + ]] + decode: nil | (string, Key) -> string, +} + +--[[ + * A match result contains data about the path match. + ]] +export type MatchResult

= { + path: string, + index: number, + params: P, +} + +--[[ + * A match is either `false` (no match) or a match result. + ]] +-- export type Match

= false | MatchResult

+export type Match

= boolean | MatchResult

+ +--[[ + * The match function takes a string and returns whether it matched the path. + ]] +export type MatchFunction

= (string) -> Match

+ +--[[ + * Create path match function from `path-to-regexp` spec. + ]] +function exports.match(str, options) + local keys: { Key } = {} + local re = exports.pathToRegexp(str, keys, options) + return exports.regexpToFunction(re, keys, options) +end + +--[[ + * Create a path match function from `path-to-regexp` output. + ]] +function exports.regexpToFunction(re: any, keys: { Key }, options: RegexpToFunctionOptions) + if options == nil then + options = {} + end + local decode = options.decode or function(x: string) + return x + end + + return function(pathname: string) + local matches = re:exec(pathname) + if not matches then + return false + end + + local path = matches[1] + local index = matches.index or 0 + local params = {} + + -- Roblox deviation: start the iteration from 2 instead of 1 because the individual + -- matches start at 2 in our polyfill version of RegExp objects. + for i = 2, matches.n do + if matches[i] == nil then + continue + end + + -- Roblox comment: keep the `-1` because our matches array start from 1, + -- so the loop starts at index 2 (index 1 is the full matched string) + local key = keys[i - 1] + if key.modifier == "*" or key.modifier == "+" then + params[key.name] = Array.map(string.split(matches[i], key.prefix .. key.suffix), function(value) + return decode(value, key) + end) + else + params[key.name] = decode(matches[i], key) + end + end + + return { + path = path, + index = index, + params = params, + } + end +end + +--[[ + * Escape a regular expression string. + ]] +function escapeString(str: string) + return string.gsub(str, "[%.%+%*%?=%^!:${}%(%)%[%]|/\\]", function(match) + return "\\" .. match + end) +end + +--[[ + * Get the flags for a regexp from the options. + ]] +function flags(options: { sensitive: boolean? }?) + if options and options.sensitive then + return "" + else + return "i" + end +end + +--[[ + * Metadata about a key. + ]] +export type Key = { + name: string | number, + prefix: string, + suffix: string, + pattern: string, + modifier: string, +} + +--[[ + * A token is a string (nothing special) or key metadata (capture group). + ]] +export type Token = string | Key + +-- Roblox deviation: this functionality is not required so it has been omitted +--[[ + * Pull out keys from a regexp. + ]] +-- local function regexpToRegexp(path: string, keys: { Key }?): string +-- if not keys then +-- return path +-- end + +-- local groupsRegex = "%(" .. "(%?<(.*)>)?" .. "[^%?]" + +-- local index = 0 +-- local matchGenerator = path.source:gmatch(groupsRegex) +-- -- local execResult = groupsRegex.exec(path.source) +-- local execResult = matchGenerator() + +-- while execResult do +-- error('got match -> ' .. execResult .. " for path = " .. path) +-- local name = execResult[1] +-- if name then +-- name = index +-- index += 1 +-- end +-- table.insert(keys, { +-- -- // Use parenthesized substring match if available, index otherwise +-- name = name, +-- prefix = "", +-- suffix = "", +-- modifier = "", +-- pattern = "", +-- }) + +-- -- execResult = groupsRegex.exec(path.source) +-- execResult = matchGenerator() +-- end + +-- return path +-- end + +--[[ + * Transform an array into a regexp. + ]] +local function arrayToRegexp( + paths: { string }, + keys: { Key }?, + options: (TokensToRegexpOptions & ParseOptions)? +): string + local parts = Array.map(paths, function(path) + return exports.pathToRegexp(path, keys, options).source + end) + + return RegExp(("(?:%s)"):format(table.concat(parts, "|")), flags(options)) +end + +--[[ + * Create a path regexp from string input. + ]] +local function stringToRegexp(path, keys, options) + return exports.tokensToRegexp(exports.parse(path, options), keys, options) +end + +export type TokensToRegexpOptions = { + --[[ + * When `true` the regexp will be case sensitive. (default: `false`) + ]] + sensitive: boolean?, + --[[ + * When `true` the regexp won't allow an optional trailing delimiter to match. (default: `false`) + ]] + strict: boolean?, + --[[ + * When `true` the regexp will match to the end of the string. (default: `true`) + ]] + end_: boolean?, + --[[ + * When `true` the regexp will match from the beginning of the string. (default: `true`) + ]] + start: boolean?, + --[[ + * Sets the final character for non-ending optimistic matches. (default: `/`) + ]] + delimiter: string?, + --[[ + * List of characters that can also be "end" characters. + ]] + endsWith: string?, + --[[ + * Encode path tokens for use in the `RegExp`. + ]] + encode: nil | (string) -> string, +} + +--[[ + * Expose a function for taking tokens and returning a RegExp. + ]] +function exports.tokensToRegexp(tokens: { Token }, keys: { Key }?, optionalOptions: TokensToRegexpOptions?) + local options = {} + if optionalOptions ~= nil then + options = optionalOptions + end + local strict = options.strict + if strict == nil then + strict = false + end + local start = options.start + if start == nil then + start = true + end + local end_ = options.end_ + if end_ == nil then + end_ = true + end + local encode = options.encode or function(x: string) + return x + end + -- Roblox deviation: our Lua regex implementation does not support empty character class + local endsWith = if options.endsWith then ("[%s]|$"):format(escapeString(options.endsWith or "")) else "$" + local delimiter = ("[%s]"):format(escapeString(options.delimiter or "/#?")) + local route = if start then "^" else "" + + -- // Iterate over the tokens and create our regexp string. + for _, token in tokens do + if type(token) == "string" then + route ..= escapeString(encode(token)) + else + local prefix = escapeString(encode(token.prefix)) + local suffix = escapeString(encode(token.suffix)) + + if token.pattern and token.pattern ~= "" then + if keys then + table.insert(keys, token) + end + + if (prefix and prefix ~= "") or (suffix and suffix ~= "") then + if token.modifier == "+" or token.modifier == "*" then + local mod = if token.modifier == "*" then "?" else "" + route ..= ("(?:%s((?:%s)(?:%s%s(?:%s))*)%s)%s"):format( + prefix, + token.pattern, + suffix, + prefix, + token.pattern, + suffix, + mod + ) + else + route ..= ("(?:%s(%s)%s)%s"):format(prefix, token.pattern, suffix, token.modifier) + end + else + route ..= ("(%s)%s"):format(token.pattern, token.modifier) + end + else + route ..= ("(?:%s%s)%s"):format(prefix, suffix, token.modifier) + end + end + end + + if end_ then + if not strict then + route ..= ("%s?"):format(delimiter) + end + + if options.endsWith and options.endsWith ~= "" then + route ..= ("(?=%s)"):format(endsWith) + else + route ..= "$" + end + else + local endToken = tokens[#tokens] + local isEndDelimited = endToken == nil + if type(endToken) == "string" then + isEndDelimited = string.find(delimiter, endToken:sub(-1)) ~= nil + end + + if not strict then + route ..= string.format("(?:%s(?=%s))?", delimiter, endsWith) + end + if not isEndDelimited then + route ..= string.format("(?=%s|%s)", delimiter, endsWith) + end + end + + return RegExp(route, flags(options)) +end + +--[[ + * Supported `path-to-regexp` input types. + ]] +export type Path = string | { string } + +--[[ + * Normalize the given path string, returning a regular expression. + * + * An empty array can be passed in for the keys, which will hold the + * placeholder key descriptions. For example, using `/user/:id`, `keys` will + * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. + ]] +function exports.pathToRegexp(path: Path, keys: { Key }?, options: (TokensToRegexpOptions & ParseOptions)?) + -- if (path instanceof RegExp) return regexpToRegexp(path, keys); + if Array.isArray(path) then + return arrayToRegexp(path :: { string }, keys, options) + end + return stringToRegexp(path, keys, options) +end + +return exports \ No newline at end of file diff --git a/samples/Luau/replaceMacros.luau b/samples/Luau/replaceMacros.luau deleted file mode 100644 index 11daa0b368..0000000000 --- a/samples/Luau/replaceMacros.luau +++ /dev/null @@ -1,311 +0,0 @@ ---// Authored by @grilme99 (https://github.com/grilme99) ---// Fetched from (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/scripts/replaceMacros.luau) ---// Licensed under the MIT License (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/LICENSE-Brooke) - --- upstream: https://github.com/dead/typeflex/blob/422cb26/tools/repalce_macros.py - -local function YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - if node:getStyle().instanceName ~= paramName then - local style: YGStyle = node:getStyle() - style.instanceName = paramName - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) - if - node:getStyle().instanceName.value ~= YGFloatSanitize(paramName) - or node:getStyle().instanceName.unit ~= YGUnit.Percent - then - local style: YGStyle = node:getStyle() - style.instanceName.value = YGFloatSanitize(paramName) - style.instanceName.unit = if YGFloatIsUndefined(paramName) then YGUnit.Auto else YGUnit.Percent - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -local function YGNodeStyleSet##nameAuto(node: YGNode) - if node:getStyle().instanceName.unit ~= YGUnit.Auto then - local style: YGStyle = node:getStyle() - style.instanceName.value = 0 - style.instanceName.unit = YGUnit.Auto - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - return node:getStyle().instanceName -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, edge: YGEdge, paramName: number) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName[edge].unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName[edge] = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, edge: YGEdge, paramName: number) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent - ) - - if - (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName[edge].unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName[edge] = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -local function YGNodeStyleGet##name(node: YGNode, edge: YGEdge): type - local value: YGValue = node:getStyle().instanceName[edge] - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeStyleSet##nameAuto(node: YGNode, edge: YGEdge) - if node:getStyle().instanceName[edge].unit ~= YGUnit.Auto then - local style: YGStyle = node:getStyle() - style.instanceName[edge].value = 0 - style.instanceName[edge].unit = YGUnit.Auto - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL("number", name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - local value: YGValue = node:getStyle().instanceName - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL("number", name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - local value: YGValue = node:getStyle().instanceName - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_LAYOUT_PROPERTY_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeLayoutGet##name(node: YGNode): type - return node:getLayout().instanceName -end -exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local function YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeLayoutGet##name(node: YGNode, edge: YGEdge): type - -- YGAssertWithNode(node, edge <= YGEdge.End, "Cannot get layout properties of multi-edge shorthands") - - if edge == YGEdge.Start then - if node:getLayout().direction == YGDirection.RTL then - return node:getLayout().instanceName[YGEdge.Right] - else - return node:getLayout().instanceName[YGEdge.Left] - end - end - - if edge == YGEdge.End then - if node:getLayout().direction == YGDirection.RTL then - return node:getLayout().instanceName[YGEdge.Left] - else - return node:getLayout().instanceName[YGEdge.Right] - end - end - - return node:getLayout().instanceName[edge] -end -exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local cod = "" - -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDirection", "Direction", "direction", "direction") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGFlexDirection", "FlexDirection", "flexDirection", "flexDirection") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGJustify", "JustifyContent", "justifyContent", "justifyContent") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignContent", "alignContent", "alignContent") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignItems", "alignItems", "alignItems") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignSelf", "alignSelf", "alignSelf") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGPositionType", "PositionType", "positionType", "positionType") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGWrap", "FlexWrap", "flexWrap", "flexWrap") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGOverflow", "Overflow", "overflow", "overflow") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDisplay", "Display", "display", "display") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Position", "position", "position") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Margin", "margin", "margin") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Padding", "padding", "padding") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Margin", "margin") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Width", "width", "dimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Height", "height", "dimensions[YGDimension.Height]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinWidth", "minWidth", "minDimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinHeight", "minHeight", "minDimensions[YGDimension.Height]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxWidth", "maxWidth", "maxDimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxHeight", "maxHeight", "maxDimensions[YGDimension.Height]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Left", "position[YGEdge.Left]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Top", "position[YGEdge.Top]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Right", "position[YGEdge.Right]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Bottom", "position[YGEdge.Bottom]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Width", "dimensions[YGDimension.Width]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Height", "dimensions[YGDimension.Height]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("YGDirection", "Direction", "direction") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("boolean", "HadOverflow", "hadOverflow") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Margin", "margin") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Border", "border") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Padding", "padding") - -print(cod) \ No newline at end of file diff --git a/samples/Luau/roblox.luau b/samples/Luau/roblox.luau new file mode 100644 index 0000000000..2b1ea9beb8 --- /dev/null +++ b/samples/Luau/roblox.luau @@ -0,0 +1,512 @@ +--// Authored by @Filiptibell (https://github.com/filiptibell) +--// Fetched from (https://github.com/lune-org/lune/blob/main/types/roblox.luau) +--// Licensed under the Mozilla Public License v2.0 (https://github.com/lune-org/lune/blob/main/LICENSE.txt) + +--!strict +export type DatabaseScriptability = "None" | "Custom" | "Read" | "ReadWrite" | "Write" + +export type DatabasePropertyTag = + "Deprecated" + | "Hidden" + | "NotBrowsable" + | "NotReplicated" + | "NotScriptable" + | "ReadOnly" + | "WriteOnly" + +export type DatabaseClassTag = + "Deprecated" + | "NotBrowsable" + | "NotCreatable" + | "NotReplicated" + | "PlayerReplicated" + | "Service" + | "Settings" + | "UserSettings" + +export type DatabaseProperty = { + --[=[ + The name of the property. + ]=] + Name: string, + --[=[ + The datatype of the property. + + For normal datatypes this will be a string such as `string`, `Color3`, ... + + For enums this will be a string formatted as `Enum.EnumName`. + ]=] + Datatype: string, + --[=[ + The scriptability of this property, meaning if it can be written / read at runtime. + + All properties are writable and readable in Lune even if scriptability is not. + ]=] + Scriptability: DatabaseScriptability, + --[=[ + Tags describing the property. + + These include information such as if the property can be replicated to players + at runtime, if the property should be hidden in Roblox Studio, and more. + ]=] + Tags: { DatabasePropertyTag }, +} + +export type DatabaseClass = { + --[=[ + The name of the class. + ]=] + Name: string, + --[=[ + The superclass (parent class) of this class. + + May be nil if no parent class exists. + ]=] + Superclass: string?, + --[=[ + Known properties for this class. + ]=] + Properties: { [string]: DatabaseProperty }, + --[=[ + Default values for properties of this class. + + Note that these default properties use Lune's built-in datatype + userdatas, and that if there is a new datatype that Lune does + not yet know about, it may be missing from this table. + ]=] + DefaultProperties: { [string]: any }, + --[=[ + Tags describing the class. + + These include information such as if the class can be replicated + to players at runtime, and top-level class categories. + ]=] + Tags: { DatabaseClassTag }, +} + +export type DatabaseEnum = { + --[=[ + The name of this enum, for example `PartType` or `UserInputState`. + ]=] + Name: string, + --[=[ + Members of this enum. + + Note that this is a direct map of name -> enum values, + and does not actually use the EnumItem datatype itself. + ]=] + Items: { [string]: number }, +} + +export type Database = { + --[=[ + The current version of the reflection database. + + This will follow the format `x.y.z.w`, which most commonly looks something like `0.567.0.123456789` + ]=] + Version: string, + --[=[ + Retrieves a list of all currently known class names. + ]=] + GetClassNames: (self: Database) -> { string }, + --[=[ + Retrieves a list of all currently known enum names. + ]=] + GetEnumNames: (self: Database) -> { string }, + --[=[ + Gets a class with the exact given name, if one exists. + ]=] + GetClass: (self: Database, name: string) -> DatabaseClass?, + --[=[ + Gets an enum with the exact given name, if one exists. + ]=] + GetEnum: (self: Database, name: string) -> DatabaseEnum?, + --[=[ + Finds a class with the given name. + + This will use case-insensitive matching and ignore leading and trailing whitespace. + ]=] + FindClass: (self: Database, name: string) -> DatabaseClass?, + --[=[ + Finds an enum with the given name. + + This will use case-insensitive matching and ignore leading and trailing whitespace. + ]=] + FindEnum: (self: Database, name: string) -> DatabaseEnum?, +} + +type InstanceProperties = { + Parent: Instance?, + ClassName: string, + Name: string, + -- FIXME: This breaks intellisense, but we need some way to access + -- instance properties without casting the entire instance to any... + -- [string]: any, +} + +type InstanceMetatable = { + Clone: (self: Instance) -> Instance, + Destroy: (self: Instance) -> (), + ClearAllChildren: (self: Instance) -> (), + + GetChildren: (self: Instance) -> { Instance }, + GetDebugId: (self: Instance) -> string, + GetDescendants: (self: Instance) -> { Instance }, + GetFullName: (self: Instance) -> string, + + FindFirstAncestor: (self: Instance, name: string) -> Instance?, + FindFirstAncestorOfClass: (self: Instance, className: string) -> Instance?, + FindFirstAncestorWhichIsA: (self: Instance, className: string) -> Instance?, + FindFirstChild: (self: Instance, name: string, recursive: boolean?) -> Instance?, + FindFirstChildOfClass: (self: Instance, className: string, recursive: boolean?) -> Instance?, + FindFirstChildWhichIsA: (self: Instance, className: string, recursive: boolean?) -> Instance?, + + IsA: (self: Instance, className: string) -> boolean, + IsAncestorOf: (self: Instance, descendant: Instance) -> boolean, + IsDescendantOf: (self: Instance, ancestor: Instance) -> boolean, + + GetAttribute: (self: Instance, name: string) -> any, + GetAttributes: (self: Instance) -> { [string]: any }, + SetAttribute: (self: Instance, name: string, value: any) -> (), + + GetTags: (self: Instance) -> { string }, + HasTag: (self: Instance, name: string) -> boolean, + AddTag: (self: Instance, name: string) -> (), + RemoveTag: (self: Instance, name: string) -> (), +} + +export type Instance = typeof(setmetatable( + (nil :: any) :: InstanceProperties, + (nil :: any) :: { __index: InstanceMetatable } +)) + +export type DataModelProperties = {} +export type DataModelMetatable = { + GetService: (self: DataModel, name: string) -> Instance, + FindService: (self: DataModel, name: string) -> Instance?, +} + +export type DataModel = + Instance + & typeof(setmetatable( + (nil :: any) :: DataModelProperties, + (nil :: any) :: { __index: DataModelMetatable } + )) + +--[=[ + @class Roblox + + Built-in library for manipulating Roblox place & model files + + ### Example usage + + ```lua + local fs = require("@lune/fs") + local roblox = require("@lune/roblox") + + -- Reading a place file + local placeFile = fs.readFile("myPlaceFile.rbxl") + local game = roblox.deserializePlace(placeFile) + + -- Manipulating and reading instances - just like in Roblox! + local workspace = game:GetService("Workspace") + for _, child in workspace:GetChildren() do + print("Found child " .. child.Name .. " of class " .. child.ClassName) + end + + -- Writing a place file + local newPlaceFile = roblox.serializePlace(game) + fs.writeFile("myPlaceFile.rbxl", newPlaceFile) + ``` +]=] +local roblox = {} + +--[=[ + @within Roblox + @tag must_use + + Deserializes a place into a DataModel instance. + + This function accepts a string of contents, *not* a file path. + If reading a place file from a file path is desired, `fs.readFile` + can be used and the resulting string may be passed to this function. + + ### Example usage + + ```lua + local fs = require("@lune/fs") + local roblox = require("@lune/roblox") + + local placeFile = fs.readFile("filePath.rbxl") + local game = roblox.deserializePlace(placeFile) + ``` + + @param contents The contents of the place to read +]=] +function roblox.deserializePlace(contents: string): DataModel + return nil :: any +end + +--[=[ + @within Roblox + @tag must_use + + Deserializes a model into an array of instances. + + This function accepts a string of contents, *not* a file path. + If reading a model file from a file path is desired, `fs.readFile` + can be used and the resulting string may be passed to this function. + + ### Example usage + + ```lua + local fs = require("@lune/fs") + local roblox = require("@lune/roblox") + + local modelFile = fs.readFile("filePath.rbxm") + local instances = roblox.deserializeModel(modelFile) + ``` + + @param contents The contents of the model to read +]=] +function roblox.deserializeModel(contents: string): { Instance } + return nil :: any +end + +--[=[ + @within Roblox + @tag must_use + + Serializes a place from a DataModel instance. + + This string can then be written to a file, or sent over the network. + + ### Example usage + + ```lua + local fs = require("@lune/fs") + local roblox = require("@lune/roblox") + + local placeFile = roblox.serializePlace(game) + fs.writeFile("filePath.rbxl", placeFile) + ``` + + @param dataModel The DataModel for the place to serialize + @param xml If the place should be serialized as xml or not. Defaults to `false`, meaning the place gets serialized using the binary format and not xml. +]=] +function roblox.serializePlace(dataModel: DataModel, xml: boolean?): string + return nil :: any +end + +--[=[ + @within Roblox + @tag must_use + + Serializes one or more instances as a model. + + This string can then be written to a file, or sent over the network. + + ### Example usage + + ```lua + local fs = require("@lune/fs") + local roblox = require("@lune/roblox") + + local modelFile = roblox.serializeModel({ instance1, instance2, ... }) + fs.writeFile("filePath.rbxm", modelFile) + ``` + + @param instances The array of instances to serialize + @param xml If the model should be serialized as xml or not. Defaults to `false`, meaning the model gets serialized using the binary format and not xml. +]=] +function roblox.serializeModel(instances: { Instance }, xml: boolean?): string + return nil :: any +end + +--[=[ + @within Roblox + @tag must_use + + Gets the current auth cookie, for usage with Roblox web APIs. + + Note that this auth cookie is formatted for use as a "Cookie" header, + and that it contains restrictions so that it may only be used for + official Roblox endpoints. To get the raw cookie value without any + additional formatting, you can pass `true` as the first and only parameter. + + ### Example usage + + ```lua + local roblox = require("@lune/roblox") + local net = require("@lune/net") + + local cookie = roblox.getAuthCookie() + assert(cookie ~= nil, "Failed to get roblox auth cookie") + + local myPrivatePlaceId = 1234567890 + + local response = net.request({ + url = "https://assetdelivery.roblox.com/v2/assetId/" .. tostring(myPrivatePlaceId), + headers = { + Cookie = cookie, + }, + }) + + local responseTable = net.jsonDecode(response.body) + local responseLocation = responseTable.locations[1].location + print("Download link to place: " .. responseLocation) + ``` + + @param raw If the cookie should be returned as a pure value or not. Defaults to false +]=] +function roblox.getAuthCookie(raw: boolean?): string? + return nil :: any +end + +--[=[ + @within Roblox + @tag must_use + + Gets the bundled reflection database. + + This database contains information about Roblox enums, classes, and their properties. + + ### Example usage + + ```lua + local roblox = require("@lune/roblox") + + local db = roblox.getReflectionDatabase() + + print("There are", #db:GetClassNames(), "classes in the reflection database") + + print("All base instance properties:") + + local class = db:GetClass("Instance") + for name, prop in class.Properties do + print(string.format( + "- %s with datatype %s and default value %s", + prop.Name, + prop.Datatype, + tostring(class.DefaultProperties[prop.Name]) + )) + end + ``` +]=] +function roblox.getReflectionDatabase(): Database + return nil :: any +end + +--[=[ + @within Roblox + + Implements a property for all instances of the given `className`. + + This takes into account class hierarchies, so implementing a property + for the `BasePart` class will also implement it for `Part` and others, + unless a more specific implementation is added to the `Part` class directly. + + ### Behavior + + The given `getter` callback will be called each time the property is + indexed, with the instance as its one and only argument. The `setter` + callback, if given, will be called each time the property should be set, + with the instance as the first argument and the property value as second. + + ### Example usage + + ```lua + local roblox = require("@lune/roblox") + + local part = roblox.Instance.new("Part") + + local propertyValues = {} + roblox.implementProperty( + "BasePart", + "CoolProp", + function(instance) + if propertyValues[instance] == nil then + propertyValues[instance] = 0 + end + propertyValues[instance] += 1 + return propertyValues[instance] + end, + function(instance, value) + propertyValues[instance] = value + end + ) + + print(part.CoolProp) --> 1 + print(part.CoolProp) --> 2 + print(part.CoolProp) --> 3 + + part.CoolProp = 10 + + print(part.CoolProp) --> 11 + print(part.CoolProp) --> 12 + print(part.CoolProp) --> 13 + ``` + + @param className The class to implement the property for. + @param propertyName The name of the property to implement. + @param getter The function which will be called to get the property value when indexed. + @param setter The function which will be called to set the property value when indexed. Defaults to a function that will error with a message saying the property is read-only. +]=] +function roblox.implementProperty( + className: string, + propertyName: string, + getter: (instance: Instance) -> T, + setter: ((instance: Instance, value: T) -> ())? +) + return nil :: any +end + +--[=[ + @within Roblox + + Implements a method for all instances of the given `className`. + + This takes into account class hierarchies, so implementing a method + for the `BasePart` class will also implement it for `Part` and others, + unless a more specific implementation is added to the `Part` class directly. + + ### Behavior + + The given `callback` will be called every time the method is called, + and will receive the instance it was called on as its first argument. + The remaining arguments will be what the caller passed to the method, and + all values returned from the callback will then be returned to the caller. + + ### Example usage + + ```lua + local roblox = require("@lune/roblox") + + local part = roblox.Instance.new("Part") + + roblox.implementMethod("BasePart", "TestMethod", function(instance, ...) + print("Called TestMethod on instance", instance, "with", ...) + end) + + part:TestMethod("Hello", "world!") + --> Called TestMethod on instance Part with Hello, world! + ``` + + @param className The class to implement the method for. + @param methodName The name of the method to implement. + @param callback The function which will be called when the method is called. +]=] +function roblox.implementMethod( + className: string, + methodName: string, + callback: (instance: Instance, ...any) -> ...any +) + return nil :: any +end + +-- TODO: Make typedefs for all of the datatypes as well... +roblox.Instance = (nil :: any) :: { + new: ((className: "DataModel") -> DataModel) & ((className: string) -> Instance), +} + +return roblox diff --git a/samples/Luau/timeStepper.luau b/samples/Luau/timeStepper.luau deleted file mode 100644 index 038a614451..0000000000 --- a/samples/Luau/timeStepper.luau +++ /dev/null @@ -1,36 +0,0 @@ ---// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) ---// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/TimeStepper.luau) ---// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) - ---!strict - ---[[ - -A time stepper is responsible for stepping systems in a deterministic order at a set interval. - -]] - -local TimeStepper = {} -TimeStepper.__index = TimeStepper - -function TimeStepper.new(interval: number, systems) - local self = setmetatable({ - _systems = systems, - _interval = interval, - }, TimeStepper) - - return self -end - -function TimeStepper:start() - coroutine.resume(coroutine.create(function() - while true do - local timeStep, _ = wait(self._interval) - for _, system in ipairs(self._systems) do - system:step(timeStep) - end - end - end)) -end - -return TimeStepper diff --git a/samples/Luau/toSet.luau b/samples/Luau/toSet.luau deleted file mode 100644 index 7d18f99ed2..0000000000 --- a/samples/Luau/toSet.luau +++ /dev/null @@ -1,22 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/List/toSet.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---!strict - ---[=[ - Returns a Set from the given List. - - @within List - @function toSet - @ignore -]=] -return function(list: { Value }): { [Value]: boolean } - local set = {} - - for _, v in list do - set[v] = true - end - - return set -end From f80ee088e38d45bbc195122a770e4fa39076da52 Mon Sep 17 00:00:00 2001 From: robloxiandemo <76854027+robloxiandemo@users.noreply.github.com> Date: Sat, 6 Apr 2024 17:29:50 -0400 Subject: [PATCH 26/29] Patch: Update old samples and their sources. --- samples/Luau/EnumList.luau | 109 +++++++ samples/Luau/Maid.luau | 213 +++++++++++++ samples/Luau/Option.luau | 497 ++++++++++++++++++++++++++++++ samples/Luau/Signal.luau | 437 ++++++++++++++++++++++++++ samples/Luau/createProcessor.luau | 22 -- samples/Luau/createSignal.luau | 37 --- samples/Luau/equals.luau | 17 - samples/Luau/getEnv.luau | 39 +++ samples/Luau/isEmpty.luau | 21 -- samples/Luau/none.luau | 21 -- samples/Luau/replaceMacros.luau | 311 ------------------- samples/Luau/timeStepper.luau | 36 --- samples/Luau/toSet.luau | 22 -- 13 files changed, 1295 insertions(+), 487 deletions(-) create mode 100644 samples/Luau/EnumList.luau create mode 100644 samples/Luau/Maid.luau create mode 100644 samples/Luau/Option.luau create mode 100644 samples/Luau/Signal.luau delete mode 100644 samples/Luau/createProcessor.luau delete mode 100644 samples/Luau/createSignal.luau delete mode 100644 samples/Luau/equals.luau create mode 100644 samples/Luau/getEnv.luau delete mode 100644 samples/Luau/isEmpty.luau delete mode 100644 samples/Luau/none.luau delete mode 100644 samples/Luau/replaceMacros.luau delete mode 100644 samples/Luau/timeStepper.luau delete mode 100644 samples/Luau/toSet.luau diff --git a/samples/Luau/EnumList.luau b/samples/Luau/EnumList.luau new file mode 100644 index 0000000000..6b3403a9ed --- /dev/null +++ b/samples/Luau/EnumList.luau @@ -0,0 +1,109 @@ +--// Authored by @Sleitnick (https://github.com/sleitnick) +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/enum-list/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +--!optimize 2 +--!strict +--!native + +-- EnumList +-- Stephen Leitnick +-- January 08, 2021 + +type EnumNames = { string } + +--[=[ + @interface EnumItem + .Name string + .Value number + .EnumType EnumList + @within EnumList +]=] +export type EnumItem = { + Name: string, + Value: number, + EnumType: any, +} + +local LIST_KEY = newproxy() +local NAME_KEY = newproxy() + +local function CreateEnumItem(name: string, value: number, enum: any): EnumItem + local enumItem = { + Name = name, + Value = value, + EnumType = enum, + } + table.freeze(enumItem) + return enumItem +end + +--[=[ + @class EnumList + Defines a new Enum. +]=] +local EnumList = {} +EnumList.__index = EnumList + +--[=[ + @param name string + @param enums {string} + @return EnumList + Constructs a new EnumList. + + ```lua + local directions = EnumList.new("Directions", { + "Up", + "Down", + "Left", + "Right", + }) + + local direction = directions.Up + ``` +]=] +function EnumList.new(name: string, enums: EnumNames) + assert(type(name) == "string", "Name string required") + assert(type(enums) == "table", "Enums table required") + local self = {} + self[LIST_KEY] = {} + self[NAME_KEY] = name + for i, enumName in ipairs(enums) do + assert(type(enumName) == "string", "Enum name must be a string") + local enumItem = CreateEnumItem(enumName, i, self) + self[enumName] = enumItem + table.insert(self[LIST_KEY], enumItem) + end + return table.freeze(setmetatable(self, EnumList)) +end + +--[=[ + @param obj any + @return boolean + Returns `true` if `obj` belongs to the EnumList. +]=] +function EnumList:BelongsTo(obj: any): boolean + return type(obj) == "table" and obj.EnumType == self +end + +--[=[ + Returns an array of all enum items. + @return {EnumItem} + @since v2.0.0 +]=] +function EnumList:GetEnumItems() + return self[LIST_KEY] +end + +--[=[ + Get the name of the enum. + @return string + @since v2.0.0 +]=] +function EnumList:GetName() + return self[NAME_KEY] +end + +export type EnumList = typeof(EnumList.new(...)) + +return EnumList diff --git a/samples/Luau/Maid.luau b/samples/Luau/Maid.luau new file mode 100644 index 0000000000..0a6b91cd75 --- /dev/null +++ b/samples/Luau/Maid.luau @@ -0,0 +1,213 @@ +--// Authored by @Dig1t (https://github.com/dig1t) +--// Fetched from (https://github.com/dig1t/dlib/blob/main/src/Maid.luau) +--// Licensed under the MIT License (https://github.com/dig1t/dlib/blob/main/LICENSE) + +--!optimize 2 +--!strict +--!native + +local Util = require(script.Parent.Util) + +type MaidClass = { + __index: MaidClass, + new: () -> MaidType, + task: (self: MaidType, task: any, cleaner: () -> any) -> string, + removeTask: (self: MaidType, taskToRemove: MaidTask) -> nil, + clean: (self: MaidType) -> nil, + destroy: (any) -> nil, + + MaidType: MaidType +} + +type MaidInstance = { + _tasks: { [string]: MaidTask }, + [any]: any +} + +export type MaidType = typeof(setmetatable( + {} :: MaidInstance, + {} :: MaidClass +)) + +type MaidTask = { + Connected: boolean?, + Disconnect: () -> nil?, + Destroy: (any) -> nil?, + destroy: (any) -> nil?, + destructor: (task: any) -> nil?, + [any]: any +}? | () -> nil? | Instance; + +--[=[ + Task management class for cleaning up things for garbage collection. + + @class Maid +]=] +local Maid: MaidClass = {} :: MaidClass +Maid.__index = Maid + +--[=[ + Creates a new Maid instance. + + ```lua + local maid = Maid.new() + ``` + + @return Maid +]=] +function Maid.new(): MaidType + local self = setmetatable({}, Maid) + + self._tasks = {} + + return self +end + +--[=[ + Adds a task to the Maid instance. + + ```lua + local maid = Maid.new() + + maid:task(function() + print("Hello world!") + end) + ``` + + Multiple types of tasks can be added to the Maid instance. + + ```lua + local maid = Maid.new() + + -- Functions + maid:task(function() + print("Hello world!") + end) + + -- RBXScriptConnections + maid:task(workspace.ChildAdded:Connect(function() + print("Hello world!") + end)) + + -- Instances with "Destroy" methods + maid:task(Instance.new("Part")) + + -- Packages with "Destroy", "destroy", or "destructor" methods + local instance = Class.new({ + PackageVariable = "Hello world!", + Destroy = function() + -- destroy this package instance + end + }) + + maid:task(instance) + ``` + + @param _task any -- The task to add. + @return string -- The task id. +]=] +function Maid:task(_task: MaidTask): string + local taskId = Util.randomString(14) + + self._tasks[taskId] = _task + + return taskId +end + +--[=[ + Removes a task from the Maid instance. + + ```lua + local maid = Maid.new() + + local taskId = maid:task(function() + print("Hello world!") + end) + + maid:removeTask(taskId) + ``` + + @param taskToRemove any -- The task item to remove. + @return nil +]=] +function Maid:removeTask(taskToRemove: string | MaidTask): nil + -- Remove by task id + if typeof(taskToRemove) == "string" then + self._tasks[taskToRemove] = nil + + return + end + + -- Remove by task + for taskId, _task: MaidTask in pairs(self._tasks) do + if _task == taskToRemove then + self._tasks[taskId] = nil + end + end + + return +end + +--[=[ + Cleans up all tasks in the Maid instance. + + ```lua + local maid: typeof(Maid.MaidType) = Maid.new() + + maid:task(function() + print("Hello world!") + end) + + maid:clean() -- Hello world! + ``` + + @return nil +]=] +function Maid:clean(): nil + for taskId, _task: MaidTask in pairs(self._tasks) do + if typeof(_task) == "function" then + _task() -- Run cleaning _task + elseif typeof(_task) == "RBXScriptConnection" and _task.Connected then + _task:Disconnect() + elseif typeof(_task) == "Instance" or (_task and _task.Destroy) then + _task:Destroy() + elseif _task and _task.destroy then + _task:destroy() + elseif typeof(_task) == "table" then + if _task.destructor then + _task.destructor(_task.task) + end + end + + self._tasks[taskId] = nil + end + + return +end + +--[=[ + Destroys the Maid instance. + + ```lua + local maid = Maid.new() + + maid:task(function() + print("Hello world!") + end) + + maid:destroy() + + maid:clean() -- method no longer exists + ``` + + @return nil +]=] +function Maid:destroy(): nil + for key: any, _ in pairs(self) do + self[key] = nil + end + + return nil +end + +return Maid diff --git a/samples/Luau/Option.luau b/samples/Luau/Option.luau new file mode 100644 index 0000000000..726b443eac --- /dev/null +++ b/samples/Luau/Option.luau @@ -0,0 +1,497 @@ +--// Authored by @Sleitnick (https://github.com/sleitnick) +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/option/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +--!optimize 2 +--!strict +--!native + +-- Option +-- Stephen Leitnick +-- August 28, 2020 + +--[[ + + MatchTable { + Some: (value: any) -> any + None: () -> any + } + + CONSTRUCTORS: + + Option.Some(anyNonNilValue): Option + Option.Wrap(anyValue): Option + + + STATIC FIELDS: + + Option.None: Option + + + STATIC METHODS: + + Option.Is(obj): boolean + + + METHODS: + + opt:Match(): (matches: MatchTable) -> any + opt:IsSome(): boolean + opt:IsNone(): boolean + opt:Unwrap(): any + opt:Expect(errMsg: string): any + opt:ExpectNone(errMsg: string): void + opt:UnwrapOr(default: any): any + opt:UnwrapOrElse(default: () -> any): any + opt:And(opt2: Option): Option + opt:AndThen(predicate: (unwrapped: any) -> Option): Option + opt:Or(opt2: Option): Option + opt:OrElse(orElseFunc: () -> Option): Option + opt:XOr(opt2: Option): Option + opt:Contains(value: any): boolean + + -------------------------------------------------------------------- + + Options are useful for handling nil-value cases. Any time that an + operation might return nil, it is useful to instead return an + Option, which will indicate that the value might be nil, and should + be explicitly checked before using the value. This will help + prevent common bugs caused by nil values that can fail silently. + + + Example: + + local result1 = Option.Some(32) + local result2 = Option.Some(nil) + local result3 = Option.Some("Hi") + local result4 = Option.Some(nil) + local result5 = Option.None + + -- Use 'Match' to match if the value is Some or None: + result1:Match { + Some = function(value) print(value) end; + None = function() print("No value") end; + } + + -- Raw check: + if result2:IsSome() then + local value = result2:Unwrap() -- Explicitly call Unwrap + print("Value of result2:", value) + end + + if result3:IsNone() then + print("No result for result3") + end + + -- Bad, will throw error bc result4 is none: + local value = result4:Unwrap() + +--]] + +export type MatchTable = { + Some: (value: T) -> any, + None: () -> any, +} + +export type MatchFn = (matches: MatchTable) -> any + +export type DefaultFn = () -> T + +export type AndThenFn = (value: T) -> Option + +export type OrElseFn = () -> Option + +export type Option = typeof(setmetatable( + {} :: { + Match: (self: Option) -> MatchFn, + IsSome: (self: Option) -> boolean, + IsNone: (self: Option) -> boolean, + Contains: (self: Option, value: T) -> boolean, + Unwrap: (self: Option) -> T, + Expect: (self: Option, errMsg: string) -> T, + ExpectNone: (self: Option, errMsg: string) -> nil, + UnwrapOr: (self: Option, default: T) -> T, + UnwrapOrElse: (self: Option, defaultFn: DefaultFn) -> T, + And: (self: Option, opt2: Option) -> Option, + AndThen: (self: Option, predicate: AndThenFn) -> Option, + Or: (self: Option, opt2: Option) -> Option, + OrElse: (self: Option, orElseFunc: OrElseFn) -> Option, + XOr: (self: Option, opt2: Option) -> Option, + }, + {} :: { + __index: Option, + } +)) + +local CLASSNAME = "Option" + +--[=[ + @class Option + + Represents an optional value in Lua. This is useful to avoid `nil` bugs, which can + go silently undetected within code and cause hidden or hard-to-find bugs. +]=] +local Option = {} +Option.__index = Option + +function Option._new(value) + local self = setmetatable({ + ClassName = CLASSNAME, + _v = value, + _s = (value ~= nil), + }, Option) + return self +end + +--[=[ + @param value T + @return Option + + Creates an Option instance with the given value. Throws an error + if the given value is `nil`. +]=] +function Option.Some(value) + assert(value ~= nil, "Option.Some() value cannot be nil") + return Option._new(value) +end + +--[=[ + @param value T + @return Option | Option + + Safely wraps the given value as an option. If the + value is `nil`, returns `Option.None`, otherwise + returns `Option.Some(value)`. +]=] +function Option.Wrap(value) + if value == nil then + return Option.None + else + return Option.Some(value) + end +end + +--[=[ + @param obj any + @return boolean + Returns `true` if `obj` is an Option. +]=] +function Option.Is(obj) + return type(obj) == "table" and getmetatable(obj) == Option +end + +--[=[ + @param obj any + Throws an error if `obj` is not an Option. +]=] +function Option.Assert(obj) + assert(Option.Is(obj), "Result was not of type Option") +end + +--[=[ + @param data table + @return Option + Deserializes the data into an Option. This data should have come from + the `option:Serialize()` method. +]=] +function Option.Deserialize(data) -- type data = {ClassName: string, Value: any} + assert(type(data) == "table" and data.ClassName == CLASSNAME, "Invalid data for deserializing Option") + return data.Value == nil and Option.None or Option.Some(data.Value) +end + +--[=[ + @return table + Returns a serialized version of the option. +]=] +function Option:Serialize() + return { + ClassName = self.ClassName, + Value = self._v, + } +end + +--[=[ + @param matches {Some: (value: any) -> any, None: () -> any} + @return any + + Matches against the option. + + ```lua + local opt = Option.Some(32) + opt:Match { + Some = function(num) print("Number", num) end, + None = function() print("No value") end, + } + ``` +]=] +function Option:Match(matches) + local onSome = matches.Some + local onNone = matches.None + assert(type(onSome) == "function", "Missing 'Some' match") + assert(type(onNone) == "function", "Missing 'None' match") + if self:IsSome() then + return onSome(self:Unwrap()) + else + return onNone() + end +end + +--[=[ + @return boolean + Returns `true` if the option has a value. +]=] +function Option:IsSome() + return self._s +end + +--[=[ + @return boolean + Returns `true` if the option is None. +]=] +function Option:IsNone() + return not self._s +end + +--[=[ + @param msg string + @return value: any + Unwraps the value in the option, otherwise throws an error with `msg` as the error message. + ```lua + local opt = Option.Some(10) + print(opt:Expect("No number")) -> 10 + print(Option.None:Expect("No number")) -- Throws an error "No number" + ``` +]=] +function Option:Expect(msg) + assert(self:IsSome(), msg) + return self._v +end + +--[=[ + @param msg string + Throws an error with `msg` as the error message if the value is _not_ None. +]=] +function Option:ExpectNone(msg) + assert(self:IsNone(), msg) +end + +--[=[ + @return value: any + Returns the value in the option, or throws an error if the option is None. +]=] +function Option:Unwrap() + return self:Expect("Cannot unwrap option of None type") +end + +--[=[ + @param default any + @return value: any + If the option holds a value, returns the value. Otherwise, returns `default`. +]=] +function Option:UnwrapOr(default) + if self:IsSome() then + return self:Unwrap() + else + return default + end +end + +--[=[ + @param defaultFn () -> any + @return value: any + If the option holds a value, returns the value. Otherwise, returns the + result of the `defaultFn` function. +]=] +function Option:UnwrapOrElse(defaultFn) + if self:IsSome() then + return self:Unwrap() + else + return defaultFn() + end +end + +--[=[ + @param optionB Option + @return Option + Returns `optionB` if the calling option has a value, + otherwise returns None. + + ```lua + local optionA = Option.Some(32) + local optionB = Option.Some(64) + local opt = optionA:And(optionB) + -- opt == optionB + + local optionA = Option.None + local optionB = Option.Some(64) + local opt = optionA:And(optionB) + -- opt == Option.None + ``` +]=] +function Option:And(optionB) + if self:IsSome() then + return optionB + else + return Option.None + end +end + +--[=[ + @param andThenFn (value: any) -> Option + @return value: Option + If the option holds a value, then the `andThenFn` + function is called with the held value of the option, + and then the resultant Option returned by the `andThenFn` + is returned. Otherwise, None is returned. + + ```lua + local optA = Option.Some(32) + local optB = optA:AndThen(function(num) + return Option.Some(num * 2) + end) + print(optB:Expect("Expected number")) --> 64 + ``` +]=] +function Option:AndThen(andThenFn) + if self:IsSome() then + local result = andThenFn(self:Unwrap()) + Option.Assert(result) + return result + else + return Option.None + end +end + +--[=[ + @param optionB Option + @return Option + If caller has a value, returns itself. Otherwise, returns `optionB`. +]=] +function Option:Or(optionB) + if self:IsSome() then + return self + else + return optionB + end +end + +--[=[ + @param orElseFn () -> Option + @return Option + If caller has a value, returns itself. Otherwise, returns the + option generated by the `orElseFn` function. +]=] +function Option:OrElse(orElseFn) + if self:IsSome() then + return self + else + local result = orElseFn() + Option.Assert(result) + return result + end +end + +--[=[ + @param optionB Option + @return Option + If both `self` and `optionB` have values _or_ both don't have a value, + then this returns None. Otherwise, it returns the option that does have + a value. +]=] +function Option:XOr(optionB) + local someOptA = self:IsSome() + local someOptB = optionB:IsSome() + if someOptA == someOptB then + return Option.None + elseif someOptA then + return self + else + return optionB + end +end + +--[=[ + @param predicate (value: any) -> boolean + @return Option + Returns `self` if this option has a value and the predicate returns `true. + Otherwise, returns None. +]=] +function Option:Filter(predicate) + if self:IsNone() or not predicate(self._v) then + return Option.None + else + return self + end +end + +--[=[ + @param value any + @return boolean + Returns `true` if this option contains `value`. +]=] +function Option:Contains(value) + return self:IsSome() and self._v == value +end + +--[=[ + @return string + Metamethod to transform the option into a string. + ```lua + local optA = Option.Some(64) + local optB = Option.None + print(optA) --> Option + print(optB) --> Option + ``` +]=] +function Option:__tostring() + if self:IsSome() then + return ("Option<" .. typeof(self._v) .. ">") + else + return "Option" + end +end + +--[=[ + @return boolean + @param opt Option + Metamethod to check equality between two options. Returns `true` if both + options hold the same value _or_ both options are None. + ```lua + local o1 = Option.Some(32) + local o2 = Option.Some(32) + local o3 = Option.Some(64) + local o4 = Option.None + local o5 = Option.None + + print(o1 == o2) --> true + print(o1 == o3) --> false + print(o1 == o4) --> false + print(o4 == o5) --> true + ``` +]=] +function Option:__eq(opt) + if Option.Is(opt) then + if self:IsSome() and opt:IsSome() then + return (self:Unwrap() == opt:Unwrap()) + elseif self:IsNone() and opt:IsNone() then + return true + end + end + return false +end + +--[=[ + @prop None Option + @within Option + Represents no value. +]=] +Option.None = Option._new() + +return (Option :: any) :: { + Some: (value: T) -> Option, + Wrap: (value: T) -> Option, + + Is: (obj: any) -> boolean, + + None: Option, +} diff --git a/samples/Luau/Signal.luau b/samples/Luau/Signal.luau new file mode 100644 index 0000000000..ccf7aebc4f --- /dev/null +++ b/samples/Luau/Signal.luau @@ -0,0 +1,437 @@ +--// Coauthored by @Sleitnick (https://github.com/sleitnick) and @Stravant (https://github.com/stravant) +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/signal/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +--!optimize 2 +--!strict +--!native + +-- ----------------------------------------------------------------------------- +-- Batched Yield-Safe Signal Implementation -- +-- This is a Signal class which has effectively identical behavior to a -- +-- normal RBXScriptSignal, with the only difference being a couple extra -- +-- stack frames at the bottom of the stack trace when an error is thrown. -- +-- This implementation caches runner coroutines, so the ability to yield in -- +-- the signal handlers comes at minimal extra cost over a naive signal -- +-- implementation that either always or never spawns a thread. -- +-- -- +-- License: -- +-- Licensed under the MIT license. -- +-- -- +-- Authors: -- +-- stravant - July 31st, 2021 - Created the file. -- +-- sleitnick - August 3rd, 2021 - Modified for Knit. -- +-- ----------------------------------------------------------------------------- + +-- Signal types +export type Connection = { + Disconnect: (self: Connection) -> (), + Destroy: (self: Connection) -> (), + Connected: boolean, +} + +export type Signal = { + Fire: (self: Signal, T...) -> (), + FireDeferred: (self: Signal, T...) -> (), + Connect: (self: Signal, fn: (T...) -> ()) -> Connection, + Once: (self: Signal, fn: (T...) -> ()) -> Connection, + DisconnectAll: (self: Signal) -> (), + GetConnections: (self: Signal) -> { Connection }, + Destroy: (self: Signal) -> (), + Wait: (self: Signal) -> T..., +} + +-- The currently idle thread to run the next handler on +local freeRunnerThread = nil + +-- Function which acquires the currently idle handler runner thread, runs the +-- function fn on it, and then releases the thread, returning it to being the +-- currently idle one. +-- If there was a currently idle runner thread already, that's okay, that old +-- one will just get thrown and eventually GCed. +local function acquireRunnerThreadAndCallEventHandler(fn, ...) + local acquiredRunnerThread = freeRunnerThread + freeRunnerThread = nil + fn(...) + -- The handler finished running, this runner thread is free again. + freeRunnerThread = acquiredRunnerThread +end + +-- Coroutine runner that we create coroutines of. The coroutine can be +-- repeatedly resumed with functions to run followed by the argument to run +-- them with. +local function runEventHandlerInFreeThread(...) + acquireRunnerThreadAndCallEventHandler(...) + while true do + acquireRunnerThreadAndCallEventHandler(coroutine.yield()) + end +end + +--[=[ + @within Signal + @interface SignalConnection + .Connected boolean + .Disconnect (SignalConnection) -> () + + Represents a connection to a signal. + ```lua + local connection = signal:Connect(function() end) + print(connection.Connected) --> true + connection:Disconnect() + print(connection.Connected) --> false + ``` +]=] + +-- Connection class +local Connection = {} +Connection.__index = Connection + +function Connection:Disconnect() + if not self.Connected then + return + end + self.Connected = false + + -- Unhook the node, but DON'T clear it. That way any fire calls that are + -- currently sitting on this node will be able to iterate forwards off of + -- it, but any subsequent fire calls will not hit it, and it will be GCed + -- when no more fire calls are sitting on it. + if self._signal._handlerListHead == self then + self._signal._handlerListHead = self._next + else + local prev = self._signal._handlerListHead + while prev and prev._next ~= self do + prev = prev._next + end + if prev then + prev._next = self._next + end + end +end + +Connection.Destroy = Connection.Disconnect + +-- Make Connection strict +setmetatable(Connection, { + __index = function(_tb, key) + error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2) + end, + __newindex = function(_tb, key, _value) + error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2) + end, +}) + +--[=[ + @within Signal + @type ConnectionFn (...any) -> () + + A function connected to a signal. +]=] + +--[=[ + @class Signal + + A Signal is a data structure that allows events to be dispatched + and observed. + + This implementation is a direct copy of the de facto standard, [GoodSignal](https://devforum.roblox.com/t/lua-signal-class-comparison-optimal-goodsignal-class/1387063), + with some added methods and typings. + + For example: + ```lua + local signal = Signal.new() + + -- Subscribe to a signal: + signal:Connect(function(msg) + print("Got message:", msg) + end) + + -- Dispatch an event: + signal:Fire("Hello world!") + ``` +]=] +local Signal = {} +Signal.__index = Signal + +--[=[ + Constructs a new Signal + + @return Signal +]=] +function Signal.new(): Signal + local self = setmetatable({ + _handlerListHead = false, + _proxyHandler = nil, + _yieldedThreads = nil, + }, Signal) + + return self +end + +--[=[ + Constructs a new Signal that wraps around an RBXScriptSignal. + + @param rbxScriptSignal RBXScriptSignal -- Existing RBXScriptSignal to wrap + @return Signal + + For example: + ```lua + local signal = Signal.Wrap(workspace.ChildAdded) + signal:Connect(function(part) print(part.Name .. " added") end) + Instance.new("Part").Parent = workspace + ``` +]=] +function Signal.Wrap(rbxScriptSignal: RBXScriptSignal): Signal + assert( + typeof(rbxScriptSignal) == "RBXScriptSignal", + "Argument #1 to Signal.Wrap must be a RBXScriptSignal; got " .. typeof(rbxScriptSignal) + ) + + local signal = Signal.new() + signal._proxyHandler = rbxScriptSignal:Connect(function(...) + signal:Fire(...) + end) + + return signal +end + +--[=[ + Checks if the given object is a Signal. + + @param obj any -- Object to check + @return boolean -- `true` if the object is a Signal. +]=] +function Signal.Is(obj: any): boolean + return type(obj) == "table" and getmetatable(obj) == Signal +end + +--[=[ + @param fn ConnectionFn + @return SignalConnection + + Connects a function to the signal, which will be called anytime the signal is fired. + ```lua + signal:Connect(function(msg, num) + print(msg, num) + end) + + signal:Fire("Hello", 25) + ``` +]=] +function Signal:Connect(fn) + local connection = setmetatable({ + Connected = true, + _signal = self, + _fn = fn, + _next = false, + }, Connection) + + if self._handlerListHead then + connection._next = self._handlerListHead + self._handlerListHead = connection + else + self._handlerListHead = connection + end + + return connection +end + +--[=[ + @deprecated v1.3.0 -- Use `Signal:Once` instead. + @param fn ConnectionFn + @return SignalConnection +]=] +function Signal:ConnectOnce(fn) + return self:Once(fn) +end + +--[=[ + @param fn ConnectionFn + @return SignalConnection + + Connects a function to the signal, which will be called the next time the signal fires. Once + the connection is triggered, it will disconnect itself. + ```lua + signal:Once(function(msg, num) + print(msg, num) + end) + + signal:Fire("Hello", 25) + signal:Fire("This message will not go through", 10) + ``` +]=] +function Signal:Once(fn) + local connection + local done = false + + connection = self:Connect(function(...) + if done then + return + end + + done = true + connection:Disconnect() + fn(...) + end) + + return connection +end + +function Signal:GetConnections() + local items = {} + + local item = self._handlerListHead + while item do + table.insert(items, item) + item = item._next + end + + return items +end + +-- Disconnect all handlers. Since we use a linked list it suffices to clear the +-- reference to the head handler. +--[=[ + Disconnects all connections from the signal. + ```lua + signal:DisconnectAll() + ``` +]=] +function Signal:DisconnectAll() + local item = self._handlerListHead + while item do + item.Connected = false + item = item._next + end + self._handlerListHead = false + + local yieldedThreads = rawget(self, "_yieldedThreads") + if yieldedThreads then + for thread in yieldedThreads do + if coroutine.status(thread) == "suspended" then + warn(debug.traceback(thread, "signal disconnected; yielded thread cancelled", 2)) + task.cancel(thread) + end + end + table.clear(self._yieldedThreads) + end +end + +-- Signal:Fire(...) implemented by running the handler functions on the +-- coRunnerThread, and any time the resulting thread yielded without returning +-- to us, that means that it yielded to the Roblox scheduler and has been taken +-- over by Roblox scheduling, meaning we have to make a new coroutine runner. +--[=[ + @param ... any + + Fire the signal, which will call all of the connected functions with the given arguments. + ```lua + signal:Fire("Hello") + + -- Any number of arguments can be fired: + signal:Fire("Hello", 32, {Test = "Test"}, true) + ``` +]=] +function Signal:Fire(...) + local item = self._handlerListHead + while item do + if item.Connected then + if not freeRunnerThread then + freeRunnerThread = coroutine.create(runEventHandlerInFreeThread) + end + task.spawn(freeRunnerThread, item._fn, ...) + end + item = item._next + end +end + +--[=[ + @param ... any + + Same as `Fire`, but uses `task.defer` internally & doesn't take advantage of thread reuse. + ```lua + signal:FireDeferred("Hello") + ``` +]=] +function Signal:FireDeferred(...) + local item = self._handlerListHead + while item do + local conn = item + task.defer(function(...) + if conn.Connected then + conn._fn(...) + end + end, ...) + item = item._next + end +end + +--[=[ + @return ... any + @yields + + Yields the current thread until the signal is fired, and returns the arguments fired from the signal. + Yielding the current thread is not always desirable. If the desire is to only capture the next event + fired, using `Once` might be a better solution. + ```lua + task.spawn(function() + local msg, num = signal:Wait() + print(msg, num) --> "Hello", 32 + end) + signal:Fire("Hello", 32) + ``` +]=] +function Signal:Wait() + local yieldedThreads = rawget(self, "_yieldedThreads") + if not yieldedThreads then + yieldedThreads = {} + rawset(self, "_yieldedThreads", yieldedThreads) + end + + local thread = coroutine.running() + yieldedThreads[thread] = true + + self:Once(function(...) + yieldedThreads[thread] = nil + task.spawn(thread, ...) + end) + + return coroutine.yield() +end + +--[=[ + Cleans up the signal. + + Technically, this is only necessary if the signal is created using + `Signal.Wrap`. Connections should be properly GC'd once the signal + is no longer referenced anywhere. However, it is still good practice + to include ways to strictly clean up resources. Calling `Destroy` + on a signal will also disconnect all connections immediately. + ```lua + signal:Destroy() + ``` +]=] +function Signal:Destroy() + self:DisconnectAll() + + local proxyHandler = rawget(self, "_proxyHandler") + if proxyHandler then + proxyHandler:Disconnect() + end +end + +-- Make signal strict +setmetatable(Signal, { + __index = function(_tb, key) + error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2) + end, + __newindex = function(_tb, key, _value) + error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2) + end, +}) + +return table.freeze({ + new = Signal.new, + Wrap = Signal.Wrap, + Is = Signal.Is, +}) diff --git a/samples/Luau/createProcessor.luau b/samples/Luau/createProcessor.luau deleted file mode 100644 index d55aba076e..0000000000 --- a/samples/Luau/createProcessor.luau +++ /dev/null @@ -1,22 +0,0 @@ ---// Authored by @sinlerdev (https://github.com/sinlerdev) ---// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/createProcessor.luau) ---// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) - ---!strict -local function createProcessor(valueChecker: (a: any, b: any) -> boolean, cleaner: (taskItem: any) -> ()) - return function(old: T, new: T, isDestroying: boolean) - if isDestroying then - cleaner(old) - return false - end - - if valueChecker(old, new) then - return false - end - - cleaner(old) - return true - end -end - -return createProcessor diff --git a/samples/Luau/createSignal.luau b/samples/Luau/createSignal.luau deleted file mode 100644 index 5ffd461fff..0000000000 --- a/samples/Luau/createSignal.luau +++ /dev/null @@ -1,37 +0,0 @@ ---// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) ---// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/createSignal.luau) ---// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) - ---!strict - -local function createSignal() - local listeners = {} - local signal = {} - - function signal:Connect(listener) - listeners[listener] = true - - local connection = { - Connected = true, - } - - function connection.Disconnect() - connection.Connected = false - listeners[listener] = nil - end - connection.disconnect = connection.Disconnect - - return connection - end - signal.connect = signal.Connect - - local function fire(...) - for listener, _ in pairs(listeners) do - spawn(listener, ...) - end - end - - return signal, fire -end - -return createSignal diff --git a/samples/Luau/equals.luau b/samples/Luau/equals.luau deleted file mode 100644 index 6b259e0359..0000000000 --- a/samples/Luau/equals.luau +++ /dev/null @@ -1,17 +0,0 @@ ---// Authored by @sinlerdev (https://github.com/sinlerdev) ---// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/equals.luau) ---// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) - ---!strict - -local function equals(a: any, b: any): boolean - -- INFO: Vinum doesn't officially support immutability currently- so, we just assume - -- that every new table entry is not equal. See issue 26 - if type(a) == "table" then - return false - end - - return a == b -end - -return equals diff --git a/samples/Luau/getEnv.luau b/samples/Luau/getEnv.luau new file mode 100644 index 0000000000..d4877c3d17 --- /dev/null +++ b/samples/Luau/getEnv.luau @@ -0,0 +1,39 @@ +--// Authored by @Vocksel (https://github.com/vocksel) +--// Fetched from (https://github.com/flipbook-labs/module-loader/blob/main/src/init.lua) +--// Licensed under the MIT License (https://github.com/flipbook-labs/module-loader/blob/main/LICENSE) + +--!optimize 2 +--!strict +--!native + +local baseEnv = getfenv() + +local function getEnv(scriptRelativeTo: LuaSourceContainer?, globals: { [any]: any }?) + local newEnv = {} + + setmetatable(newEnv, { + __index = function(_, key) + if key ~= "plugin" then + return baseEnv[key] + else + return nil + end + end, + }) + + newEnv._G = globals + newEnv.script = scriptRelativeTo + + local realDebug = debug + + newEnv.debug = setmetatable({ + traceback = function(message) + -- Block traces to prevent overly verbose TestEZ output + return message or "" + end, + }, { __index = realDebug }) + + return newEnv +end + +return getEnv diff --git a/samples/Luau/isEmpty.luau b/samples/Luau/isEmpty.luau deleted file mode 100644 index c90e654801..0000000000 --- a/samples/Luau/isEmpty.luau +++ /dev/null @@ -1,21 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/isEmpty.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---!strict ---[=[ - Returns true if the collection is empty. - - ```lua - Freeze.isEmpty({}) - -- true - ``` - - @within Freeze - @function isEmpty - @return boolean -]=] - -return function(collection) - return next(collection) == nil -end diff --git a/samples/Luau/none.luau b/samples/Luau/none.luau deleted file mode 100644 index 118a75d8ce..0000000000 --- a/samples/Luau/none.luau +++ /dev/null @@ -1,21 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/None.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---[=[ - @prop None None - @within Freeze - - Since lua tables cannot distinguish between values not being present and a value of nil, - `Freeze.None` exists to represent values that should be interpreted as `nil`. - - This is useful when removing values with functions such as [`Freeze.Dictionary.merge`](../api/Dictionary#merge). -]=] - -local None = newproxy(true) - -getmetatable(None).__tostring = function() - return "Freeze.None" -end - -return None diff --git a/samples/Luau/replaceMacros.luau b/samples/Luau/replaceMacros.luau deleted file mode 100644 index 11daa0b368..0000000000 --- a/samples/Luau/replaceMacros.luau +++ /dev/null @@ -1,311 +0,0 @@ ---// Authored by @grilme99 (https://github.com/grilme99) ---// Fetched from (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/scripts/replaceMacros.luau) ---// Licensed under the MIT License (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/LICENSE-Brooke) - --- upstream: https://github.com/dead/typeflex/blob/422cb26/tools/repalce_macros.py - -local function YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - if node:getStyle().instanceName ~= paramName then - local style: YGStyle = node:getStyle() - style.instanceName = paramName - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) - if - node:getStyle().instanceName.value ~= YGFloatSanitize(paramName) - or node:getStyle().instanceName.unit ~= YGUnit.Percent - then - local style: YGStyle = node:getStyle() - style.instanceName.value = YGFloatSanitize(paramName) - style.instanceName.unit = if YGFloatIsUndefined(paramName) then YGUnit.Auto else YGUnit.Percent - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -local function YGNodeStyleSet##nameAuto(node: YGNode) - if node:getStyle().instanceName.unit ~= YGUnit.Auto then - local style: YGStyle = node:getStyle() - style.instanceName.value = 0 - style.instanceName.unit = YGUnit.Auto - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - return node:getStyle().instanceName -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, edge: YGEdge, paramName: number) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName[edge].unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName[edge] = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, edge: YGEdge, paramName: number) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent - ) - - if - (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName[edge].unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName[edge] = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -local function YGNodeStyleGet##name(node: YGNode, edge: YGEdge): type - local value: YGValue = node:getStyle().instanceName[edge] - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeStyleSet##nameAuto(node: YGNode, edge: YGEdge) - if node:getStyle().instanceName[edge].unit ~= YGUnit.Auto then - local style: YGStyle = node:getStyle() - style.instanceName[edge].value = 0 - style.instanceName[edge].unit = YGUnit.Auto - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL("number", name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - local value: YGValue = node:getStyle().instanceName - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL("number", name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - local value: YGValue = node:getStyle().instanceName - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_LAYOUT_PROPERTY_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeLayoutGet##name(node: YGNode): type - return node:getLayout().instanceName -end -exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local function YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeLayoutGet##name(node: YGNode, edge: YGEdge): type - -- YGAssertWithNode(node, edge <= YGEdge.End, "Cannot get layout properties of multi-edge shorthands") - - if edge == YGEdge.Start then - if node:getLayout().direction == YGDirection.RTL then - return node:getLayout().instanceName[YGEdge.Right] - else - return node:getLayout().instanceName[YGEdge.Left] - end - end - - if edge == YGEdge.End then - if node:getLayout().direction == YGDirection.RTL then - return node:getLayout().instanceName[YGEdge.Left] - else - return node:getLayout().instanceName[YGEdge.Right] - end - end - - return node:getLayout().instanceName[edge] -end -exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local cod = "" - -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDirection", "Direction", "direction", "direction") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGFlexDirection", "FlexDirection", "flexDirection", "flexDirection") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGJustify", "JustifyContent", "justifyContent", "justifyContent") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignContent", "alignContent", "alignContent") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignItems", "alignItems", "alignItems") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignSelf", "alignSelf", "alignSelf") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGPositionType", "PositionType", "positionType", "positionType") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGWrap", "FlexWrap", "flexWrap", "flexWrap") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGOverflow", "Overflow", "overflow", "overflow") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDisplay", "Display", "display", "display") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Position", "position", "position") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Margin", "margin", "margin") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Padding", "padding", "padding") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Margin", "margin") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Width", "width", "dimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Height", "height", "dimensions[YGDimension.Height]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinWidth", "minWidth", "minDimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinHeight", "minHeight", "minDimensions[YGDimension.Height]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxWidth", "maxWidth", "maxDimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxHeight", "maxHeight", "maxDimensions[YGDimension.Height]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Left", "position[YGEdge.Left]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Top", "position[YGEdge.Top]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Right", "position[YGEdge.Right]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Bottom", "position[YGEdge.Bottom]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Width", "dimensions[YGDimension.Width]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Height", "dimensions[YGDimension.Height]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("YGDirection", "Direction", "direction") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("boolean", "HadOverflow", "hadOverflow") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Margin", "margin") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Border", "border") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Padding", "padding") - -print(cod) \ No newline at end of file diff --git a/samples/Luau/timeStepper.luau b/samples/Luau/timeStepper.luau deleted file mode 100644 index 038a614451..0000000000 --- a/samples/Luau/timeStepper.luau +++ /dev/null @@ -1,36 +0,0 @@ ---// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) ---// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/TimeStepper.luau) ---// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) - ---!strict - ---[[ - -A time stepper is responsible for stepping systems in a deterministic order at a set interval. - -]] - -local TimeStepper = {} -TimeStepper.__index = TimeStepper - -function TimeStepper.new(interval: number, systems) - local self = setmetatable({ - _systems = systems, - _interval = interval, - }, TimeStepper) - - return self -end - -function TimeStepper:start() - coroutine.resume(coroutine.create(function() - while true do - local timeStep, _ = wait(self._interval) - for _, system in ipairs(self._systems) do - system:step(timeStep) - end - end - end)) -end - -return TimeStepper diff --git a/samples/Luau/toSet.luau b/samples/Luau/toSet.luau deleted file mode 100644 index 7d18f99ed2..0000000000 --- a/samples/Luau/toSet.luau +++ /dev/null @@ -1,22 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/List/toSet.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---!strict - ---[=[ - Returns a Set from the given List. - - @within List - @function toSet - @ignore -]=] -return function(list: { Value }): { [Value]: boolean } - local set = {} - - for _, v in list do - set[v] = true - end - - return set -end From 8fa4f1f428e80c05dfa1e9dca023b6d3e1d360ee Mon Sep 17 00:00:00 2001 From: robloxiandemo <76854027+robloxiandemo@users.noreply.github.com> Date: Sat, 6 Apr 2024 17:41:09 -0400 Subject: [PATCH 27/29] Patch: Update the samples further. Enhancement: Replace the signal sample. --- samples/Luau/Option.luau | 497 ----- samples/Luau/Promise.luau | 2243 ----------------------- samples/Luau/ReactRobloxHostConfig.luau | 1366 -------------- samples/Luau/Replicator.luau | 546 ------ samples/Luau/Signal.luau | 437 ----- samples/Luau/pathToRegexp.luau | 854 --------- samples/Luau/roblox.luau | 512 ------ samples/Luau/ser.luau | 181 ++ samples/Luau/waitFor.luau | 265 +++ 9 files changed, 446 insertions(+), 6455 deletions(-) delete mode 100644 samples/Luau/Option.luau delete mode 100644 samples/Luau/Promise.luau delete mode 100644 samples/Luau/ReactRobloxHostConfig.luau delete mode 100644 samples/Luau/Replicator.luau delete mode 100644 samples/Luau/Signal.luau delete mode 100644 samples/Luau/pathToRegexp.luau delete mode 100644 samples/Luau/roblox.luau create mode 100644 samples/Luau/ser.luau create mode 100644 samples/Luau/waitFor.luau diff --git a/samples/Luau/Option.luau b/samples/Luau/Option.luau deleted file mode 100644 index 726b443eac..0000000000 --- a/samples/Luau/Option.luau +++ /dev/null @@ -1,497 +0,0 @@ ---// Authored by @Sleitnick (https://github.com/sleitnick) ---// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/option/init.lua) ---// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) - ---!optimize 2 ---!strict ---!native - --- Option --- Stephen Leitnick --- August 28, 2020 - ---[[ - - MatchTable { - Some: (value: any) -> any - None: () -> any - } - - CONSTRUCTORS: - - Option.Some(anyNonNilValue): Option - Option.Wrap(anyValue): Option - - - STATIC FIELDS: - - Option.None: Option - - - STATIC METHODS: - - Option.Is(obj): boolean - - - METHODS: - - opt:Match(): (matches: MatchTable) -> any - opt:IsSome(): boolean - opt:IsNone(): boolean - opt:Unwrap(): any - opt:Expect(errMsg: string): any - opt:ExpectNone(errMsg: string): void - opt:UnwrapOr(default: any): any - opt:UnwrapOrElse(default: () -> any): any - opt:And(opt2: Option): Option - opt:AndThen(predicate: (unwrapped: any) -> Option): Option - opt:Or(opt2: Option): Option - opt:OrElse(orElseFunc: () -> Option): Option - opt:XOr(opt2: Option): Option - opt:Contains(value: any): boolean - - -------------------------------------------------------------------- - - Options are useful for handling nil-value cases. Any time that an - operation might return nil, it is useful to instead return an - Option, which will indicate that the value might be nil, and should - be explicitly checked before using the value. This will help - prevent common bugs caused by nil values that can fail silently. - - - Example: - - local result1 = Option.Some(32) - local result2 = Option.Some(nil) - local result3 = Option.Some("Hi") - local result4 = Option.Some(nil) - local result5 = Option.None - - -- Use 'Match' to match if the value is Some or None: - result1:Match { - Some = function(value) print(value) end; - None = function() print("No value") end; - } - - -- Raw check: - if result2:IsSome() then - local value = result2:Unwrap() -- Explicitly call Unwrap - print("Value of result2:", value) - end - - if result3:IsNone() then - print("No result for result3") - end - - -- Bad, will throw error bc result4 is none: - local value = result4:Unwrap() - ---]] - -export type MatchTable = { - Some: (value: T) -> any, - None: () -> any, -} - -export type MatchFn = (matches: MatchTable) -> any - -export type DefaultFn = () -> T - -export type AndThenFn = (value: T) -> Option - -export type OrElseFn = () -> Option - -export type Option = typeof(setmetatable( - {} :: { - Match: (self: Option) -> MatchFn, - IsSome: (self: Option) -> boolean, - IsNone: (self: Option) -> boolean, - Contains: (self: Option, value: T) -> boolean, - Unwrap: (self: Option) -> T, - Expect: (self: Option, errMsg: string) -> T, - ExpectNone: (self: Option, errMsg: string) -> nil, - UnwrapOr: (self: Option, default: T) -> T, - UnwrapOrElse: (self: Option, defaultFn: DefaultFn) -> T, - And: (self: Option, opt2: Option) -> Option, - AndThen: (self: Option, predicate: AndThenFn) -> Option, - Or: (self: Option, opt2: Option) -> Option, - OrElse: (self: Option, orElseFunc: OrElseFn) -> Option, - XOr: (self: Option, opt2: Option) -> Option, - }, - {} :: { - __index: Option, - } -)) - -local CLASSNAME = "Option" - ---[=[ - @class Option - - Represents an optional value in Lua. This is useful to avoid `nil` bugs, which can - go silently undetected within code and cause hidden or hard-to-find bugs. -]=] -local Option = {} -Option.__index = Option - -function Option._new(value) - local self = setmetatable({ - ClassName = CLASSNAME, - _v = value, - _s = (value ~= nil), - }, Option) - return self -end - ---[=[ - @param value T - @return Option - - Creates an Option instance with the given value. Throws an error - if the given value is `nil`. -]=] -function Option.Some(value) - assert(value ~= nil, "Option.Some() value cannot be nil") - return Option._new(value) -end - ---[=[ - @param value T - @return Option | Option - - Safely wraps the given value as an option. If the - value is `nil`, returns `Option.None`, otherwise - returns `Option.Some(value)`. -]=] -function Option.Wrap(value) - if value == nil then - return Option.None - else - return Option.Some(value) - end -end - ---[=[ - @param obj any - @return boolean - Returns `true` if `obj` is an Option. -]=] -function Option.Is(obj) - return type(obj) == "table" and getmetatable(obj) == Option -end - ---[=[ - @param obj any - Throws an error if `obj` is not an Option. -]=] -function Option.Assert(obj) - assert(Option.Is(obj), "Result was not of type Option") -end - ---[=[ - @param data table - @return Option - Deserializes the data into an Option. This data should have come from - the `option:Serialize()` method. -]=] -function Option.Deserialize(data) -- type data = {ClassName: string, Value: any} - assert(type(data) == "table" and data.ClassName == CLASSNAME, "Invalid data for deserializing Option") - return data.Value == nil and Option.None or Option.Some(data.Value) -end - ---[=[ - @return table - Returns a serialized version of the option. -]=] -function Option:Serialize() - return { - ClassName = self.ClassName, - Value = self._v, - } -end - ---[=[ - @param matches {Some: (value: any) -> any, None: () -> any} - @return any - - Matches against the option. - - ```lua - local opt = Option.Some(32) - opt:Match { - Some = function(num) print("Number", num) end, - None = function() print("No value") end, - } - ``` -]=] -function Option:Match(matches) - local onSome = matches.Some - local onNone = matches.None - assert(type(onSome) == "function", "Missing 'Some' match") - assert(type(onNone) == "function", "Missing 'None' match") - if self:IsSome() then - return onSome(self:Unwrap()) - else - return onNone() - end -end - ---[=[ - @return boolean - Returns `true` if the option has a value. -]=] -function Option:IsSome() - return self._s -end - ---[=[ - @return boolean - Returns `true` if the option is None. -]=] -function Option:IsNone() - return not self._s -end - ---[=[ - @param msg string - @return value: any - Unwraps the value in the option, otherwise throws an error with `msg` as the error message. - ```lua - local opt = Option.Some(10) - print(opt:Expect("No number")) -> 10 - print(Option.None:Expect("No number")) -- Throws an error "No number" - ``` -]=] -function Option:Expect(msg) - assert(self:IsSome(), msg) - return self._v -end - ---[=[ - @param msg string - Throws an error with `msg` as the error message if the value is _not_ None. -]=] -function Option:ExpectNone(msg) - assert(self:IsNone(), msg) -end - ---[=[ - @return value: any - Returns the value in the option, or throws an error if the option is None. -]=] -function Option:Unwrap() - return self:Expect("Cannot unwrap option of None type") -end - ---[=[ - @param default any - @return value: any - If the option holds a value, returns the value. Otherwise, returns `default`. -]=] -function Option:UnwrapOr(default) - if self:IsSome() then - return self:Unwrap() - else - return default - end -end - ---[=[ - @param defaultFn () -> any - @return value: any - If the option holds a value, returns the value. Otherwise, returns the - result of the `defaultFn` function. -]=] -function Option:UnwrapOrElse(defaultFn) - if self:IsSome() then - return self:Unwrap() - else - return defaultFn() - end -end - ---[=[ - @param optionB Option - @return Option - Returns `optionB` if the calling option has a value, - otherwise returns None. - - ```lua - local optionA = Option.Some(32) - local optionB = Option.Some(64) - local opt = optionA:And(optionB) - -- opt == optionB - - local optionA = Option.None - local optionB = Option.Some(64) - local opt = optionA:And(optionB) - -- opt == Option.None - ``` -]=] -function Option:And(optionB) - if self:IsSome() then - return optionB - else - return Option.None - end -end - ---[=[ - @param andThenFn (value: any) -> Option - @return value: Option - If the option holds a value, then the `andThenFn` - function is called with the held value of the option, - and then the resultant Option returned by the `andThenFn` - is returned. Otherwise, None is returned. - - ```lua - local optA = Option.Some(32) - local optB = optA:AndThen(function(num) - return Option.Some(num * 2) - end) - print(optB:Expect("Expected number")) --> 64 - ``` -]=] -function Option:AndThen(andThenFn) - if self:IsSome() then - local result = andThenFn(self:Unwrap()) - Option.Assert(result) - return result - else - return Option.None - end -end - ---[=[ - @param optionB Option - @return Option - If caller has a value, returns itself. Otherwise, returns `optionB`. -]=] -function Option:Or(optionB) - if self:IsSome() then - return self - else - return optionB - end -end - ---[=[ - @param orElseFn () -> Option - @return Option - If caller has a value, returns itself. Otherwise, returns the - option generated by the `orElseFn` function. -]=] -function Option:OrElse(orElseFn) - if self:IsSome() then - return self - else - local result = orElseFn() - Option.Assert(result) - return result - end -end - ---[=[ - @param optionB Option - @return Option - If both `self` and `optionB` have values _or_ both don't have a value, - then this returns None. Otherwise, it returns the option that does have - a value. -]=] -function Option:XOr(optionB) - local someOptA = self:IsSome() - local someOptB = optionB:IsSome() - if someOptA == someOptB then - return Option.None - elseif someOptA then - return self - else - return optionB - end -end - ---[=[ - @param predicate (value: any) -> boolean - @return Option - Returns `self` if this option has a value and the predicate returns `true. - Otherwise, returns None. -]=] -function Option:Filter(predicate) - if self:IsNone() or not predicate(self._v) then - return Option.None - else - return self - end -end - ---[=[ - @param value any - @return boolean - Returns `true` if this option contains `value`. -]=] -function Option:Contains(value) - return self:IsSome() and self._v == value -end - ---[=[ - @return string - Metamethod to transform the option into a string. - ```lua - local optA = Option.Some(64) - local optB = Option.None - print(optA) --> Option - print(optB) --> Option - ``` -]=] -function Option:__tostring() - if self:IsSome() then - return ("Option<" .. typeof(self._v) .. ">") - else - return "Option" - end -end - ---[=[ - @return boolean - @param opt Option - Metamethod to check equality between two options. Returns `true` if both - options hold the same value _or_ both options are None. - ```lua - local o1 = Option.Some(32) - local o2 = Option.Some(32) - local o3 = Option.Some(64) - local o4 = Option.None - local o5 = Option.None - - print(o1 == o2) --> true - print(o1 == o3) --> false - print(o1 == o4) --> false - print(o4 == o5) --> true - ``` -]=] -function Option:__eq(opt) - if Option.Is(opt) then - if self:IsSome() and opt:IsSome() then - return (self:Unwrap() == opt:Unwrap()) - elseif self:IsNone() and opt:IsNone() then - return true - end - end - return false -end - ---[=[ - @prop None Option - @within Option - Represents no value. -]=] -Option.None = Option._new() - -return (Option :: any) :: { - Some: (value: T) -> Option, - Wrap: (value: T) -> Option, - - Is: (obj: any) -> boolean, - - None: Option, -} diff --git a/samples/Luau/Promise.luau b/samples/Luau/Promise.luau deleted file mode 100644 index fb7c265b43..0000000000 --- a/samples/Luau/Promise.luau +++ /dev/null @@ -1,2243 +0,0 @@ ---// Coauthored by @ArxkDev (https://github.com/arxkdev) and @Evaera (https://github.com/evaera) ---// Fetched from (https://github.com/arxkdev/Leaderboard/blob/main/src/Leaderboard/Promise.luau) ---// Licensed under the MIT License (https://github.com/arxkdev/Leaderboard/blob/main/LICENSE) - ---!strict ---[[ - An implementation of Promises similar to Promise/A+. -]] - -export type Status = "Started" | "Resolved" | "Rejected" | "Cancelled" - -export type Promise = { - andThen: ( - self: Promise, - successHandler: (...any) -> ...any, - failureHandler: ((...any) -> ...any)? - ) -> Promise, - andThenCall: (self: Promise, callback: (TArgs...) -> ...any, TArgs...) -> any, - andThenReturn: (self: Promise, ...any) -> Promise, - - await: (self: Promise) -> (boolean, ...any), - awaitStatus: (self: Promise) -> (Status, ...any), - - cancel: (self: Promise) -> (), - catch: (self: Promise, failureHandler: (...any) -> ...any) -> Promise, - expect: (self: Promise) -> ...any, - - finally: (self: Promise, finallyHandler: (status: Status) -> ...any) -> Promise, - finallyCall: (self: Promise, callback: (TArgs...) -> ...any, TArgs...) -> Promise, - finallyReturn: (self: Promise, ...any) -> Promise, - - getStatus: (self: Promise) -> Status, - now: (self: Promise, rejectionValue: any?) -> Promise, - tap: (self: Promise, tapHandler: (...any) -> ...any) -> Promise, - timeout: (self: Promise, seconds: number, rejectionValue: any?) -> Promise, -} - -export type TypedPromise = { - andThen: ( - self: TypedPromise, - successHandler: (result: T) -> ...any, - failureHandler: ((...any) -> ...any)? - ) -> TypedPromise, - andThenReturn: (self: TypedPromise, ...any) -> TypedPromise, - - await: (self: TypedPromise) -> (boolean, ...T), - awaitStatus: (self: TypedPromise) -> (Status, ...T), - - cancel: (self: TypedPromise) -> (), - catch: (self: TypedPromise, failureHandler: (...any) -> ...any) -> TypedPromise, - expect: (self: TypedPromise) -> ...any, - finally: (self: TypedPromise, finallyHandler: (status: Status) -> ...any) -> TypedPromise, - - finallyReturn: (self: TypedPromise, ...any) -> TypedPromise, - finallyCall: (self: TypedPromise, callback: (TArgs...) -> ...any, TArgs...) -> TypedPromise, - - getStatus: (self: TypedPromise) -> Status, - now: (self: TypedPromise, rejectionValue: any?) -> TypedPromise, - tap: (self: TypedPromise, tapHandler: (...any) -> ...any) -> TypedPromise, - timeout: (self: TypedPromise, seconds: number, rejectionValue: any?) -> TypedPromise, -} - -type Signal = { - Connect: (self: Signal, callback: (T...) -> ...any) -> SignalConnection, -} - -type SignalConnection = { - Disconnect: (self: SignalConnection) -> ...any, - [any]: any, -} - -local ERROR_NON_PROMISE_IN_LIST = "Non-promise value passed into %s at index %s" -local ERROR_NON_LIST = "Please pass a list of promises to %s" -local ERROR_NON_FUNCTION = "Please pass a handler function to %s!" -local MODE_KEY_METATABLE = { __mode = "k" } - -local function isCallable(value) - if type(value) == "function" then - return true - end - - if type(value) == "table" then - local metatable = getmetatable(value) - if metatable and type(rawget(metatable, "__call")) == "function" then - return true - end - end - - return false -end - ---[[ - Creates an enum dictionary with some metamethods to prevent common mistakes. -]] -local function makeEnum(enumName, members) - local enum = {} - - for _, memberName in ipairs(members) do - enum[memberName] = memberName - end - - return setmetatable(enum, { - __index = function(_, k) - error(string.format("%s is not in %s!", k, enumName), 2) - end, - __newindex = function() - error(string.format("Creating new members in %s is not allowed!", enumName), 2) - end, - }) -end - ---[=[ - An object to represent runtime errors that occur during execution. - Promises that experience an error like this will be rejected with - an instance of this object. - - @class Error -]=] -local Error -do - Error = { - Kind = makeEnum("Promise.Error.Kind", { - "ExecutionError", - "AlreadyCancelled", - "NotResolvedInTime", - "TimedOut", - }), - } - Error.__index = Error - - function Error.new(options, parent) - options = options or {} - return setmetatable({ - error = tostring(options.error) or "[This error has no error text.]", - trace = options.trace, - context = options.context, - kind = options.kind, - parent = parent, - createdTick = os.clock(), - createdTrace = debug.traceback(), - }, Error) - end - - function Error.is(anything) - if type(anything) == "table" then - local metatable = getmetatable(anything) - - if type(metatable) == "table" then - return rawget(anything, "error") ~= nil and type(rawget(metatable, "extend")) == "function" - end - end - - return false - end - - function Error.isKind(anything, kind) - assert(kind ~= nil, "Argument #2 to Promise.Error.isKind must not be nil") - - return Error.is(anything) and anything.kind == kind - end - - function Error:extend(options) - options = options or {} - - options.kind = options.kind or self.kind - - return Error.new(options, self) - end - - function Error:getErrorChain() - local runtimeErrors = { self } - - while runtimeErrors[#runtimeErrors].parent do - table.insert(runtimeErrors, runtimeErrors[#runtimeErrors].parent) - end - - return runtimeErrors - end - - function Error:__tostring() - local errorStrings = { - string.format("-- Promise.Error(%s) --", self.kind or "?"), - } - - for _, runtimeError in ipairs(self:getErrorChain()) do - table.insert( - errorStrings, - table.concat({ - runtimeError.trace or runtimeError.error, - runtimeError.context, - }, "\n") - ) - end - - return table.concat(errorStrings, "\n") - end -end - ---[[ - Packs a number of arguments into a table and returns its length. - - Used to cajole varargs without dropping sparse values. -]] -local function pack(...) - return select("#", ...), { ... } -end - ---[[ - Returns first value (success), and packs all following values. -]] -local function packResult(success, ...) - return success, select("#", ...), { ... } -end - -local function makeErrorHandler(traceback) - assert(traceback ~= nil, "traceback is nil") - - return function(err) - -- If the error object is already a table, forward it directly. - -- Should we extend the error here and add our own trace? - - if type(err) == "table" then - return err - end - - return Error.new({ - error = err, - kind = Error.Kind.ExecutionError, - trace = debug.traceback(tostring(err), 2), - context = "Promise created at:\n\n" .. traceback, - }) - end -end - ---[[ - Calls a Promise executor with error handling. -]] -local function runExecutor(traceback, callback, ...) - return packResult(xpcall(callback, makeErrorHandler(traceback), ...)) -end - ---[[ - Creates a function that invokes a callback with correct error handling and - resolution mechanisms. -]] -local function createAdvancer(traceback, callback, resolve, reject) - return function(...) - local ok, resultLength, result = runExecutor(traceback, callback, ...) - - if ok then - resolve(unpack(result, 1, resultLength)) - else - reject(result[1]) - end - end -end - -local function isEmpty(t) - return next(t) == nil -end - ---[=[ - An enum value used to represent the Promise's status. - @interface Status - @tag enum - @within Promise - .Started "Started" -- The Promise is executing, and not settled yet. - .Resolved "Resolved" -- The Promise finished successfully. - .Rejected "Rejected" -- The Promise was rejected. - .Cancelled "Cancelled" -- The Promise was cancelled before it finished. -]=] ---[=[ - @prop Status Status - @within Promise - @readonly - @tag enums - A table containing all members of the `Status` enum, e.g., `Promise.Status.Resolved`. -]=] ---[=[ - A Promise is an object that represents a value that will exist in the future, but doesn't right now. - Promises allow you to then attach callbacks that can run once the value becomes available (known as *resolving*), - or if an error has occurred (known as *rejecting*). - - @class Promise - @__index prototype -]=] -local Promise = { - Error = Error, - Status = makeEnum("Promise.Status", { "Started", "Resolved", "Rejected", "Cancelled" }), - _getTime = os.clock, - _timeEvent = game:GetService("RunService").Heartbeat, - _unhandledRejectionCallbacks = {}, -} -Promise.prototype = {} -Promise.__index = Promise.prototype - -function Promise._new(traceback, callback, parent) - if parent ~= nil and not Promise.is(parent) then - error("Argument #2 to Promise.new must be a promise or nil", 2) - end - - local self = { - -- The executor thread. - _thread = nil, - - -- Used to locate where a promise was created - _source = traceback, - - _status = Promise.Status.Started, - - -- A table containing a list of all results, whether success or failure. - -- Only valid if _status is set to something besides Started - _values = nil, - - -- Lua doesn't like sparse arrays very much, so we explicitly store the - -- length of _values to handle middle nils. - _valuesLength = -1, - - -- Tracks if this Promise has no error observers.. - _unhandledRejection = true, - - -- Queues representing functions we should invoke when we update! - _queuedResolve = {}, - _queuedReject = {}, - _queuedFinally = {}, - - -- The function to run when/if this promise is cancelled. - _cancellationHook = nil, - - -- The "parent" of this promise in a promise chain. Required for - -- cancellation propagation upstream. - _parent = parent, - - -- Consumers are Promises that have chained onto this one. - -- We track them for cancellation propagation downstream. - _consumers = setmetatable({}, MODE_KEY_METATABLE), - } - - if parent and parent._status == Promise.Status.Started then - parent._consumers[self] = true - end - - setmetatable(self, Promise) - - local function resolve(...) - self:_resolve(...) - end - - local function reject(...) - self:_reject(...) - end - - local function onCancel(cancellationHook) - if cancellationHook then - if self._status == Promise.Status.Cancelled then - cancellationHook() - else - self._cancellationHook = cancellationHook - end - end - - return self._status == Promise.Status.Cancelled - end - - self._thread = coroutine.create(function() - local ok, _, result = runExecutor(self._source, callback, resolve, reject, onCancel) - - if not ok then - reject(result[1]) - end - end) - - task.spawn(self._thread) - - return self -end - ---[=[ - Construct a new Promise that will be resolved or rejected with the given callbacks. - - If you `resolve` with a Promise, it will be chained onto. - - You can safely yield within the executor function and it will not block the creating thread. - - ```lua - local myFunction() - return Promise.new(function(resolve, reject, onCancel) - wait(1) - resolve("Hello world!") - end) - end - - myFunction():andThen(print) - ``` - - You do not need to use `pcall` within a Promise. Errors that occur during execution will be caught and turned into a rejection automatically. If `error()` is called with a table, that table will be the rejection value. Otherwise, string errors will be converted into `Promise.Error(Promise.Error.Kind.ExecutionError)` objects for tracking debug information. - - You may register an optional cancellation hook by using the `onCancel` argument: - - * This should be used to abort any ongoing operations leading up to the promise being settled. - * Call the `onCancel` function with a function callback as its only argument to set a hook which will in turn be called when/if the promise is cancelled. - * `onCancel` returns `true` if the Promise was already cancelled when you called `onCancel`. - * Calling `onCancel` with no argument will not override a previously set cancellation hook, but it will still return `true` if the Promise is currently cancelled. - * You can set the cancellation hook at any time before resolving. - * When a promise is cancelled, calls to `resolve` or `reject` will be ignored, regardless of if you set a cancellation hook or not. - - :::caution - If the Promise is cancelled, the `executor` thread is closed with `coroutine.close` after the cancellation hook is called. - - You must perform any cleanup code in the cancellation hook: any time your executor yields, it **may never resume**. - ::: - - @param executor (resolve: (...: any) -> (), reject: (...: any) -> (), onCancel: (abortHandler?: () -> ()) -> boolean) -> () - @return Promise -]=] -function Promise.new(executor) - return Promise._new(debug.traceback(nil, 2), executor) -end - -function Promise:__tostring() - return string.format("Promise(%s)", self._status) -end - ---[=[ - The same as [Promise.new](/api/Promise#new), except execution begins after the next `Heartbeat` event. - - This is a spiritual replacement for `spawn`, but it does not suffer from the same [issues](https://eryn.io/gist/3db84579866c099cdd5bb2ff37947cec) as `spawn`. - - ```lua - local function waitForChild(instance, childName, timeout) - return Promise.defer(function(resolve, reject) - local child = instance:WaitForChild(childName, timeout) - - ;(child and resolve or reject)(child) - end) - end - ``` - - @param executor (resolve: (...: any) -> (), reject: (...: any) -> (), onCancel: (abortHandler?: () -> ()) -> boolean) -> () - @return Promise -]=] -function Promise.defer(executor) - local traceback = debug.traceback(nil, 2) - local promise - promise = Promise._new(traceback, function(resolve, reject, onCancel) - local connection - connection = Promise._timeEvent:Connect(function() - connection:Disconnect() - local ok, _, result = runExecutor(traceback, executor, resolve, reject, onCancel) - - if not ok then - reject(result[1]) - end - end) - end) - - return promise -end - --- Backwards compatibility -Promise.async = Promise.defer - ---[=[ - Creates an immediately resolved Promise with the given value. - - ```lua - -- Example using Promise.resolve to deliver cached values: - function getSomething(name) - if cache[name] then - return Promise.resolve(cache[name]) - else - return Promise.new(function(resolve, reject) - local thing = getTheThing() - cache[name] = thing - - resolve(thing) - end) - end - end - ``` - - @param ... any - @return Promise<...any> -]=] -function Promise.resolve(...) - local length, values = pack(...) - return Promise._new(debug.traceback(nil, 2), function(resolve) - resolve(unpack(values, 1, length)) - end) -end - ---[=[ - Creates an immediately rejected Promise with the given value. - - :::caution - Something needs to consume this rejection (i.e. `:catch()` it), otherwise it will emit an unhandled Promise rejection warning on the next frame. Thus, you should not create and store rejected Promises for later use. Only create them on-demand as needed. - ::: - - @param ... any - @return Promise<...any> -]=] -function Promise.reject(...) - local length, values = pack(...) - return Promise._new(debug.traceback(nil, 2), function(_, reject) - reject(unpack(values, 1, length)) - end) -end - ---[[ - Runs a non-promise-returning function as a Promise with the - given arguments. -]] -function Promise._try(traceback, callback, ...) - local valuesLength, values = pack(...) - - return Promise._new(traceback, function(resolve) - resolve(callback(unpack(values, 1, valuesLength))) - end) -end - ---[=[ - Begins a Promise chain, calling a function and returning a Promise resolving with its return value. If the function errors, the returned Promise will be rejected with the error. You can safely yield within the Promise.try callback. - - :::info - `Promise.try` is similar to [Promise.promisify](#promisify), except the callback is invoked immediately instead of returning a new function. - ::: - - ```lua - Promise.try(function() - return math.random(1, 2) == 1 and "ok" or error("Oh an error!") - end) - :andThen(function(text) - print(text) - end) - :catch(function(err) - warn("Something went wrong") - end) - ``` - - @param callback (...: T...) -> ...any - @param ... T... -- Additional arguments passed to `callback` - @return Promise -]=] -function Promise.try(callback, ...) - return Promise._try(debug.traceback(nil, 2), callback, ...) -end - ---[[ - Returns a new promise that: - * is resolved when all input promises resolve - * is rejected if ANY input promises reject -]] -function Promise._all(traceback, promises, amount) - if type(promises) ~= "table" then - error(string.format(ERROR_NON_LIST, "Promise.all"), 3) - end - - -- We need to check that each value is a promise here so that we can produce - -- a proper error rather than a rejected promise with our error. - for i, promise in pairs(promises) do - if not Promise.is(promise) then - error(string.format(ERROR_NON_PROMISE_IN_LIST, "Promise.all", tostring(i)), 3) - end - end - - -- If there are no values then return an already resolved promise. - if #promises == 0 or amount == 0 then - return Promise.resolve({}) - end - - return Promise._new(traceback, function(resolve, reject, onCancel) - -- An array to contain our resolved values from the given promises. - local resolvedValues = {} - local newPromises = {} - - -- Keep a count of resolved promises because just checking the resolved - -- values length wouldn't account for promises that resolve with nil. - local resolvedCount = 0 - local rejectedCount = 0 - local done = false - - local function cancel() - for _, promise in ipairs(newPromises) do - promise:cancel() - end - end - - -- Called when a single value is resolved and resolves if all are done. - local function resolveOne(i, ...) - if done then - return - end - - resolvedCount = resolvedCount + 1 - - if amount == nil then - resolvedValues[i] = ... - else - resolvedValues[resolvedCount] = ... - end - - if resolvedCount >= (amount or #promises) then - done = true - resolve(resolvedValues) - cancel() - end - end - - onCancel(cancel) - - -- We can assume the values inside `promises` are all promises since we - -- checked above. - for i, promise in ipairs(promises) do - newPromises[i] = promise:andThen(function(...) - resolveOne(i, ...) - end, function(...) - rejectedCount = rejectedCount + 1 - - if amount == nil or #promises - rejectedCount < amount then - cancel() - done = true - - reject(...) - end - end) - end - - if done then - cancel() - end - end) -end - ---[=[ - Accepts an array of Promises and returns a new promise that: - * is resolved after all input promises resolve. - * is rejected if *any* input promises reject. - - :::info - Only the first return value from each promise will be present in the resulting array. - ::: - - After any input Promise rejects, all other input Promises that are still pending will be cancelled if they have no other consumers. - - ```lua - local promises = { - returnsAPromise("example 1"), - returnsAPromise("example 2"), - returnsAPromise("example 3"), - } - - return Promise.all(promises) - ``` - - @param promises {Promise} - @return Promise<{T}> -]=] -function Promise.all(promises) - return Promise._all(debug.traceback(nil, 2), promises) -end - ---[=[ - Folds an array of values or promises into a single value. The array is traversed sequentially. - - The reducer function can return a promise or value directly. Each iteration receives the resolved value from the previous, and the first receives your defined initial value. - - The folding will stop at the first rejection encountered. - ```lua - local basket = {"blueberry", "melon", "pear", "melon"} - Promise.fold(basket, function(cost, fruit) - if fruit == "blueberry" then - return cost -- blueberries are free! - else - -- call a function that returns a promise with the fruit price - return fetchPrice(fruit):andThen(function(fruitCost) - return cost + fruitCost - end) - end - end, 0) - ``` - - @since v3.1.0 - @param list {T | Promise} - @param reducer (accumulator: U, value: T, index: number) -> U | Promise - @param initialValue U -]=] -function Promise.fold(list, reducer, initialValue) - assert(type(list) == "table", "Bad argument #1 to Promise.fold: must be a table") - assert(isCallable(reducer), "Bad argument #2 to Promise.fold: must be a function") - - local accumulator = Promise.resolve(initialValue) - return Promise.each(list, function(resolvedElement, i) - accumulator = accumulator:andThen(function(previousValueResolved) - return reducer(previousValueResolved, resolvedElement, i) - end) - end):andThen(function() - return accumulator - end) -end - ---[=[ - Accepts an array of Promises and returns a Promise that is resolved as soon as `count` Promises are resolved from the input array. The resolved array values are in the order that the Promises resolved in. When this Promise resolves, all other pending Promises are cancelled if they have no other consumers. - - `count` 0 results in an empty array. The resultant array will never have more than `count` elements. - - ```lua - local promises = { - returnsAPromise("example 1"), - returnsAPromise("example 2"), - returnsAPromise("example 3"), - } - - return Promise.some(promises, 2) -- Only resolves with first 2 promises to resolve - ``` - - @param promises {Promise} - @param count number - @return Promise<{T}> -]=] -function Promise.some(promises, count) - assert(type(count) == "number", "Bad argument #2 to Promise.some: must be a number") - - return Promise._all(debug.traceback(nil, 2), promises, count) -end - ---[=[ - Accepts an array of Promises and returns a Promise that is resolved as soon as *any* of the input Promises resolves. It will reject only if *all* input Promises reject. As soon as one Promises resolves, all other pending Promises are cancelled if they have no other consumers. - - Resolves directly with the value of the first resolved Promise. This is essentially [[Promise.some]] with `1` count, except the Promise resolves with the value directly instead of an array with one element. - - ```lua - local promises = { - returnsAPromise("example 1"), - returnsAPromise("example 2"), - returnsAPromise("example 3"), - } - - return Promise.any(promises) -- Resolves with first value to resolve (only rejects if all 3 rejected) - ``` - - @param promises {Promise} - @return Promise -]=] -function Promise.any(promises) - return Promise._all(debug.traceback(nil, 2), promises, 1):andThen(function(values) - return values[1] - end) -end - ---[=[ - Accepts an array of Promises and returns a new Promise that resolves with an array of in-place Statuses when all input Promises have settled. This is equivalent to mapping `promise:finally` over the array of Promises. - - ```lua - local promises = { - returnsAPromise("example 1"), - returnsAPromise("example 2"), - returnsAPromise("example 3"), - } - - return Promise.allSettled(promises) - ``` - - @param promises {Promise} - @return Promise<{Status}> -]=] -function Promise.allSettled(promises) - if type(promises) ~= "table" then - error(string.format(ERROR_NON_LIST, "Promise.allSettled"), 2) - end - - -- We need to check that each value is a promise here so that we can produce - -- a proper error rather than a rejected promise with our error. - for i, promise in pairs(promises) do - if not Promise.is(promise) then - error(string.format(ERROR_NON_PROMISE_IN_LIST, "Promise.allSettled", tostring(i)), 2) - end - end - - -- If there are no values then return an already resolved promise. - if #promises == 0 then - return Promise.resolve({}) - end - - return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel) - -- An array to contain our resolved values from the given promises. - local fates = {} - local newPromises = {} - - -- Keep a count of resolved promises because just checking the resolved - -- values length wouldn't account for promises that resolve with nil. - local finishedCount = 0 - - -- Called when a single value is resolved and resolves if all are done. - local function resolveOne(i, ...) - finishedCount = finishedCount + 1 - - fates[i] = ... - - if finishedCount >= #promises then - resolve(fates) - end - end - - onCancel(function() - for _, promise in ipairs(newPromises) do - promise:cancel() - end - end) - - -- We can assume the values inside `promises` are all promises since we - -- checked above. - for i, promise in ipairs(promises) do - newPromises[i] = promise:finally(function(...) - resolveOne(i, ...) - end) - end - end) -end - ---[=[ - Accepts an array of Promises and returns a new promise that is resolved or rejected as soon as any Promise in the array resolves or rejects. - - :::warning - If the first Promise to settle from the array settles with a rejection, the resulting Promise from `race` will reject. - - If you instead want to tolerate rejections, and only care about at least one Promise resolving, you should use [Promise.any](#any) or [Promise.some](#some) instead. - ::: - - All other Promises that don't win the race will be cancelled if they have no other consumers. - - ```lua - local promises = { - returnsAPromise("example 1"), - returnsAPromise("example 2"), - returnsAPromise("example 3"), - } - - return Promise.race(promises) -- Only returns 1st value to resolve or reject - ``` - - @param promises {Promise} - @return Promise -]=] -function Promise.race(promises) - assert(type(promises) == "table", string.format(ERROR_NON_LIST, "Promise.race")) - - for i, promise in pairs(promises) do - assert(Promise.is(promise), string.format(ERROR_NON_PROMISE_IN_LIST, "Promise.race", tostring(i))) - end - - return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel) - local newPromises = {} - local finished = false - - local function cancel() - for _, promise in ipairs(newPromises) do - promise:cancel() - end - end - - local function finalize(callback) - return function(...) - cancel() - finished = true - return callback(...) - end - end - - if onCancel(finalize(reject)) then - return - end - - for i, promise in ipairs(promises) do - newPromises[i] = promise:andThen(finalize(resolve), finalize(reject)) - end - - if finished then - cancel() - end - end) -end - ---[=[ - Iterates serially over the given an array of values, calling the predicate callback on each value before continuing. - - If the predicate returns a Promise, we wait for that Promise to resolve before moving on to the next item - in the array. - - :::info - `Promise.each` is similar to `Promise.all`, except the Promises are ran in order instead of all at once. - - But because Promises are eager, by the time they are created, they're already running. Thus, we need a way to defer creation of each Promise until a later time. - - The predicate function exists as a way for us to operate on our data instead of creating a new closure for each Promise. If you would prefer, you can pass in an array of functions, and in the predicate, call the function and return its return value. - ::: - - ```lua - Promise.each({ - "foo", - "bar", - "baz", - "qux" - }, function(value, index) - return Promise.delay(1):andThen(function() - print(("%d) Got %s!"):format(index, value)) - end) - end) - - --[[ - (1 second passes) - > 1) Got foo! - (1 second passes) - > 2) Got bar! - (1 second passes) - > 3) Got baz! - (1 second passes) - > 4) Got qux! - ]] - ``` - - If the Promise a predicate returns rejects, the Promise from `Promise.each` is also rejected with the same value. - - If the array of values contains a Promise, when we get to that point in the list, we wait for the Promise to resolve before calling the predicate with the value. - - If a Promise in the array of values is already Rejected when `Promise.each` is called, `Promise.each` rejects with that value immediately (the predicate callback will never be called even once). If a Promise in the list is already Cancelled when `Promise.each` is called, `Promise.each` rejects with `Promise.Error(Promise.Error.Kind.AlreadyCancelled`). If a Promise in the array of values is Started at first, but later rejects, `Promise.each` will reject with that value and iteration will not continue once iteration encounters that value. - - Returns a Promise containing an array of the returned/resolved values from the predicate for each item in the array of values. - - If this Promise returned from `Promise.each` rejects or is cancelled for any reason, the following are true: - - Iteration will not continue. - - Any Promises within the array of values will now be cancelled if they have no other consumers. - - The Promise returned from the currently active predicate will be cancelled if it hasn't resolved yet. - - @since 3.0.0 - @param list {T | Promise} - @param predicate (value: T, index: number) -> U | Promise - @return Promise<{U}> -]=] -function Promise.each(list, predicate) - assert(type(list) == "table", string.format(ERROR_NON_LIST, "Promise.each")) - assert(isCallable(predicate), string.format(ERROR_NON_FUNCTION, "Promise.each")) - - return Promise._new(debug.traceback(nil, 2), function(resolve, reject, onCancel) - local results = {} - local promisesToCancel = {} - - local cancelled = false - - local function cancel() - for _, promiseToCancel in ipairs(promisesToCancel) do - promiseToCancel:cancel() - end - end - - onCancel(function() - cancelled = true - - cancel() - end) - - -- We need to preprocess the list of values and look for Promises. - -- If we find some, we must register our andThen calls now, so that those Promises have a consumer - -- from us registered. If we don't do this, those Promises might get cancelled by something else - -- before we get to them in the series because it's not possible to tell that we plan to use it - -- unless we indicate it here. - - local preprocessedList = {} - - for index, value in ipairs(list) do - if Promise.is(value) then - if value:getStatus() == Promise.Status.Cancelled then - cancel() - return reject(Error.new({ - error = "Promise is cancelled", - kind = Error.Kind.AlreadyCancelled, - context = string.format( - "The Promise that was part of the array at index %d passed into Promise.each was already cancelled when Promise.each began.\n\nThat Promise was created at:\n\n%s", - index, - value._source - ), - })) - elseif value:getStatus() == Promise.Status.Rejected then - cancel() - return reject(select(2, value:await())) - end - - -- Chain a new Promise from this one so we only cancel ours - local ourPromise = value:andThen(function(...) - return ... - end) - - table.insert(promisesToCancel, ourPromise) - preprocessedList[index] = ourPromise - else - preprocessedList[index] = value - end - end - - for index, value in ipairs(preprocessedList) do - if Promise.is(value) then - local success - success, value = value:await() - - if not success then - cancel() - return reject(value) - end - end - - if cancelled then - return - end - - local predicatePromise = Promise.resolve(predicate(value, index)) - - table.insert(promisesToCancel, predicatePromise) - - local success, result = predicatePromise:await() - - if not success then - cancel() - return reject(result) - end - - results[index] = result - end - - resolve(results) - end) -end - ---[=[ - Checks whether the given object is a Promise via duck typing. This only checks if the object is a table and has an `andThen` method. - - @param object any - @return boolean -- `true` if the given `object` is a Promise. -]=] -function Promise.is(object) - if type(object) ~= "table" then - return false - end - - local objectMetatable = getmetatable(object) - - if objectMetatable == Promise then - -- The Promise came from this library. - return true - elseif objectMetatable == nil then - -- No metatable, but we should still chain onto tables with andThen methods - return isCallable(object.andThen) - elseif - type(objectMetatable) == "table" - and type(rawget(objectMetatable, "__index")) == "table" - and isCallable(rawget(rawget(objectMetatable, "__index"), "andThen")) - then - -- Maybe this came from a different or older Promise library. - return true - end - - return false -end - ---[=[ - Wraps a function that yields into one that returns a Promise. - - Any errors that occur while executing the function will be turned into rejections. - - :::info - `Promise.promisify` is similar to [Promise.try](#try), except the callback is returned as a callable function instead of being invoked immediately. - ::: - - ```lua - local sleep = Promise.promisify(wait) - - sleep(1):andThen(print) - ``` - - ```lua - local isPlayerInGroup = Promise.promisify(function(player, groupId) - return player:IsInGroup(groupId) - end) - ``` - - @param callback (...: any) -> ...any - @return (...: any) -> Promise -]=] -function Promise.promisify(callback) - return function(...) - return Promise._try(debug.traceback(nil, 2), callback, ...) - end -end - ---[=[ - Returns a Promise that resolves after `seconds` seconds have passed. The Promise resolves with the actual amount of time that was waited. - - This function is **not** a wrapper around `wait`. `Promise.delay` uses a custom scheduler which provides more accurate timing. As an optimization, cancelling this Promise instantly removes the task from the scheduler. - - :::warning - Passing `NaN`, infinity, or a number less than 1/60 is equivalent to passing 1/60. - ::: - - ```lua - Promise.delay(5):andThenCall(print, "This prints after 5 seconds") - ``` - - @function delay - @within Promise - @param seconds number - @return Promise -]=] -do - -- uses a sorted doubly linked list (queue) to achieve O(1) remove operations and O(n) for insert - - -- the initial node in the linked list - local first - local connection - - function Promise.delay(seconds) - assert(type(seconds) == "number", "Bad argument #1 to Promise.delay, must be a number.") - -- If seconds is -INF, INF, NaN, or less than 1 / 60, assume seconds is 1 / 60. - -- This mirrors the behavior of wait() - if not (seconds >= 1 / 60) or seconds == math.huge then - seconds = 1 / 60 - end - - return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel) - local startTime = Promise._getTime() - local endTime = startTime + seconds - - local node = { - resolve = resolve, - startTime = startTime, - endTime = endTime, - } - - if connection == nil then -- first is nil when connection is nil - first = node - connection = Promise._timeEvent:Connect(function() - local threadStart = Promise._getTime() - - while first ~= nil and first.endTime < threadStart do - local current = first - first = current.next - - if first == nil then - connection:Disconnect() - connection = nil - else - first.previous = nil - end - - current.resolve(Promise._getTime() - current.startTime) - end - end) - else -- first is non-nil - if first.endTime < endTime then -- if `node` should be placed after `first` - -- we will insert `node` between `current` and `next` - -- (i.e. after `current` if `next` is nil) - local current = first - local next = current.next - - while next ~= nil and next.endTime < endTime do - current = next - next = current.next - end - - -- `current` must be non-nil, but `next` could be `nil` (i.e. last item in list) - current.next = node - node.previous = current - - if next ~= nil then - node.next = next - next.previous = node - end - else - -- set `node` to `first` - node.next = first - first.previous = node - first = node - end - end - - onCancel(function() - -- remove node from queue - local next = node.next - - if first == node then - if next == nil then -- if `node` is the first and last - connection:Disconnect() - connection = nil - else -- if `node` is `first` and not the last - next.previous = nil - end - first = next - else - local previous = node.previous - -- since `node` is not `first`, then we know `previous` is non-nil - previous.next = next - - if next ~= nil then - next.previous = previous - end - end - end) - end) - end -end - ---[=[ - Returns a new Promise that resolves if the chained Promise resolves within `seconds` seconds, or rejects if execution time exceeds `seconds`. The chained Promise will be cancelled if the timeout is reached. - - Rejects with `rejectionValue` if it is non-nil. If a `rejectionValue` is not given, it will reject with a `Promise.Error(Promise.Error.Kind.TimedOut)`. This can be checked with [[Error.isKind]]. - - ```lua - getSomething():timeout(5):andThen(function(something) - -- got something and it only took at max 5 seconds - end):catch(function(e) - -- Either getting something failed or the time was exceeded. - - if Promise.Error.isKind(e, Promise.Error.Kind.TimedOut) then - warn("Operation timed out!") - else - warn("Operation encountered an error!") - end - end) - ``` - - Sugar for: - - ```lua - Promise.race({ - Promise.delay(seconds):andThen(function() - return Promise.reject( - rejectionValue == nil - and Promise.Error.new({ kind = Promise.Error.Kind.TimedOut }) - or rejectionValue - ) - end), - promise - }) - ``` - - @param seconds number - @param rejectionValue? any -- The value to reject with if the timeout is reached - @return Promise -]=] -function Promise.prototype:timeout(seconds, rejectionValue) - local traceback = debug.traceback(nil, 2) - - return Promise.race({ - Promise.delay(seconds):andThen(function() - return Promise.reject(rejectionValue == nil and Error.new({ - kind = Error.Kind.TimedOut, - error = "Timed out", - context = string.format( - "Timeout of %d seconds exceeded.\n:timeout() called at:\n\n%s", - seconds, - traceback - ), - }) or rejectionValue) - end), - self, - }) -end - ---[=[ - Returns the current Promise status. - - @return Status -]=] -function Promise.prototype:getStatus() - return self._status -end - ---[[ - Creates a new promise that receives the result of this promise. - - The given callbacks are invoked depending on that result. -]] -function Promise.prototype:_andThen(traceback, successHandler, failureHandler) - self._unhandledRejection = false - - -- If we are already cancelled, we return a cancelled Promise - if self._status == Promise.Status.Cancelled then - local promise = Promise.new(function() end) - promise:cancel() - - return promise - end - - -- Create a new promise to follow this part of the chain - return Promise._new(traceback, function(resolve, reject, onCancel) - -- Our default callbacks just pass values onto the next promise. - -- This lets success and failure cascade correctly! - - local successCallback = resolve - if successHandler then - successCallback = createAdvancer(traceback, successHandler, resolve, reject) - end - - local failureCallback = reject - if failureHandler then - failureCallback = createAdvancer(traceback, failureHandler, resolve, reject) - end - - if self._status == Promise.Status.Started then - -- If we haven't resolved yet, put ourselves into the queue - table.insert(self._queuedResolve, successCallback) - table.insert(self._queuedReject, failureCallback) - - onCancel(function() - -- These are guaranteed to exist because the cancellation handler is guaranteed to only - -- be called at most once - if self._status == Promise.Status.Started then - table.remove(self._queuedResolve, table.find(self._queuedResolve, successCallback)) - table.remove(self._queuedReject, table.find(self._queuedReject, failureCallback)) - end - end) - elseif self._status == Promise.Status.Resolved then - -- This promise has already resolved! Trigger success immediately. - successCallback(unpack(self._values, 1, self._valuesLength)) - elseif self._status == Promise.Status.Rejected then - -- This promise died a terrible death! Trigger failure immediately. - failureCallback(unpack(self._values, 1, self._valuesLength)) - end - end, self) -end - ---[=[ - Chains onto an existing Promise and returns a new Promise. - - :::warning - Within the failure handler, you should never assume that the rejection value is a string. Some rejections within the Promise library are represented by [[Error]] objects. If you want to treat it as a string for debugging, you should call `tostring` on it first. - ::: - - You can return a Promise from the success or failure handler and it will be chained onto. - - Calling `andThen` on a cancelled Promise returns a cancelled Promise. - - :::tip - If the Promise returned by `andThen` is cancelled, `successHandler` and `failureHandler` will not run. - - To run code no matter what, use [Promise:finally]. - ::: - - @param successHandler (...: any) -> ...any - @param failureHandler? (...: any) -> ...any - @return Promise<...any> -]=] -function Promise.prototype:andThen(successHandler, failureHandler) - assert(successHandler == nil or isCallable(successHandler), string.format(ERROR_NON_FUNCTION, "Promise:andThen")) - assert(failureHandler == nil or isCallable(failureHandler), string.format(ERROR_NON_FUNCTION, "Promise:andThen")) - - return self:_andThen(debug.traceback(nil, 2), successHandler, failureHandler) -end - ---[=[ - Shorthand for `Promise:andThen(nil, failureHandler)`. - - Returns a Promise that resolves if the `failureHandler` worked without encountering an additional error. - - :::warning - Within the failure handler, you should never assume that the rejection value is a string. Some rejections within the Promise library are represented by [[Error]] objects. If you want to treat it as a string for debugging, you should call `tostring` on it first. - ::: - - Calling `catch` on a cancelled Promise returns a cancelled Promise. - - :::tip - If the Promise returned by `catch` is cancelled, `failureHandler` will not run. - - To run code no matter what, use [Promise:finally]. - ::: - - @param failureHandler (...: any) -> ...any - @return Promise<...any> -]=] -function Promise.prototype:catch(failureHandler) - assert(failureHandler == nil or isCallable(failureHandler), string.format(ERROR_NON_FUNCTION, "Promise:catch")) - return self:_andThen(debug.traceback(nil, 2), nil, failureHandler) -end - ---[=[ - Similar to [Promise.andThen](#andThen), except the return value is the same as the value passed to the handler. In other words, you can insert a `:tap` into a Promise chain without affecting the value that downstream Promises receive. - - ```lua - getTheValue() - :tap(print) - :andThen(function(theValue) - print("Got", theValue, "even though print returns nil!") - end) - ``` - - If you return a Promise from the tap handler callback, its value will be discarded but `tap` will still wait until it resolves before passing the original value through. - - @param tapHandler (...: any) -> ...any - @return Promise<...any> -]=] -function Promise.prototype:tap(tapHandler) - assert(isCallable(tapHandler), string.format(ERROR_NON_FUNCTION, "Promise:tap")) - return self:_andThen(debug.traceback(nil, 2), function(...) - local callbackReturn = tapHandler(...) - - if Promise.is(callbackReturn) then - local length, values = pack(...) - return callbackReturn:andThen(function() - return unpack(values, 1, length) - end) - end - - return ... - end) -end - ---[=[ - Attaches an `andThen` handler to this Promise that calls the given callback with the predefined arguments. The resolved value is discarded. - - ```lua - promise:andThenCall(someFunction, "some", "arguments") - ``` - - This is sugar for - - ```lua - promise:andThen(function() - return someFunction("some", "arguments") - end) - ``` - - @param callback (...: any) -> any - @param ...? any -- Additional arguments which will be passed to `callback` - @return Promise -]=] -function Promise.prototype:andThenCall(callback, ...) - assert(isCallable(callback), string.format(ERROR_NON_FUNCTION, "Promise:andThenCall")) - local length, values = pack(...) - return self:_andThen(debug.traceback(nil, 2), function() - return callback(unpack(values, 1, length)) - end) -end - ---[=[ - Attaches an `andThen` handler to this Promise that discards the resolved value and returns the given value from it. - - ```lua - promise:andThenReturn("some", "values") - ``` - - This is sugar for - - ```lua - promise:andThen(function() - return "some", "values" - end) - ``` - - :::caution - Promises are eager, so if you pass a Promise to `andThenReturn`, it will begin executing before `andThenReturn` is reached in the chain. Likewise, if you pass a Promise created from [[Promise.reject]] into `andThenReturn`, it's possible that this will trigger the unhandled rejection warning. If you need to return a Promise, it's usually best practice to use [[Promise.andThen]]. - ::: - - @param ... any -- Values to return from the function - @return Promise -]=] -function Promise.prototype:andThenReturn(...) - local length, values = pack(...) - return self:_andThen(debug.traceback(nil, 2), function() - return unpack(values, 1, length) - end) -end - ---[=[ - Cancels this promise, preventing the promise from resolving or rejecting. Does not do anything if the promise is already settled. - - Cancellations will propagate upwards and downwards through chained promises. - - Promises will only be cancelled if all of their consumers are also cancelled. This is to say that if you call `andThen` twice on the same promise, and you cancel only one of the child promises, it will not cancel the parent promise until the other child promise is also cancelled. - - ```lua - promise:cancel() - ``` -]=] -function Promise.prototype:cancel() - if self._status ~= Promise.Status.Started then - return - end - - self._status = Promise.Status.Cancelled - - if self._cancellationHook then - self._cancellationHook() - end - - coroutine.close(self._thread) - - if self._parent then - self._parent:_consumerCancelled(self) - end - - for child in pairs(self._consumers) do - child:cancel() - end - - self:_finalize() -end - ---[[ - Used to decrease the number of consumers by 1, and if there are no more, - cancel this promise. -]] -function Promise.prototype:_consumerCancelled(consumer) - if self._status ~= Promise.Status.Started then - return - end - - self._consumers[consumer] = nil - - if next(self._consumers) == nil then - self:cancel() - end -end - ---[[ - Used to set a handler for when the promise resolves, rejects, or is - cancelled. -]] -function Promise.prototype:_finally(traceback, finallyHandler) - self._unhandledRejection = false - - local promise = Promise._new(traceback, function(resolve, reject, onCancel) - local handlerPromise - - onCancel(function() - -- The finally Promise is not a proper consumer of self. We don't care about the resolved value. - -- All we care about is running at the end. Therefore, if self has no other consumers, it's safe to - -- cancel. We don't need to hold out cancelling just because there's a finally handler. - self:_consumerCancelled(self) - - if handlerPromise then - handlerPromise:cancel() - end - end) - - local finallyCallback = resolve - if finallyHandler then - finallyCallback = function(...) - local callbackReturn = finallyHandler(...) - - if Promise.is(callbackReturn) then - handlerPromise = callbackReturn - - callbackReturn - :finally(function(status) - if status ~= Promise.Status.Rejected then - resolve(self) - end - end) - :catch(function(...) - reject(...) - end) - else - resolve(self) - end - end - end - - if self._status == Promise.Status.Started then - -- The promise is not settled, so queue this. - table.insert(self._queuedFinally, finallyCallback) - else - -- The promise already settled or was cancelled, run the callback now. - finallyCallback(self._status) - end - end) - - return promise -end - ---[=[ - Set a handler that will be called regardless of the promise's fate. The handler is called when the promise is - resolved, rejected, *or* cancelled. - - Returns a new Promise that: - - resolves with the same values that this Promise resolves with. - - rejects with the same values that this Promise rejects with. - - is cancelled if this Promise is cancelled. - - If the value you return from the handler is a Promise: - - We wait for the Promise to resolve, but we ultimately discard the resolved value. - - If the returned Promise rejects, the Promise returned from `finally` will reject with the rejected value from the - *returned* promise. - - If the `finally` Promise is cancelled, and you returned a Promise from the handler, we cancel that Promise too. - - Otherwise, the return value from the `finally` handler is entirely discarded. - - :::note Cancellation - As of Promise v4, `Promise:finally` does not count as a consumer of the parent Promise for cancellation purposes. - This means that if all of a Promise's consumers are cancelled and the only remaining callbacks are finally handlers, - the Promise is cancelled and the finally callbacks run then and there. - - Cancellation still propagates through the `finally` Promise though: if you cancel the `finally` Promise, it can cancel - its parent Promise if it had no other consumers. Likewise, if the parent Promise is cancelled, the `finally` Promise - will also be cancelled. - ::: - - ```lua - local thing = createSomething() - - doSomethingWith(thing) - :andThen(function() - print("It worked!") - -- do something.. - end) - :catch(function() - warn("Oh no it failed!") - end) - :finally(function() - -- either way, destroy thing - - thing:Destroy() - end) - - ``` - - @param finallyHandler (status: Status) -> ...any - @return Promise<...any> -]=] -function Promise.prototype:finally(finallyHandler) - assert(finallyHandler == nil or isCallable(finallyHandler), string.format(ERROR_NON_FUNCTION, "Promise:finally")) - return self:_finally(debug.traceback(nil, 2), finallyHandler) -end - ---[=[ - Same as `andThenCall`, except for `finally`. - - Attaches a `finally` handler to this Promise that calls the given callback with the predefined arguments. - - @param callback (...: any) -> any - @param ...? any -- Additional arguments which will be passed to `callback` - @return Promise -]=] -function Promise.prototype:finallyCall(callback, ...) - assert(isCallable(callback), string.format(ERROR_NON_FUNCTION, "Promise:finallyCall")) - local length, values = pack(...) - return self:_finally(debug.traceback(nil, 2), function() - return callback(unpack(values, 1, length)) - end) -end - ---[=[ - Attaches a `finally` handler to this Promise that discards the resolved value and returns the given value from it. - - ```lua - promise:finallyReturn("some", "values") - ``` - - This is sugar for - - ```lua - promise:finally(function() - return "some", "values" - end) - ``` - - @param ... any -- Values to return from the function - @return Promise -]=] -function Promise.prototype:finallyReturn(...) - local length, values = pack(...) - return self:_finally(debug.traceback(nil, 2), function() - return unpack(values, 1, length) - end) -end - ---[=[ - Yields the current thread until the given Promise completes. Returns the Promise's status, followed by the values that the promise resolved or rejected with. - - @yields - @return Status -- The Status representing the fate of the Promise - @return ...any -- The values the Promise resolved or rejected with. -]=] -function Promise.prototype:awaitStatus() - self._unhandledRejection = false - - if self._status == Promise.Status.Started then - local thread = coroutine.running() - - self - :finally(function() - task.spawn(thread) - end) - -- The finally promise can propagate rejections, so we attach a catch handler to prevent the unhandled - -- rejection warning from appearing - :catch( - function() end - ) - - coroutine.yield() - end - - if self._status == Promise.Status.Resolved then - return self._status, unpack(self._values, 1, self._valuesLength) - elseif self._status == Promise.Status.Rejected then - return self._status, unpack(self._values, 1, self._valuesLength) - end - - return self._status -end - -local function awaitHelper(status, ...) - return status == Promise.Status.Resolved, ... -end - ---[=[ - Yields the current thread until the given Promise completes. Returns true if the Promise resolved, followed by the values that the promise resolved or rejected with. - - :::caution - If the Promise gets cancelled, this function will return `false`, which is indistinguishable from a rejection. If you need to differentiate, you should use [[Promise.awaitStatus]] instead. - ::: - - ```lua - local worked, value = getTheValue():await() - - if worked then - print("got", value) - else - warn("it failed") - end - ``` - - @yields - @return boolean -- `true` if the Promise successfully resolved - @return ...any -- The values the Promise resolved or rejected with. -]=] -function Promise.prototype:await() - return awaitHelper(self:awaitStatus()) -end - -local function expectHelper(status, ...) - if status ~= Promise.Status.Resolved then - error((...) == nil and "Expected Promise rejected with no value." or (...), 3) - end - - return ... -end - ---[=[ - Yields the current thread until the given Promise completes. Returns the values that the promise resolved with. - - ```lua - local worked = pcall(function() - print("got", getTheValue():expect()) - end) - - if not worked then - warn("it failed") - end - ``` - - This is essentially sugar for: - - ```lua - select(2, assert(promise:await())) - ``` - - **Errors** if the Promise rejects or gets cancelled. - - @error any -- Errors with the rejection value if this Promise rejects or gets cancelled. - @yields - @return ...any -- The values the Promise resolved with. -]=] -function Promise.prototype:expect() - return expectHelper(self:awaitStatus()) -end - --- Backwards compatibility -Promise.prototype.awaitValue = Promise.prototype.expect - ---[[ - Intended for use in tests. - - Similar to await(), but instead of yielding if the promise is unresolved, - _unwrap will throw. This indicates an assumption that a promise has - resolved. -]] -function Promise.prototype:_unwrap() - if self._status == Promise.Status.Started then - error("Promise has not resolved or rejected.", 2) - end - - local success = self._status == Promise.Status.Resolved - - return success, unpack(self._values, 1, self._valuesLength) -end - -function Promise.prototype:_resolve(...) - if self._status ~= Promise.Status.Started then - if Promise.is((...)) then - (...):_consumerCancelled(self) - end - return - end - - -- If the resolved value was a Promise, we chain onto it! - if Promise.is((...)) then - -- Without this warning, arguments sometimes mysteriously disappear - if select("#", ...) > 1 then - local message = string.format( - "When returning a Promise from andThen, extra arguments are " .. "discarded! See:\n\n%s", - self._source - ) - warn(message) - end - - local chainedPromise = ... - - local promise = chainedPromise:andThen(function(...) - self:_resolve(...) - end, function(...) - local maybeRuntimeError = chainedPromise._values[1] - - -- Backwards compatibility < v2 - if chainedPromise._error then - maybeRuntimeError = Error.new({ - error = chainedPromise._error, - kind = Error.Kind.ExecutionError, - context = "[No stack trace available as this Promise originated from an older version of the Promise library (< v2)]", - }) - end - - if Error.isKind(maybeRuntimeError, Error.Kind.ExecutionError) then - return self:_reject(maybeRuntimeError:extend({ - error = "This Promise was chained to a Promise that errored.", - trace = "", - context = string.format( - "The Promise at:\n\n%s\n...Rejected because it was chained to the following Promise, which encountered an error:\n", - self._source - ), - })) - end - - self:_reject(...) - end) - - if promise._status == Promise.Status.Cancelled then - self:cancel() - elseif promise._status == Promise.Status.Started then - -- Adopt ourselves into promise for cancellation propagation. - self._parent = promise - promise._consumers[self] = true - end - - return - end - - self._status = Promise.Status.Resolved - self._valuesLength, self._values = pack(...) - - -- We assume that these callbacks will not throw errors. - for _, callback in ipairs(self._queuedResolve) do - coroutine.wrap(callback)(...) - end - - self:_finalize() -end - -function Promise.prototype:_reject(...) - if self._status ~= Promise.Status.Started then - return - end - - self._status = Promise.Status.Rejected - self._valuesLength, self._values = pack(...) - - -- If there are any rejection handlers, call those! - if not isEmpty(self._queuedReject) then - -- We assume that these callbacks will not throw errors. - for _, callback in ipairs(self._queuedReject) do - coroutine.wrap(callback)(...) - end - else - -- At this point, no one was able to observe the error. - -- An error handler might still be attached if the error occurred - -- synchronously. We'll wait one tick, and if there are still no - -- observers, then we should put a message in the console. - - local err = tostring((...)) - - coroutine.wrap(function() - Promise._timeEvent:Wait() - - -- Someone observed the error, hooray! - if not self._unhandledRejection then - return - end - - -- Build a reasonable message - local message = string.format("Unhandled Promise rejection:\n\n%s\n\n%s", err, self._source) - - for _, callback in ipairs(Promise._unhandledRejectionCallbacks) do - task.spawn(callback, self, unpack(self._values, 1, self._valuesLength)) - end - - if Promise.TEST then - -- Don't spam output when we're running tests. - return - end - - warn(message) - end)() - end - - self:_finalize() -end - ---[[ - Calls any :finally handlers. We need this to be a separate method and - queue because we must call all of the finally callbacks upon a success, - failure, *and* cancellation. -]] -function Promise.prototype:_finalize() - for _, callback in ipairs(self._queuedFinally) do - -- Purposefully not passing values to callbacks here, as it could be the - -- resolved values, or rejected errors. If the developer needs the values, - -- they should use :andThen or :catch explicitly. - coroutine.wrap(callback)(self._status) - end - - self._queuedFinally = nil - self._queuedReject = nil - self._queuedResolve = nil - - -- Clear references to other Promises to allow gc - if not Promise.TEST then - self._parent = nil - self._consumers = nil - end - - task.defer(coroutine.close, self._thread) -end - ---[=[ - Chains a Promise from this one that is resolved if this Promise is already resolved, and rejected if it is not resolved at the time of calling `:now()`. This can be used to ensure your `andThen` handler occurs on the same frame as the root Promise execution. - - ```lua - doSomething() - :now() - :andThen(function(value) - print("Got", value, "synchronously.") - end) - ``` - - If this Promise is still running, Rejected, or Cancelled, the Promise returned from `:now()` will reject with the `rejectionValue` if passed, otherwise with a `Promise.Error(Promise.Error.Kind.NotResolvedInTime)`. This can be checked with [[Error.isKind]]. - - @param rejectionValue? any -- The value to reject with if the Promise isn't resolved - @return Promise -]=] -function Promise.prototype:now(rejectionValue) - local traceback = debug.traceback(nil, 2) - if self._status == Promise.Status.Resolved then - return self:_andThen(traceback, function(...) - return ... - end) - else - return Promise.reject(rejectionValue == nil and Error.new({ - kind = Error.Kind.NotResolvedInTime, - error = "This Promise was not resolved in time for :now()", - context = ":now() was called at:\n\n" .. traceback, - }) or rejectionValue) - end -end - ---[=[ - Repeatedly calls a Promise-returning function up to `times` number of times, until the returned Promise resolves. - - If the amount of retries is exceeded, the function will return the latest rejected Promise. - - ```lua - local function canFail(a, b, c) - return Promise.new(function(resolve, reject) - -- do something that can fail - - local failed, thing = doSomethingThatCanFail(a, b, c) - - if failed then - reject("it failed") - else - resolve(thing) - end - end) - end - - local MAX_RETRIES = 10 - local value = Promise.retry(canFail, MAX_RETRIES, "foo", "bar", "baz") -- args to send to canFail - ``` - - @since 3.0.0 - @param callback (...: P) -> Promise - @param times number - @param ...? P - @return Promise -]=] -function Promise.retry(callback, times, ...) - assert(isCallable(callback), "Parameter #1 to Promise.retry must be a function") - assert(type(times) == "number", "Parameter #2 to Promise.retry must be a number") - - local args, length = { ... }, select("#", ...) - - return Promise.resolve(callback(...)):catch(function(...) - if times > 0 then - return Promise.retry(callback, times - 1, unpack(args, 1, length)) - else - return Promise.reject(...) - end - end) -end - ---[=[ - Repeatedly calls a Promise-returning function up to `times` number of times, waiting `seconds` seconds between each - retry, until the returned Promise resolves. - - If the amount of retries is exceeded, the function will return the latest rejected Promise. - - @since v3.2.0 - @param callback (...: P) -> Promise - @param times number - @param seconds number - @param ...? P - @return Promise -]=] -function Promise.retryWithDelay(callback, times, seconds, ...) - assert(isCallable(callback), "Parameter #1 to Promise.retryWithDelay must be a function") - assert(type(times) == "number", "Parameter #2 (times) to Promise.retryWithDelay must be a number") - assert(type(seconds) == "number", "Parameter #3 (seconds) to Promise.retryWithDelay must be a number") - - local args, length = { ... }, select("#", ...) - - return Promise.resolve(callback(...)):catch(function(...) - if times > 0 then - Promise.delay(seconds):await() - - return Promise.retryWithDelay(callback, times - 1, seconds, unpack(args, 1, length)) - else - return Promise.reject(...) - end - end) -end - - ---[=[ - Repeatedly calls a Promise-returning function up to `times` number of times, with a variable delay between retries - based on the specified equation ("Linear", "Exponential", or "Quadratic"), until the returned Promise resolves. - - If the amount of retries is exceeded, the function will return the latest rejected Promise. - - @param callback (...: P) -> Promise - @param times number - @param seconds number - @param equation string - @param ...? P - @return Promise -]=] -function Promise.retryWithVariableDelay(callback, times, seconds, equation, ...) - assert(isCallable(callback), "Parameter #1 to Promise.retryWithVariableDelay must be a function") - assert(type(times) == "number", "Parameter #2 (times) to Promise.retryWithVariableDelay must be a number") - assert(type(seconds) == "number", "Parameter #3 (seconds) to Promise.retryWithVariableDelay must be a number") - assert(type(equation) == "string", "Parameter #3 (seconds) to Promise.retryWithVariableDelay must be a string") - - local attempts = 0 - local args, length = { ... }, select("#", ...) - - local function attempt() - attempts += 1 - return Promise.resolve(callback(unpack(args, 1, length))):catch(function(...) - if attempts < times then - - local delaySeconds = seconds - if equation == "Exponential" then - delaySeconds = seconds * math.pow(2, attempts) - elseif equation == "Quadratic" then - delaySeconds = seconds * math.pow(attempts, 2) - end - Promise.delay(delaySeconds):await() - - return attempt() - else - warn("Return reject") - return Promise.reject(...) - end - end) - end - - return Promise.new(function(resolve, reject) - attempt():andThen(resolve):catch(reject) - end) -end - ---[=[ - Converts an event into a Promise which resolves the next time the event fires. - - The optional `predicate` callback, if passed, will receive the event arguments and should return `true` or `false`, based on if this fired event should resolve the Promise or not. If `true`, the Promise resolves. If `false`, nothing happens and the predicate will be rerun the next time the event fires. - - The Promise will resolve with the event arguments. - - :::tip - This function will work given any object with a `Connect` method. This includes all Roblox events. - ::: - - ```lua - -- Creates a Promise which only resolves when `somePart` is touched - -- by a part named `"Something specific"`. - return Promise.fromEvent(somePart.Touched, function(part) - return part.Name == "Something specific" - end) - ``` - - @since 3.0.0 - @param event Event -- Any object with a `Connect` method. This includes all Roblox events. - @param predicate? (...: P) -> boolean -- A function which determines if the Promise should resolve with the given value, or wait for the next event to check again. - @return Promise

-]=] -function Promise.fromEvent(event, predicate) - predicate = predicate or function() - return true - end - - return Promise._new(debug.traceback(nil, 2), function(resolve, _, onCancel) - local connection - local shouldDisconnect = false - - local function disconnect() - connection:Disconnect() - connection = nil - end - - -- We use shouldDisconnect because if the callback given to Connect is called before - -- Connect returns, connection will still be nil. This happens with events that queue up - -- events when there's nothing connected, such as RemoteEvents - - connection = event:Connect(function(...) - local callbackValue = predicate(...) - - if callbackValue == true then - resolve(...) - - if connection then - disconnect() - else - shouldDisconnect = true - end - elseif type(callbackValue) ~= "boolean" then - error("Promise.fromEvent predicate should always return a boolean") - end - end) - - if shouldDisconnect and connection then - return disconnect() - end - - onCancel(disconnect) - end) -end - ---[=[ - Registers a callback that runs when an unhandled rejection happens. An unhandled rejection happens when a Promise - is rejected, and the rejection is not observed with `:catch`. - - The callback is called with the actual promise that rejected, followed by the rejection values. - - @since v3.2.0 - @param callback (promise: Promise, ...: any) -- A callback that runs when an unhandled rejection happens. - @return () -> () -- Function that unregisters the `callback` when called -]=] -function Promise.onUnhandledRejection(callback) - table.insert(Promise._unhandledRejectionCallbacks, callback) - - return function() - local index = table.find(Promise._unhandledRejectionCallbacks, callback) - - if index then - table.remove(Promise._unhandledRejectionCallbacks, index) - end - end -end - -return Promise :: { - Error: any, - - all: (promises: { TypedPromise }) -> TypedPromise<{ T }>, - allSettled: (promise: { TypedPromise }) -> TypedPromise<{ Status }>, - any: (promise: { TypedPromise }) -> TypedPromise, - defer: ( - executor: ( - resolve: (TReturn...) -> (), - reject: (...any) -> (), - onCancel: (abortHandler: (() -> ())?) -> boolean - ) -> () - ) -> TypedPromise, - delay: (seconds: number) -> TypedPromise, - each: ( - list: { T | TypedPromise }, - predicate: (value: T, index: number) -> TReturn | TypedPromise - ) -> TypedPromise<{ TReturn }>, - fold: ( - list: { T | TypedPromise }, - reducer: (accumulator: TReturn, value: T, index: number) -> TReturn | TypedPromise - ) -> TypedPromise, - fromEvent: ( - event: Signal, - predicate: ((TReturn...) -> boolean)? - ) -> TypedPromise, - is: (object: any) -> boolean, - new: ( - executor: ( - resolve: (TReturn...) -> (), - reject: (...any) -> (), - onCancel: (abortHandler: (() -> ())?) -> boolean - ) -> () - ) -> TypedPromise, - onUnhandledRejection: (callback: (promise: TypedPromise, ...any) -> ()) -> () -> (), - promisify: (callback: (TArgs...) -> TReturn...) -> (TArgs...) -> TypedPromise, - race: (promises: { TypedPromise }) -> TypedPromise, - reject: (...any) -> TypedPromise<...any>, - resolve: (TReturn...) -> TypedPromise, - retry: ( - callback: (TArgs...) -> TypedPromise, - times: number, - TArgs... - ) -> TypedPromise, - retryWithDelay: ( - callback: (TArgs...) -> TypedPromise, - times: number, - seconds: number, - TArgs... - ) -> TypedPromise, - retryWithVariableDelay: ( - callback: (TArgs...) -> TypedPromise, - times: number, - seconds: number, - equation: "Exponential" | "Quadratic", - TArgs... - ) -> TypedPromise, - some: (promise: { TypedPromise }, count: number) -> TypedPromise<{ T }>, - try: (callback: (TArgs...) -> TReturn..., TArgs...) -> TypedPromise, -} diff --git a/samples/Luau/ReactRobloxHostConfig.luau b/samples/Luau/ReactRobloxHostConfig.luau deleted file mode 100644 index 53ca831b4f..0000000000 --- a/samples/Luau/ReactRobloxHostConfig.luau +++ /dev/null @@ -1,1366 +0,0 @@ ---// Authored by @JSDotLua (https://github.com/jsdotlua) ---// Fetched from (https://github.com/jsdotlua/react-lua/blob/main/modules/react-roblox/src/client/ReactRobloxHostConfig.lua) ---// Licensed under the MIT License (https://github.com/jsdotlua/react-lua/blob/main/LICENSE) - ---!strict --- ROBLOX upstream: https://github.com/facebook/react/blob/8e5adfbd7e605bda9c5e96c10e015b3dc0df688e/packages/react-dom/src/client/ReactDOMHostConfig.js --- ROBLOX upstream: https://github.com/facebook/react/blob/efd8f6442d1aa7c4566fe812cba03e7e83aaccc3/packages/react-native-renderer/src/ReactNativeHostConfig.js ---[[* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow -]] --- FIXME (roblox): remove this when our unimplemented -local function unimplemented(message: string) - print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - print("UNIMPLEMENTED ERROR: " .. tostring(message)) - error("FIXME (roblox): " .. message .. " is unimplemented", 2) -end - -local CollectionService = game:GetService("CollectionService") -local LuauPolyfill = require("@pkg/@jsdotlua/luau-polyfill") -local inspect = LuauPolyfill.util.inspect -local console = require("@pkg/@jsdotlua/shared").console -local Object = LuauPolyfill.Object -local setTimeout = LuauPolyfill.setTimeout -local clearTimeout = LuauPolyfill.clearTimeout - --- local type {DOMEventName} = require(Packages.../events/DOMEventNames') --- local type {Fiber, FiberRoot} = require(Packages.react-reconciler/src/ReactInternalTypes') --- local type { --- BoundingRect, --- IntersectionObserverOptions, --- ObserveVisibleRectsCallback, --- } = require(Packages.react-reconciler/src/ReactTestSelectors') -local ReactRobloxHostTypes = require("./ReactRobloxHostTypes.roblox.lua") -type RootType = ReactRobloxHostTypes.RootType -type Container = ReactRobloxHostTypes.Container -type HostInstance = ReactRobloxHostTypes.HostInstance -type SuspenseInstance = ReactRobloxHostTypes.SuspenseInstance -type TextInstance = ReactRobloxHostTypes.TextInstance -type Props = ReactRobloxHostTypes.Props -type Type = ReactRobloxHostTypes.Type -type HostContext = ReactRobloxHostTypes.HostContext - --- local type {ReactScopeInstance} = require(Packages.shared/ReactTypes') --- local type {ReactDOMFundamentalComponentInstance} = require(Packages.../shared/ReactDOMTypes') - -local ReactRobloxComponentTree = require("./ReactRobloxComponentTree") -local precacheFiberNode = ReactRobloxComponentTree.precacheFiberNode -local uncacheFiberNode = ReactRobloxComponentTree.uncacheFiberNode -local updateFiberProps = ReactRobloxComponentTree.updateFiberProps --- local getClosestInstanceFromNode = ReactRobloxComponentTree.getClosestInstanceFromNode --- local getFiberFromScopeInstance = ReactRobloxComponentTree.getFiberFromScopeInstance --- local getInstanceFromNodeDOMTree = ReactRobloxComponentTree.getInstanceFromNode --- local isContainerMarkedAsRoot = ReactRobloxComponentTree.isContainerMarkedAsRoot - --- local {hasRole} = require(Packages../DOMAccessibilityRoles') -local ReactRobloxComponent = require("./ReactRobloxComponent") --- local createElement = ReactRobloxComponent.createElement --- local createTextNode = ReactRobloxComponent.createTextNode -local setInitialProperties = ReactRobloxComponent.setInitialProperties -local diffProperties = ReactRobloxComponent.diffProperties -local updateProperties = ReactRobloxComponent.updateProperties -local cleanupHostComponent = ReactRobloxComponent.cleanupHostComponent --- local diffHydratedProperties = ReactRobloxComponent.diffHydratedProperties --- local diffHydratedText = ReactRobloxComponent.diffHydratedText --- local trapClickOnNonInteractiveElement = ReactRobloxComponent.trapClickOnNonInteractiveElement --- local warnForUnmatchedText = ReactRobloxComponent.warnForUnmatchedText --- local warnForDeletedHydratableElement = ReactRobloxComponent.warnForDeletedHydratableElement --- local warnForDeletedHydratableText = ReactRobloxComponent.warnForDeletedHydratableText --- local warnForInsertedHydratedElement = ReactRobloxComponent.warnForInsertedHydratedElement --- local warnForInsertedHydratedText = ReactRobloxComponent.warnForInsertedHydratedText --- local {getSelectionInformation, restoreSelection} = require(Packages../ReactInputSelection') --- local setTextContent = require(Packages../setTextContent') --- local {validateDOMNesting, updatedAncestorInfo} = require(Packages../validateDOMNesting') --- local { --- isEnabled as ReactBrowserEventEmitterIsEnabled, --- setEnabled as ReactBrowserEventEmitterSetEnabled, --- } = require(Packages.../events/ReactDOMEventListener') --- local {getChildNamespace} = require(Packages.../shared/DOMNamespaces') --- local { --- ELEMENT_NODE, --- TEXT_NODE, --- COMMENT_NODE, --- DOCUMENT_NODE, --- DOCUMENT_FRAGMENT_NODE, --- } = require(Packages.../shared/HTMLNodeType') --- local dangerousStyleValue = require(Packages.../shared/dangerousStyleValue') - --- local {REACT_OPAQUE_ID_TYPE} = require(Packages.shared/ReactSymbols') --- local {retryIfBlockedOn} = require(Packages.../events/ReactDOMEventReplaying') - -local ReactFeatureFlags = require("@pkg/@jsdotlua/shared").ReactFeatureFlags --- local enableSuspenseServerRenderer = ReactFeatureFlags.enableSuspenseServerRenderer --- local enableFundamentalAPI = ReactFeatureFlags.enableFundamentalAPI -local enableCreateEventHandleAPI = ReactFeatureFlags.enableCreateEventHandleAPI --- local enableScopeAPI = ReactFeatureFlags.enableScopeAPI --- local enableEagerRootListeners = ReactFeatureFlags.enableEagerRootListeners - --- local {HostComponent, HostText} = require(Packages.react-reconciler/src/ReactWorkTags') --- local { --- listenToReactEvent, --- listenToAllSupportedEvents, --- } = require(Packages.../events/DOMPluginEventSystem') - -type Array = { [number]: T } -type Object = { [any]: any } - --- ROBLOX deviation: Moved to ReactRobloxHostTypes --- export type Type = string; --- export type Props = { --- autoFocus: boolean?, --- children: any, --- disabled: boolean?, --- hidden: boolean?, --- suppressHydrationWarning: boolean?, --- dangerouslySetInnerHTML: any, --- style: { display: string, [any]: any }?, --- bottom: number?, --- left: number?, --- right: number?, --- top: number?, --- -- ... --- [any]: any, --- }; --- export type EventTargetChildElement = { --- type: string, --- props: nil | { --- style?: { --- position?: string, --- zIndex?: number, --- bottom?: string, --- left?: string, --- right?: string, --- top?: string, --- ... --- }, --- ... --- }, --- ... --- end - --- ROBLOX deviation: Moved to ReactRobloxHostTypes --- export type SuspenseInstance = Comment & {_reactRetry?: () => void, ...} --- export type HydratableInstance = Instance | TextInstance | SuspenseInstance - --- ROBLOX deviation: Moved to ReactRobloxHostTypes --- export type PublicInstance = Element | Text --- type HostContextDev = { --- namespace: string, --- ancestorInfo: any, --- -- ... --- [any]: any, --- } --- type HostContextProd = string --- export type HostContext = HostContextDev | HostContextProd - --- export type UpdatePayload = Array --- ROBLOX FIXME: cannot create type equal to void --- export type ChildSet = void; -- Unused --- export type TimeoutHandle = TimeoutID --- export type NoTimeout = -1 --- export type RendererInspectionConfig = $ReadOnly<{or}> - --- export opaque type OpaqueIDType = --- | string --- | { --- toString: () => string | void, --- valueOf: () => string | void, --- end - --- type SelectionInformation = {| --- focusedElem: nil | HTMLElement, --- selectionRange: mixed, --- |} - --- local SUPPRESS_HYDRATION_WARNING --- if __DEV__) --- SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning' --- end - --- local SUSPENSE_START_DATA = '$' --- local SUSPENSE_END_DATA = '/$' --- local SUSPENSE_PENDING_START_DATA = '$?' --- local SUSPENSE_FALLBACK_START_DATA = '$!' - --- local STYLE = 'style' - --- local eventsEnabled: boolean? = nil --- local selectionInformation: nil | SelectionInformation = nil - --- function shouldAutoFocusHostComponent(type: string, props: Props): boolean { --- switch (type) --- case 'button': --- case 'input': --- case 'select': --- case 'textarea': --- return !!props.autoFocus --- end --- return false --- end - --- ROBLOX deviation: Use GetDescendants rather than recursion -local function recursivelyUncacheFiberNode(node: HostInstance) - -- ROBLOX https://jira.rbx.com/browse/LUAFDN-713: Tables are somehow ending up - -- in this function that expects Instances. In that case, we won't be able to - -- iterate through its descendants. - if typeof(node :: any) ~= "Instance" then - return - end - - uncacheFiberNode(node) - - for _, child in node:GetDescendants() do - uncacheFiberNode(child) - end -end - -local exports: { [any]: any } = {} -Object.assign( - exports, - require("@pkg/@jsdotlua/shared").ReactFiberHostConfig.WithNoPersistence -) - -exports.getRootHostContext = function(rootContainerInstance: Container): HostContext - -- ROBLOX deviation: This is a lot of HTML-DOM specific logic; I'm not clear on - -- whether there'll be an equivalent of `namespaceURI` for our use cases, but - -- we may want to provide other kinds of context for host objects. - - -- For now, as a guess, we'll return the kind of instance we're attached to - return rootContainerInstance.ClassName - - -- local type - -- local namespace - -- local nodeType = rootContainerInstance.nodeType - -- switch (nodeType) - -- case DOCUMENT_NODE: - -- case DOCUMENT_FRAGMENT_NODE: { - -- type = nodeType == DOCUMENT_NODE ? '#document' : '#fragment' - -- local root = (rootContainerInstance: any).documentElement - -- namespace = root ? root.namespaceURI : getChildNamespace(null, '') - -- break - -- end - -- default: { - -- local container: any = - -- nodeType == COMMENT_NODE - -- ? rootContainerInstance.parentNode - -- : rootContainerInstance - -- local ownNamespace = container.namespaceURI or nil - -- type = container.tagName - -- namespace = getChildNamespace(ownNamespace, type) - -- break - -- end - -- end - -- if _G.__DEV__ then - -- local validatedTag = type.toLowerCase() - -- local ancestorInfo = updatedAncestorInfo(null, validatedTag) - -- return {namespace, ancestorInfo} - -- end - -- return namespace -end - -exports.getChildHostContext = function( - parentHostContext: HostContext, - type: string, - rootContainerInstance: Container -): HostContext - -- ROBLOX deviation: unclear on the purpose here just yet, might be fine to - -- just return parent's hostContext for now - return parentHostContext - -- if _G.__DEV__ then - -- local parentHostContextDev = ((parentHostContext: any): HostContextDev) - -- local namespace = getChildNamespace(parentHostContextDev.namespace, type) - -- local ancestorInfo = updatedAncestorInfo( - -- parentHostContextDev.ancestorInfo, - -- type, - -- ) - -- return {namespace, ancestorInfo} - -- end - -- local parentNamespace = ((parentHostContext: any): HostContextProd) - -- return getChildNamespace(parentNamespace, type) -end - -exports.getPublicInstance = function(instance: Instance): any - return instance -end - -exports.prepareForCommit = function(containerInfo: Container): Object? - -- eventsEnabled = ReactBrowserEventEmitterIsEnabled() - -- selectionInformation = getSelectionInformation() - local activeInstance = nil - if enableCreateEventHandleAPI then - unimplemented("enableCreateEventHandleAPI") - -- local focusedElem = selectionInformation.focusedElem - -- if focusedElem ~= nil then - -- activeInstance = getClosestInstanceFromNode(focusedElem) - -- end - end - -- ReactBrowserEventEmitterSetEnabled(false) - return activeInstance -end - -exports.beforeActiveInstanceBlur = function() - if enableCreateEventHandleAPI then - unimplemented("enableCreateEventHandleAPI") - -- ReactBrowserEventEmitterSetEnabled(true) - -- dispatchBeforeDetachedBlur((selectionInformation: any).focusedElem) - -- ReactBrowserEventEmitterSetEnabled(false) - end -end - -exports.afterActiveInstanceBlur = function() - if enableCreateEventHandleAPI then - unimplemented("enableCreateEventHandleAPI") - -- ReactBrowserEventEmitterSetEnabled(true) - -- dispatchAfterDetachedBlur((selectionInformation: any).focusedElem) - -- ReactBrowserEventEmitterSetEnabled(false) - end -end - -exports.resetAfterCommit = function(containerInfo: Container) - -- warn("Skip unimplemented: resetAfterCommit") - -- restoreSelection(selectionInformation) - -- ReactBrowserEventEmitterSetEnabled(eventsEnabled) - -- eventsEnabled = nil - -- selectionInformation = nil -end - -exports.createInstance = function( - type_: string, - props: Props, - rootContainerInstance: Container, - hostContext: HostContext, - internalInstanceHandle: Object -): HostInstance - -- local hostKey = virtualNode.hostKey - - local domElement = Instance.new(type_) - -- ROBLOX deviation: compatibility with old Roact where instances have their name - -- set to the key value - if internalInstanceHandle.key then - domElement.Name = internalInstanceHandle.key - else - local currentHandle = internalInstanceHandle.return_ - while currentHandle do - if currentHandle.key then - domElement.Name = currentHandle.key - break - end - currentHandle = currentHandle.return_ - end - end - - precacheFiberNode(internalInstanceHandle, domElement) - updateFiberProps(domElement, props) - - -- TODO: Support refs (does that actually happen here, or later?) - -- applyRef(element.props[Ref], instance) - - -- Will have to be managed outside of createInstance - -- if virtualNode.eventManager ~= nil then - -- virtualNode.eventManager:resume() - -- end - - return domElement - - -- return Instance.new("Frame") - -- local parentNamespace: string - -- if __DEV__) - -- -- TODO: take namespace into account when validating. - -- local hostContextDev = ((hostContext: any): HostContextDev) - -- validateDOMNesting(type, nil, hostContextDev.ancestorInfo) - -- if - -- typeof props.children == 'string' or - -- typeof props.children == 'number' - -- ) - -- local string = '' + props.children - -- local ownAncestorInfo = updatedAncestorInfo( - -- hostContextDev.ancestorInfo, - -- type, - -- ) - -- validateDOMNesting(null, string, ownAncestorInfo) - -- end - -- parentNamespace = hostContextDev.namespace - -- } else { - -- parentNamespace = ((hostContext: any): HostContextProd) - -- end - -- local domElement: Instance = createElement( - -- type, - -- props, - -- rootContainerInstance, - -- parentNamespace, - -- ) -end - -exports.appendInitialChild = function(parentInstance: Instance, child: Instance) - -- ROBLOX deviation: Establish hierarchy with Parent property - child.Parent = parentInstance -end - -exports.finalizeInitialChildren = function( - domElement: HostInstance, - type_: string, - props: Props, - rootContainerInstance: Container, - hostContext: HostContext -): boolean - setInitialProperties(domElement, type_, props, rootContainerInstance) - return false - -- return shouldAutoFocusHostComponent(type_, props) -end - -local function prepareUpdate( - domElement: Instance, - type_: string, - oldProps: Props, - newProps: Props, - rootContainerInstance: Container, - hostContext: HostContext -): nil | Array - -- if _G.__DEV__ then - -- local hostContextDev = ((hostContext: any): HostContextDev) - -- if - -- typeof newProps.children ~= typeof oldProps.children and - -- (typeof newProps.children == 'string' or - -- typeof newProps.children == 'number') - -- ) - -- local string = '' + newProps.children - -- local ownAncestorInfo = updatedAncestorInfo( - -- hostContextDev.ancestorInfo, - -- type, - -- ) - -- validateDOMNesting(null, string, ownAncestorInfo) - -- end - -- end - return diffProperties(domElement, type_, oldProps, newProps, rootContainerInstance) -end -exports.prepareUpdate = prepareUpdate - -exports.shouldSetTextContent = function(_type: string, _props: Props): boolean - -- ROBLOX deviation: Ignore TextInstance logic, which isn't applicable to Roblox - return false - -- return ( - -- type == 'textarea' or - -- type == 'option' or - -- type == 'noscript' or - -- typeof props.children == 'string' or - -- typeof props.children == 'number' or - -- (typeof props.dangerouslySetInnerHTML == 'table’' and - -- props.dangerouslySetInnerHTML ~= nil and - -- props.dangerouslySetInnerHTML.__html ~= nil) - -- ) -end - --- ROBLOX deviation: Text nodes aren't supported in Roblox renderer, so error so that tests fail immediately -exports.createTextInstance = function( - text: string, - rootContainerInstance: Container, - hostContext: HostContext, - internalInstanceHandle: Object -): any - unimplemented("createTextInstance") - return nil -end - -exports.isPrimaryRenderer = true -exports.warnsIfNotActing = true --- This initialization code may run even on server environments --- if a component just imports ReactDOM (e.g. for findDOMNode). --- Some environments might not have setTimeout or clearTimeout. --- ROBLOX deviation: We're only dealing with client right now, so these always populate -exports.scheduleTimeout = setTimeout -exports.cancelTimeout = clearTimeout -exports.noTimeout = -1 - --- ------------------- --- Mutation --- ------------------- - -exports.supportsMutation = true - -exports.commitMount = function( - domElement: Instance, - type: string, - newProps: Props, - internalInstanceHandle: Object -) - unimplemented("commitMount") - -- -- Despite the naming that might imply otherwise, this method only - -- -- fires if there is an `Update` effect scheduled during mounting. - -- -- This happens if `finalizeInitialChildren` returns `true` (which it - -- -- does to implement the `autoFocus` attribute on the client). But - -- -- there are also other cases when this might happen (such as patching - -- -- up text content during hydration mismatch). So we'll check this again. - -- if shouldAutoFocusHostComponent(type, newProps)) - -- ((domElement: any): - -- | HTMLButtonElement - -- | HTMLInputElement - -- | HTMLSelectElement - -- | HTMLTextAreaElement).focus() - -- end -end - -exports.commitUpdate = function( - domElement: Instance, - updatePayload: Array, - type_: string, - oldProps: Props, - newProps: Props, - internalInstanceHandle: Object -) - -- Update the props handle so that we know which props are the ones with - -- with current event handlers. - updateFiberProps(domElement, newProps) - -- Apply the diff to the DOM node. - updateProperties(domElement, updatePayload, oldProps) -end - --- ROBLOX deviation: Ignore TextInstance logic, which isn't applicable to Roblox --- exports.resetTextContent(domElement: Instance): void { --- setTextContent(domElement, '') --- end - --- ROBLOX deviation: Ignore TextInstance logic, which isn't applicable to Roblox --- exports.commitTextUpdate( --- textInstance: TextInstance, --- oldText: string, --- newText: string, --- ): void { --- textInstance.nodeValue = newText --- end - -local function checkTags(instance: Instance) - if typeof(instance :: any) ~= "Instance" then - console.warn("Could not check tags on non-instance %s.", inspect(instance)) - return - end - if not instance:IsDescendantOf(game) then - if #CollectionService:GetTags(instance) > 0 then - console.warn( - 'Tags applied to orphaned %s "%s" cannot be accessed via' - .. " CollectionService:GetTagged. If you're relying on tag" - .. " behavior in a unit test, consider mounting your test " - .. "root into the DataModel.", - instance.ClassName, - instance.Name - ) - end - end -end - -exports.appendChild = function(parentInstance: Instance, child: Instance) - -- ROBLOX deviation: Roblox's DOM is based on child->parent references - child.Parent = parentInstance - -- parentInstance.appendChild(child) - if _G.__DEV__ then - checkTags(child) - end -end - -exports.appendChildToContainer = function(container: Container, child: Instance) - -- ROBLOX TODO: Some of this logic may come back; for now, keep it simple - local parentNode = container - exports.appendChild(parentNode, child) - - -- if container.nodeType == COMMENT_NODE) - -- parentNode = (container.parentNode: any) - -- parentNode.insertBefore(child, container) - -- } else { - -- parentNode = container - -- parentNode.appendChild(child) - -- end - -- -- This container might be used for a portal. - -- -- If something inside a portal is clicked, that click should bubble - -- -- through the React tree. However, on Mobile Safari the click would - -- -- never bubble through the *DOM* tree unless an ancestor with onclick - -- -- event exists. So we wouldn't see it and dispatch it. - -- -- This is why we ensure that non React root containers have inline onclick - -- -- defined. - -- -- https://github.com/facebook/react/issues/11918 - -- local reactRootContainer = container._reactRootContainer - -- if - -- reactRootContainer == nil and parentNode.onclick == nil - -- then - -- -- TODO: This cast may not be sound for SVG, MathML or custom elements. - -- trapClickOnNonInteractiveElement(((parentNode: any): HTMLElement)) - -- end -end - -exports.insertBefore = - function(parentInstance: Instance, child: Instance, _beforeChild: Instance) - -- ROBLOX deviation: Roblox's DOM is based on child->parent references - child.Parent = parentInstance - -- parentInstance.insertBefore(child, beforeChild) - if _G.__DEV__ then - checkTags(child) - end - end - -exports.insertInContainerBefore = - function(container: Container, child: Instance, beforeChild: Instance) - -- ROBLOX deviation: use our container definition - local parentNode = container - exports.insertBefore(parentNode, child, beforeChild) - -- if container.nodeType == COMMENT_NODE) - -- (container.parentNode: any).insertBefore(child, beforeChild) - -- } else { - -- container.insertBefore(child, beforeChild) - -- end - end - --- function createEvent(type: DOMEventName, bubbles: boolean): Event { --- local event = document.createEvent('Event') --- event.initEvent(((type: any): string), bubbles, false) --- return event --- end - --- function dispatchBeforeDetachedBlur(target: HTMLElement): void { --- if enableCreateEventHandleAPI) --- local event = createEvent('beforeblur', true) --- -- Dispatch "beforeblur" directly on the target, --- -- so it gets picked up by the event system and --- -- can propagate through the React internal tree. --- target.dispatchEvent(event) --- end --- end - --- function dispatchAfterDetachedBlur(target: HTMLElement): void { --- if enableCreateEventHandleAPI) --- local event = createEvent('afterblur', false) --- -- So we know what was detached, make the relatedTarget the --- -- detached target on the "afterblur" event. --- (event: any).relatedTarget = target --- -- Dispatch the event on the document. --- document.dispatchEvent(event) --- end --- end - -exports.removeChild = function(_parentInstance: Instance, child: Instance) - recursivelyUncacheFiberNode(child) - -- ROBLOX deviation: The roblox renderer tracks bindings and event managers - -- for instances, so make sure we clean those up when we remove the instance - cleanupHostComponent(child) - -- ROBLOX deviation: Roblox's DOM is based on child->parent references - child.Parent = nil - -- parentInstance.removeChild(child) - -- ROBLOX deviation: Guard against misuse by locking parent and forcing external cleanup via Destroy - child:Destroy() -end - -exports.removeChildFromContainer = function(_container: Container, child: Instance) - -- ROBLOX deviation: Containers don't have special behavior and comment nodes - -- have no datamodel equivalent, so just forward to the removeChild logic - exports.removeChild(_container, child) - -- if container.nodeType == COMMENT_NODE) - -- (container.parentNode: any).removeChild(child) - -- } else { - -- container.removeChild(child) - -- end -end - -exports.clearSuspenseBoundary = - function(parentInstance: Instance, suspenseInstance: SuspenseInstance) - -- ROBLOX FIXME: this is a major thing we need to fix for Suspense to work as a feature - unimplemented("clearSuspenseBoundary") - -- local node = suspenseInstance - -- -- Delete all nodes within this suspense boundary. - -- -- There might be nested nodes so we need to keep track of how - -- -- deep we are and only break out when we're back on top. - -- local depth = 0 - -- do { - -- local nextNode = node.nextSibling - -- parentInstance.removeChild(node) - -- if nextNode and nextNode.nodeType == COMMENT_NODE) - -- local data = ((nextNode: any).data: string) - -- if data == SUSPENSE_END_DATA) - -- if depth == 0) - -- parentInstance.removeChild(nextNode) - -- -- Retry if any event replaying was blocked on this. - -- retryIfBlockedOn(suspenseInstance) - -- return - -- } else { - -- depth-- - -- end - -- } else if - -- data == SUSPENSE_START_DATA or - -- data == SUSPENSE_PENDING_START_DATA or - -- data == SUSPENSE_FALLBACK_START_DATA - -- ) - -- depth++ - -- end - -- end - -- node = nextNode - -- } while (node) - -- -- TODO: Warn, we didn't find the end comment boundary. - -- -- Retry if any event replaying was blocked on this. - -- retryIfBlockedOn(suspenseInstance) - end - -exports.clearSuspenseBoundaryFromContainer = - function(container: Container, suspenseInstance: SuspenseInstance) - -- ROBLOX FIXME: this is a major thing we need to fix for Suspense to work as a feature - unimplemented("clearSuspenseBoundaryFromContainer") - -- if container.nodeType == COMMENT_NODE) - -- clearSuspenseBoundary((container.parentNode: any), suspenseInstance) - -- } else if container.nodeType == ELEMENT_NODE) - -- clearSuspenseBoundary((container: any), suspenseInstance) - -- } else { - -- -- Document nodes should never contain suspense boundaries. - -- end - -- -- Retry if any event replaying was blocked on this. - -- retryIfBlockedOn(container) - end - -exports.hideInstance = function(instance: Instance) - unimplemented("hideInstance") - -- -- TODO: Does this work for all element types? What about MathML? Should we - -- -- pass host context to this method? - -- instance = ((instance: any): HTMLElement) - -- local style = instance.style - -- if typeof style.setProperty == 'function') - -- style.setProperty('display', 'none', 'important') - -- } else { - -- style.display = 'none' - -- end -end - --- ROBLOX deviation: error on TextInstance logic, which isn't applicable to Roblox -exports.hideTextInstance = function(textInstance: TextInstance): () - unimplemented("hideTextInstance") - -- textInstance.nodeValue = '' -end - -exports.unhideInstance = function(instance: Instance, props: Props) - unimplemented("unhideInstance") - -- instance = ((instance: any): HTMLElement) - -- local styleProp = props[STYLE] - -- local display = - -- styleProp ~= undefined and - -- styleProp ~= nil and - -- styleProp.hasOwnProperty('display') - -- ? styleProp.display - -- : nil - -- instance.style.display = dangerousStyleValue('display', display) -end - --- ROBLOX deviation: error on TextInstance logic, which isn't applicable to Roblox -exports.unhideTextInstance = function(textInstance: TextInstance, text: string): () - unimplemented("unhideTextInstance") - -- textInstance.nodeValue = text -end - -exports.clearContainer = function(container: Container) - -- ROBLOX deviation: with Roblox, we can simply enumerate and remove the children - local parentInstance = container - for _, child in parentInstance:GetChildren() do - exports.removeChild(parentInstance, child) - end - -- if container.nodeType == ELEMENT_NODE) - -- ((container: any): Element).textContent = '' - -- } else if container.nodeType == DOCUMENT_NODE) - -- local body = ((container: any): Document).body - -- if body ~= nil) - -- body.textContent = '' - -- end - -- end -end - --- -- ------------------- --- -- Hydration --- -- ------------------- - --- export local supportsHydration = true - --- exports.canHydrateInstance( --- instance: HydratableInstance, --- type: string, --- props: Props, --- ): nil | Instance { --- if --- instance.nodeType ~= ELEMENT_NODE or --- type.toLowerCase() ~= instance.nodeName.toLowerCase() --- ) --- return nil --- end --- -- This has now been refined to an element node. --- return ((instance: any): Instance) --- end - --- exports.canHydrateTextInstance( --- instance: HydratableInstance, --- text: string, --- ): nil | TextInstance { --- if text == '' or instance.nodeType ~= TEXT_NODE) --- -- Empty strings are not parsed by HTML so there won't be a correct match here. --- return nil --- end --- -- This has now been refined to a text node. --- return ((instance: any): TextInstance) --- end - --- exports.canHydrateSuspenseInstance( --- instance: HydratableInstance, --- ): nil | SuspenseInstance { --- if instance.nodeType ~= COMMENT_NODE) --- -- Empty strings are not parsed by HTML so there won't be a correct match here. --- return nil --- end --- -- This has now been refined to a suspense node. --- return ((instance: any): SuspenseInstance) --- end - --- exports.isSuspenseInstanceFallback(instance: SuspenseInstance) --- return instance.data == SUSPENSE_FALLBACK_START_DATA --- end - --- exports.registerSuspenseInstanceRetry( --- instance: SuspenseInstance, --- callback: () => void, --- ) --- instance._reactRetry = callback --- end - --- function getNextHydratable(node) --- -- Skip non-hydratable nodes. --- for (; node ~= nil; node = node.nextSibling) --- local nodeType = node.nodeType --- if nodeType == ELEMENT_NODE or nodeType == TEXT_NODE) --- break --- end --- if enableSuspenseServerRenderer) --- if nodeType == COMMENT_NODE) --- local nodeData = (node: any).data --- if --- nodeData == SUSPENSE_START_DATA or --- nodeData == SUSPENSE_FALLBACK_START_DATA or --- nodeData == SUSPENSE_PENDING_START_DATA --- ) --- break --- end --- end --- end --- end --- return (node: any) --- end - --- exports.getNextHydratableSibling( --- instance: HydratableInstance, --- ): nil | HydratableInstance { --- return getNextHydratable(instance.nextSibling) --- end - --- exports.getFirstHydratableChild( --- parentInstance: Container | Instance, --- ): nil | HydratableInstance { --- return getNextHydratable(parentInstance.firstChild) --- end - --- exports.hydrateInstance( --- instance: Instance, --- type: string, --- props: Props, --- rootContainerInstance: Container, --- hostContext: HostContext, --- internalInstanceHandle: Object, --- ): nil | Array { --- precacheFiberNode(internalInstanceHandle, instance) --- -- TODO: Possibly defer this until the commit phase where all the events --- -- get attached. --- updateFiberProps(instance, props) --- local parentNamespace: string --- if __DEV__) --- local hostContextDev = ((hostContext: any): HostContextDev) --- parentNamespace = hostContextDev.namespace --- } else { --- parentNamespace = ((hostContext: any): HostContextProd) --- end --- return diffHydratedProperties( --- instance, --- type, --- props, --- parentNamespace, --- rootContainerInstance, --- ) --- end - --- exports.hydrateTextInstance( --- textInstance: TextInstance, --- text: string, --- internalInstanceHandle: Object, --- ): boolean { --- precacheFiberNode(internalInstanceHandle, textInstance) --- return diffHydratedText(textInstance, text) --- end - --- exports.hydrateSuspenseInstance( --- suspenseInstance: SuspenseInstance, --- internalInstanceHandle: Object, --- ) --- precacheFiberNode(internalInstanceHandle, suspenseInstance) --- end - --- exports.getNextHydratableInstanceAfterSuspenseInstance( --- suspenseInstance: SuspenseInstance, --- ): nil | HydratableInstance { --- local node = suspenseInstance.nextSibling --- -- Skip past all nodes within this suspense boundary. --- -- There might be nested nodes so we need to keep track of how --- -- deep we are and only break out when we're back on top. --- local depth = 0 --- while (node) --- if node.nodeType == COMMENT_NODE) --- local data = ((node: any).data: string) --- if data == SUSPENSE_END_DATA) --- if depth == 0) --- return getNextHydratableSibling((node: any)) --- } else { --- depth-- --- end --- } else if --- data == SUSPENSE_START_DATA or --- data == SUSPENSE_FALLBACK_START_DATA or --- data == SUSPENSE_PENDING_START_DATA --- ) --- depth++ --- end --- end --- node = node.nextSibling --- end --- -- TODO: Warn, we didn't find the end comment boundary. --- return nil --- end - --- -- Returns the SuspenseInstance if this node is a direct child of a --- -- SuspenseInstance. I.e. if its previous sibling is a Comment with --- -- SUSPENSE_x_START_DATA. Otherwise, nil. --- exports.getParentSuspenseInstance( --- targetInstance: Node, --- ): nil | SuspenseInstance { --- local node = targetInstance.previousSibling --- -- Skip past all nodes within this suspense boundary. --- -- There might be nested nodes so we need to keep track of how --- -- deep we are and only break out when we're back on top. --- local depth = 0 --- while (node) --- if node.nodeType == COMMENT_NODE) --- local data = ((node: any).data: string) --- if --- data == SUSPENSE_START_DATA or --- data == SUSPENSE_FALLBACK_START_DATA or --- data == SUSPENSE_PENDING_START_DATA --- ) --- if depth == 0) --- return ((node: any): SuspenseInstance) --- } else { --- depth-- --- end --- } else if data == SUSPENSE_END_DATA) --- depth++ --- end --- end --- node = node.previousSibling --- end --- return nil --- end - --- exports.commitHydratedContainer(container: Container): void { --- -- Retry if any event replaying was blocked on this. --- retryIfBlockedOn(container) --- end - --- exports.commitHydratedSuspenseInstance( --- suspenseInstance: SuspenseInstance, --- ): void { --- -- Retry if any event replaying was blocked on this. --- retryIfBlockedOn(suspenseInstance) --- end - --- exports.didNotMatchHydratedContainerTextInstance( --- parentContainer: Container, --- textInstance: TextInstance, --- text: string, --- ) --- if __DEV__) --- warnForUnmatchedText(textInstance, text) --- end --- end - --- exports.didNotMatchHydratedTextInstance( --- parentType: string, --- parentProps: Props, --- parentInstance: Instance, --- textInstance: TextInstance, --- text: string, --- ) --- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) --- warnForUnmatchedText(textInstance, text) --- end --- end - --- exports.didNotHydrateContainerInstance( --- parentContainer: Container, --- instance: HydratableInstance, --- ) --- if __DEV__) --- if instance.nodeType == ELEMENT_NODE) --- warnForDeletedHydratableElement(parentContainer, (instance: any)) --- } else if instance.nodeType == COMMENT_NODE) --- -- TODO: warnForDeletedHydratableSuspenseBoundary --- } else { --- warnForDeletedHydratableText(parentContainer, (instance: any)) --- end --- end --- end - --- exports.didNotHydrateInstance( --- parentType: string, --- parentProps: Props, --- parentInstance: Instance, --- instance: HydratableInstance, --- ) --- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) --- if instance.nodeType == ELEMENT_NODE) --- warnForDeletedHydratableElement(parentInstance, (instance: any)) --- } else if instance.nodeType == COMMENT_NODE) --- -- TODO: warnForDeletedHydratableSuspenseBoundary --- } else { --- warnForDeletedHydratableText(parentInstance, (instance: any)) --- end --- end --- end - --- exports.didNotFindHydratableContainerInstance( --- parentContainer: Container, --- type: string, --- props: Props, --- ) --- if __DEV__) --- warnForInsertedHydratedElement(parentContainer, type, props) --- end --- end - --- exports.didNotFindHydratableContainerTextInstance( --- parentContainer: Container, --- text: string, --- ) --- if __DEV__) --- warnForInsertedHydratedText(parentContainer, text) --- end --- end - --- exports.didNotFindHydratableContainerSuspenseInstance( --- parentContainer: Container, --- ) --- if __DEV__) --- -- TODO: warnForInsertedHydratedSuspense(parentContainer) --- end --- end - --- exports.didNotFindHydratableInstance( --- parentType: string, --- parentProps: Props, --- parentInstance: Instance, --- type: string, --- props: Props, --- ) --- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) --- warnForInsertedHydratedElement(parentInstance, type, props) --- end --- end - --- exports.didNotFindHydratableTextInstance( --- parentType: string, --- parentProps: Props, --- parentInstance: Instance, --- text: string, --- ) --- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) --- warnForInsertedHydratedText(parentInstance, text) --- end --- end - --- exports.didNotFindHydratableSuspenseInstance( --- parentType: string, --- parentProps: Props, --- parentInstance: Instance, --- ) --- if __DEV__ and parentProps[SUPPRESS_HYDRATION_WARNING] ~= true) --- -- TODO: warnForInsertedHydratedSuspense(parentInstance) --- end --- end - --- exports.getFundamentalComponentInstance( --- fundamentalInstance: ReactDOMFundamentalComponentInstance, --- ): Instance { --- if enableFundamentalAPI) --- local {currentFiber, impl, props, state} = fundamentalInstance --- local instance = impl.getInstance(null, props, state) --- precacheFiberNode(currentFiber, instance) --- return instance --- end --- -- Because of the flag above, this gets around the Flow error --- return (null: any) --- end - --- exports.mountFundamentalComponent( --- fundamentalInstance: ReactDOMFundamentalComponentInstance, --- ): void { --- if enableFundamentalAPI) --- local {impl, instance, props, state} = fundamentalInstance --- local onMount = impl.onMount --- if onMount ~= undefined) --- onMount(null, instance, props, state) --- end --- end --- end - --- exports.shouldUpdateFundamentalComponent( --- fundamentalInstance: ReactDOMFundamentalComponentInstance, --- ): boolean { --- if enableFundamentalAPI) --- local {impl, prevProps, props, state} = fundamentalInstance --- local shouldUpdate = impl.shouldUpdate --- if shouldUpdate ~= undefined) --- return shouldUpdate(null, prevProps, props, state) --- end --- end --- return true --- end - --- exports.updateFundamentalComponent( --- fundamentalInstance: ReactDOMFundamentalComponentInstance, --- ): void { --- if enableFundamentalAPI) --- local {impl, instance, prevProps, props, state} = fundamentalInstance --- local onUpdate = impl.onUpdate --- if onUpdate ~= undefined) --- onUpdate(null, instance, prevProps, props, state) --- end --- end --- end - --- exports.unmountFundamentalComponent( --- fundamentalInstance: ReactDOMFundamentalComponentInstance, --- ): void { --- if enableFundamentalAPI) --- local {impl, instance, props, state} = fundamentalInstance --- local onUnmount = impl.onUnmount --- if onUnmount ~= undefined) --- onUnmount(null, instance, props, state) --- end --- end --- end - --- exports.getInstanceFromNode(node: HTMLElement): nil | Object { --- return getClosestInstanceFromNode(node) or nil --- end - --- local clientId: number = 0 --- exports.makeClientId(): OpaqueIDType { --- return 'r:' + (clientId++).toString(36) --- end - --- exports.makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType { --- local id = 'r:' + (clientId++).toString(36) --- return { --- toString() --- warnOnAccessInDEV() --- return id --- }, --- valueOf() --- warnOnAccessInDEV() --- return id --- }, --- end --- end - --- exports.isOpaqueHydratingObject(value: mixed): boolean { --- return ( --- value ~= nil and --- typeof value == 'table’' and --- value.$$typeof == REACT_OPAQUE_ID_TYPE --- ) --- end - --- exports.makeOpaqueHydratingObject( --- attemptToReadValue: () => void, --- ): OpaqueIDType { --- return { --- $$typeof: REACT_OPAQUE_ID_TYPE, --- toString: attemptToReadValue, --- valueOf: attemptToReadValue, --- end --- end - -exports.preparePortalMount = function(portalInstance: Instance): () - -- ROBLOX TODO: Revisit this logic and see if any of it applies - -- if enableEagerRootListeners then - -- listenToAllSupportedEvents(portalInstance) - -- else - -- listenToReactEvent('onMouseEnter', portalInstance) - -- end -end - --- exports.prepareScopeUpdate( --- scopeInstance: ReactScopeInstance, --- internalInstanceHandle: Object, --- ): void { --- if enableScopeAPI) --- precacheFiberNode(internalInstanceHandle, scopeInstance) --- end --- end - --- exports.getInstanceFromScope( --- scopeInstance: ReactScopeInstance, --- ): nil | Object { --- if enableScopeAPI) --- return getFiberFromScopeInstance(scopeInstance) --- end --- return nil --- end - --- export local supportsTestSelectors = true - --- exports.findFiberRoot(node: Instance): nil | FiberRoot { --- local stack = [node] --- local index = 0 --- while (index < stack.length) --- local current = stack[index++] --- if isContainerMarkedAsRoot(current)) --- return ((getInstanceFromNodeDOMTree(current): any): FiberRoot) --- end --- stack.push(...current.children) --- end --- return nil --- end - --- exports.getBoundingRect(node: Instance): BoundingRect { --- local rect = node.getBoundingClientRect() --- return { --- x: rect.left, --- y: rect.top, --- width: rect.width, --- height: rect.height, --- end --- end - --- exports.matchAccessibilityRole(node: Instance, role: string): boolean { --- if hasRole(node, role)) --- return true --- end - --- return false --- end - --- exports.getTextContent(fiber: Fiber): string | nil { --- switch (fiber.tag) --- case HostComponent: --- local textContent = '' --- local childNodes = fiber.stateNode.childNodes --- for (local i = 0; i < childNodes.length; i++) --- local childNode = childNodes[i] --- if childNode.nodeType == Node.TEXT_NODE) --- textContent += childNode.textContent --- end --- end --- return textContent --- case HostText: --- return fiber.stateNode.textContent --- end - --- return nil --- end - --- exports.isHiddenSubtree(fiber: Fiber): boolean { --- return fiber.tag == HostComponent and fiber.memoizedProps.hidden == true --- end - --- exports.setFocusIfFocusable(node: Instance): boolean { --- -- The logic for determining if an element is focusable is kind of complex, --- -- and since we want to actually change focus anyway- we can just skip it. --- -- Instead we'll just listen for a "focus" event to verify that focus was set. --- -- --- -- We could compare the node to document.activeElement after focus, --- -- but this would not handle the case where application code managed focus to automatically blur. --- local didFocus = false --- local handleFocus = () => { --- didFocus = true --- end - --- local element = ((node: any): HTMLElement) --- try { --- element.addEventListener('focus', handleFocus) --- (element.focus or HTMLElement.prototype.focus).call(element) --- } finally { --- element.removeEventListener('focus', handleFocus) --- end - --- return didFocus --- end - --- type RectRatio = { --- ratio: number, --- rect: BoundingRect, --- end - --- exports.setupIntersectionObserver( --- targets: Array, --- callback: ObserveVisibleRectsCallback, --- options?: IntersectionObserverOptions, --- ): {| --- disconnect: () => void, --- observe: (instance: Instance) => void, --- unobserve: (instance: Instance) => void, --- |} { --- local rectRatioCache: Map = new Map() --- targets.forEach(target => { --- rectRatioCache.set(target, { --- rect: getBoundingRect(target), --- ratio: 0, --- }) --- }) - --- local handleIntersection = (entries: Array) => { --- entries.forEach(entry => { --- local {boundingClientRect, intersectionRatio, target} = entry --- rectRatioCache.set(target, { --- rect: { --- x: boundingClientRect.left, --- y: boundingClientRect.top, --- width: boundingClientRect.width, --- height: boundingClientRect.height, --- }, --- ratio: intersectionRatio, --- }) --- }) - --- callback(Array.from(rectRatioCache.values())) --- end - --- local observer = new IntersectionObserver(handleIntersection, options) --- targets.forEach(target => { --- observer.observe((target: any)) --- }) - --- return { --- disconnect: () => observer.disconnect(), --- observe: target => { --- rectRatioCache.set(target, { --- rect: getBoundingRect(target), --- ratio: 0, --- }) --- observer.observe((target: any)) --- }, --- unobserve: target => { --- rectRatioCache.delete(target) --- observer.unobserve((target: any)) --- }, --- end --- end - -return exports diff --git a/samples/Luau/Replicator.luau b/samples/Luau/Replicator.luau deleted file mode 100644 index 69d3012104..0000000000 --- a/samples/Luau/Replicator.luau +++ /dev/null @@ -1,546 +0,0 @@ ---// Authored by @ColRealPro (https://github.com/colrealpro) ---// Fetched from (https://github.com/ColRealPro/SimplyReplicate/blob/main/lib/Replicator.luau) ---// Licensed under the MIT License (https://github.com/ColRealPro/SimplyReplicate/blob/main/LICENSE) - ---!strict - --- // Services -local Players = game:GetService("Players") -local RunService = game:GetService("RunService") - --- // Modules -local NetworkManager = require(script.Parent.NetworkManager) -local console = require(script.Parent.console) -local Maid = require(script.Parent.Maid) -local Signal = require(script.Parent.Signal) - --- console.WriteEnv() -local print = console.log - --- // Internal Types -type dataType = T -type data = { [string]: dataType } -type DataStructure = typeof(setmetatable({} :: data, {})) - -type ReplicatorImpl = { - __index: ReplicatorImpl, - __tostring: (self: Replicator) -> string, - - new: (identifier: string | Instance, data: data) -> Replicator, - changeStates: (self: Replicator, changedStates: data, players: ({ Player } | Player)?) -> (), - syncPlayer: (self: Replicator, player: Player) -> (), - _prepareReplication: (self: Replicator) -> (), - _setState: (self: Replicator, state: string, value: any, players: { Player }?) -> (), - _createDataStructure: (self: Replicator, data: data) -> DataStructure, - _fetchFromServer: (self: Replicator) -> (), - _recieveUpdate: (self: Replicator, data: { syncData: { Remove: { string }?, Modify: { { Index: string, NewValue: any } }? }?, updateData: { [string]: any } }) -> (), - get: (self: Replicator, index: string?) -> { [string]: any } | any, - getForPlayer: (self: Replicator, player: Player, index: string?) -> { [string]: any } | any, - Destroy: (self: Replicator) -> () -} - -type Replicator = typeof(setmetatable({} :: { - identifier: string | Instance, - _maid: typeof(Maid), - _networkManager: NetworkManager.NetworkManager, - _isPlayer: boolean, - _context: "Server" | "Client" | string, - _deferringReplication: boolean, - _replicationNeeded: { Updates: { { Index: string, NewValue: any, Players: { Player }?, updateOrderTime: number } }, Syncs: { { Remove: { string }, Modify: { { Index: string, NewValue: any } }, Player: Player, SyncOrderTime: number } } }, - -- _dataStructure: DataStructure, - _localDataCopy: DataStructure, - _userStateCopies: { [Player]: DataStructure }, - _originalDataStructure: { [string]: any }, - StateChanged: Signal.Signal -}, {} :: ReplicatorImpl)) - ---[=[ - @class Replicator - - This is the main class of SimplyReplicate - Replicators are used to replicate data from the server to the client with ease - - Here is an example implementation of a replicator: - - **Server:** - ```lua - local Replicator = require(path.to.module) - - local GameStateReplicator = Replicator.new("GameState", { - Status = "Waiting", - RoundStarted = false, - }) - - task.wait(3) - - GameStateReplicator:changeStates({ - Status = "Game starts soon", - }) - - task.wait(3) - - GameStateReplicator:changeStates({ - Status = "Game started", - RoundStarted = true, - }) - ``` - - **Client:** - ```lua - local Replicator = require(path.to.module) - - local GameStateReplicator = Replicator.new("GameState") - - GameStateReplicator.StateChanged:Connect(function(state, value) - print("State changed", state, value) - end) - ``` -]=] -local Replicator: ReplicatorImpl = {} :: ReplicatorImpl -Replicator.__index = Replicator -Replicator.__tostring = function(self) - return `Replicator({tostring(self.identifier)})` -end - ---[=[ - Constructs a new replicator - - @yields - @function new - @within Replicator - @param identifier string | Instance - @param data { [string]: any } -- The data that will be replicated to clients - @return Replicator - - :::warning - When creating a replicator on the client, this function will yield until the initial data has been fetched from the server - ::: - - :::info - Note that if you are using strict luau, you will want to specify a type for the data parameter, - if you let Luau infer the type, when you go to change the state, it will want you to include every state as it wont have an option to be nil. - ::: -]=] -function Replicator.new(identifier: string | Instance, data: T?) - local self = setmetatable({}, Replicator) :: Replicator - - self.identifier = identifier - self._maid = Maid.new() - self._networkManager = NetworkManager.new(identifier) - self._isPlayer = typeof(identifier) == "Instance" and identifier:IsA("Player") - self._context = RunService:IsServer() and "Server" or "Client" - self._userStateCopies = {} - - -- // Signals - self.StateChanged = Signal.new() - - if self._context == "Server" then - if not data then - error("Data must be provided when creating a replicator on the server.", 2) - end - - if typeof(data) ~= "table" then - error("Data must be a table.", 2) - end - - self._replicationNeeded = { - Updates = {}, - Syncs = {} - } - self._deferringReplication = false - self._originalDataStructure = table.freeze(table.clone(data)) - self._localDataCopy = self:_createDataStructure(data) - -- self._dataStructure = self:_createDataStructure(data) - - self._networkManager:listenToFetch("FETCH_REPLICATOR", function(player) - if self._isPlayer and player ~= identifier then - return - end - - return self:get() - end) - else - self:_fetchFromServer() - self._networkManager:listen("UPDATE_REPLICATOR", function(data) - if data.identifier == self.identifier then - print("Recieved update from server") - self:_recieveUpdate(data :: { syncData: { Remove: { string }?, Modify: { { Index: string, NewValue: any } }? }?, updateData: { [string]: any } }) - end - end) - end - - return self -end - ---[=[ - Changes states in the replicator and replicate whatever changes to the clients - - @param changedStates { [string]: any? } -- The states that have been changed and their new values - @error "Invalid state" -- Thrown when the state does not exist in the data structure -]=] -function Replicator:changeStates(changedStates: data, players: ({ Player } | Player)?) - if self._context == "Client" then - error("This function can only be called on the server", 2) - end - - local playersTbl = type(players) == "table" and players or { players :: Player } - - for state, value in changedStates do - self:_setState(state, value, players and playersTbl) - end -end - ---[=[ - Syncs a player's state with the server's state - - @param player Player -]=] -function Replicator:syncPlayer(player: Player) - if self._context == "Client" then - error("This function can only be called on the server", 2) - end - - local userStateCopy = self._userStateCopies[player] - - if not userStateCopy then - print(`{player.Name} is already in sync with the server`) - return - end - - local remove = {} - local modify = {} - - for state, value in next, userStateCopy do - if not rawget(self._localDataCopy :: any, state) then - table.insert(remove, state) - elseif self._localDataCopy[state] ~= value then - -- FIXME: If a value is modified in the same frame before a sync, it will be put in the sync table even though it has not replication - -- this isn't a huge issue but it leads to wasted data being sent to the client as the original value will be dropped during replication - -- meaning that replicating the value as part of a sync is now useless - table.insert(modify, { - Index = state, - NewValue = self._localDataCopy[state] - }) - end - end - - table.insert(self._replicationNeeded.Syncs, { - Remove = remove, - Modify = modify, - Player = player, - - SyncOrderTime = os.clock() - }) - - self:_prepareReplication() -end - -function Replicator:_prepareReplication() - print("Preparing replication") - self._deferringReplication = true - - task.defer(function() - self._deferringReplication = false - - local replicationNeeded = self._replicationNeeded - self._replicationNeeded = { - Updates = {}, - Syncs = {} - } - - local replicationData = { - ["$all"] = { - updateData = {}, - } - } - - type PlayerReplicationData = { - syncData: { Remove: { string }?, Modify: { { Index: string, NewValue: any } }? }, - updateData: { [string]: any }, - internalData: { syncOrderTime: number? } - } - - local playerReplicationDataTemplate = { - syncData = {}, - updateData = {}, - internalData = {} - } - - -- Evaluate syncs first to make sure that we have enough data to keep the replication in order - -- and have the client in sync with the server - for _, v in replicationNeeded.Syncs do - local player = v.Player - local remove = v.Remove - local modify = v.Modify - local syncOrderTime = v.SyncOrderTime - - if not replicationData[player] then - replicationData[player] = table.clone(playerReplicationDataTemplate) :: PlayerReplicationData - end - - local previousSyncOrderTime = replicationData[player].internalData.syncOrderTime - if previousSyncOrderTime and syncOrderTime < previousSyncOrderTime then - print(`Dropping sync for player {player.Name} as a newer sync has already been evaluated`) - continue - end - - replicationData[player].syncData.Remove = remove - replicationData[player].syncData.Modify = modify - - replicationData[player].internalData.syncOrderTime = syncOrderTime - end - - for _, v in replicationNeeded.Updates do - local players = v.Players - local index = v.Index - local newValue = v.NewValue - - if players then - for _, player in players do - if not replicationData[player] then - replicationData[player] = table.clone(playerReplicationDataTemplate) :: PlayerReplicationData - end - - local syncOrderTime = replicationData[player].internalData.syncOrderTime - if syncOrderTime and v.updateOrderTime < syncOrderTime then - print(`Dropping update for state {v.Index} for player {player.Name} as a sync has already been evaluated after this update`) - continue - end - - replicationData[player].updateData[index] = newValue - end - else - replicationData["$all"].updateData[index] = newValue - end - end - - print("About to replicate data, sending out:", replicationData, "created from:", replicationNeeded) - - self._networkManager:send("UPDATE_REPLICATOR", { - identifier = self.identifier, - updateData = replicationData["$all"].updateData, - }) - - for player: Player | string, data in replicationData do - -- if player == "$all" then - -- self._networkManager:send("UPDATE_REPLICATOR", { - -- identifier = self.identifier, - -- updateData = data.updateData, - -- }) - -- continue - -- end - - if player == "$all" then - continue - end - - self._networkManager:send("UPDATE_REPLICATOR", { - identifier = self.identifier, - syncData = data.syncData, - updateData = data.updateData, - }, {player :: Player}) - end - end) -end - -function Replicator:_setState(state: string, value: any, players: { Player }?) - -- self._dataStructure[state] = value - -- self._replicationNeeded[state] = value - - table.insert(self._replicationNeeded.Updates, { - Index = state, - NewValue = value, - Players = players, - updateOrderTime = os.clock() - }) - - if not players then - self._localDataCopy[state] = value - players = Players:GetPlayers() - - task.defer(function() - self.StateChanged:Fire(state, value) - end) - end - - for _, player in players :: { Player } do - if not self._userStateCopies[player] then - self._userStateCopies[player] = self:_createDataStructure(self._originalDataStructure) - end - - self._userStateCopies[player][state] = value - end - - if not self._deferringReplication then - self:_prepareReplication() - end - - print(`Successfully changed state {state} to {value}`) -end - -function Replicator:_createDataStructure(data: { [string]: any }) : DataStructure - local dataStructure = setmetatable(table.clone(data), { - __index = function(self, key) - error(string.format("Invalid state %s, does not exist in the data structure.", - tostring(key)), 4) - end, - __newindex = function(self, key, value) - error(string.format("Invalid state %s, does not exist in the data structure.", - tostring(key)), 4) - end - }) - - return dataStructure -end - -function Replicator:_fetchFromServer() - local data = self._networkManager:fetch("FETCH_REPLICATOR", { - identifier = self.identifier, - }) :: { [string]: any } - - self._localDataCopy = self:_createDataStructure(data) - - print("Successfully fetched initial data from server") -end - -function Replicator:_recieveUpdate(data: { syncData: { Remove: { string }?, Modify: { { Index: string, NewValue: any } }? }?, updateData: { [string]: any } }) - -- for state, value in data do - -- self._localDataCopy[state] = value - - -- task.defer(function() - -- self.StateChanged:Fire(state, value) - -- end) - -- end - - -- print("Handled update from server") - - local syncData = data.syncData - local updateData = data.updateData - - if syncData then - local remove = syncData.Remove - local modify = syncData.Modify - - if remove then - for _, state in remove do - self._localDataCopy[state] = nil - - task.defer(function() - if self._localDataCopy[state] then - return - end - - self.StateChanged:Fire(state, nil) - end) - end - end - - if modify then - for _, v in modify do - local index = v.Index - local newValue = v.NewValue - - if self._localDataCopy[index] == newValue then - print(`Droping update for state {index} as the value is already the same`) - continue - end - - self._localDataCopy[index] = newValue - - task.defer(function() - if self._localDataCopy[index] ~= newValue then - return - end - - self.StateChanged:Fire(index, newValue) - end) - end - end - end - - for state, value in updateData do - self._localDataCopy[state] = value - - task.defer(function() - if self._localDataCopy[state] ~= value then - return - end - - self.StateChanged:Fire(state, value) - end) - end -end - ---[=[ - Returns the current state stored in the replicator - - @error "Invalid state" -- Thrown when the state does not exist in the data structure - @param index string? -- If provided, returns the state given, otherwise returns all states -]=] -function Replicator:get(index: string?): { [string]: any } | any - if index then - return self._localDataCopy[index] - end - - local dataStructure = self._localDataCopy - local data = {} - - -- Manually clone the table to remove the metatable - for state, value in next, dataStructure do - data[state] = value - end - - return data -end - ---[=[ - Returns the current state stored in the replicator for a specific player - - @error "Invalid state" -- Thrown when the state does not exist in the data structure - @param player Player - @param index string? -- If provided, returns the state given, otherwise returns all states -]=] -function Replicator:getForPlayer(player: Player, index: string?): { [string]: any } | any - if self._context == "Client" then - error("This function can only be called on the server", 2) - end - - if not self._userStateCopies[player] then - return self:get(index) - end - - if index then - return self._userStateCopies[player][index] - end - - local dataStructure = self._userStateCopies[player] - local data = {} - - -- Manually clone the table to remove the metatable - for state, value in next, dataStructure do - data[state] = value - end - - return data -end - ---[=[ - This function should be called when you are done using the replicator -]=] -function Replicator:Destroy() - self._networkManager:Destroy() - self._maid:DoCleaning() - - table.clear(self :: any) - setmetatable(self :: any, { - __metatable = "The metatable is locked", - __index = function() - error("This replicator has been destroyed", 2) - end, - __newindex = function() - error("This replicator has been destroyed", 2) - end - }) -end - -return Replicator diff --git a/samples/Luau/Signal.luau b/samples/Luau/Signal.luau deleted file mode 100644 index ccf7aebc4f..0000000000 --- a/samples/Luau/Signal.luau +++ /dev/null @@ -1,437 +0,0 @@ ---// Coauthored by @Sleitnick (https://github.com/sleitnick) and @Stravant (https://github.com/stravant) ---// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/signal/init.lua) ---// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) - ---!optimize 2 ---!strict ---!native - --- ----------------------------------------------------------------------------- --- Batched Yield-Safe Signal Implementation -- --- This is a Signal class which has effectively identical behavior to a -- --- normal RBXScriptSignal, with the only difference being a couple extra -- --- stack frames at the bottom of the stack trace when an error is thrown. -- --- This implementation caches runner coroutines, so the ability to yield in -- --- the signal handlers comes at minimal extra cost over a naive signal -- --- implementation that either always or never spawns a thread. -- --- -- --- License: -- --- Licensed under the MIT license. -- --- -- --- Authors: -- --- stravant - July 31st, 2021 - Created the file. -- --- sleitnick - August 3rd, 2021 - Modified for Knit. -- --- ----------------------------------------------------------------------------- - --- Signal types -export type Connection = { - Disconnect: (self: Connection) -> (), - Destroy: (self: Connection) -> (), - Connected: boolean, -} - -export type Signal = { - Fire: (self: Signal, T...) -> (), - FireDeferred: (self: Signal, T...) -> (), - Connect: (self: Signal, fn: (T...) -> ()) -> Connection, - Once: (self: Signal, fn: (T...) -> ()) -> Connection, - DisconnectAll: (self: Signal) -> (), - GetConnections: (self: Signal) -> { Connection }, - Destroy: (self: Signal) -> (), - Wait: (self: Signal) -> T..., -} - --- The currently idle thread to run the next handler on -local freeRunnerThread = nil - --- Function which acquires the currently idle handler runner thread, runs the --- function fn on it, and then releases the thread, returning it to being the --- currently idle one. --- If there was a currently idle runner thread already, that's okay, that old --- one will just get thrown and eventually GCed. -local function acquireRunnerThreadAndCallEventHandler(fn, ...) - local acquiredRunnerThread = freeRunnerThread - freeRunnerThread = nil - fn(...) - -- The handler finished running, this runner thread is free again. - freeRunnerThread = acquiredRunnerThread -end - --- Coroutine runner that we create coroutines of. The coroutine can be --- repeatedly resumed with functions to run followed by the argument to run --- them with. -local function runEventHandlerInFreeThread(...) - acquireRunnerThreadAndCallEventHandler(...) - while true do - acquireRunnerThreadAndCallEventHandler(coroutine.yield()) - end -end - ---[=[ - @within Signal - @interface SignalConnection - .Connected boolean - .Disconnect (SignalConnection) -> () - - Represents a connection to a signal. - ```lua - local connection = signal:Connect(function() end) - print(connection.Connected) --> true - connection:Disconnect() - print(connection.Connected) --> false - ``` -]=] - --- Connection class -local Connection = {} -Connection.__index = Connection - -function Connection:Disconnect() - if not self.Connected then - return - end - self.Connected = false - - -- Unhook the node, but DON'T clear it. That way any fire calls that are - -- currently sitting on this node will be able to iterate forwards off of - -- it, but any subsequent fire calls will not hit it, and it will be GCed - -- when no more fire calls are sitting on it. - if self._signal._handlerListHead == self then - self._signal._handlerListHead = self._next - else - local prev = self._signal._handlerListHead - while prev and prev._next ~= self do - prev = prev._next - end - if prev then - prev._next = self._next - end - end -end - -Connection.Destroy = Connection.Disconnect - --- Make Connection strict -setmetatable(Connection, { - __index = function(_tb, key) - error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2) - end, - __newindex = function(_tb, key, _value) - error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2) - end, -}) - ---[=[ - @within Signal - @type ConnectionFn (...any) -> () - - A function connected to a signal. -]=] - ---[=[ - @class Signal - - A Signal is a data structure that allows events to be dispatched - and observed. - - This implementation is a direct copy of the de facto standard, [GoodSignal](https://devforum.roblox.com/t/lua-signal-class-comparison-optimal-goodsignal-class/1387063), - with some added methods and typings. - - For example: - ```lua - local signal = Signal.new() - - -- Subscribe to a signal: - signal:Connect(function(msg) - print("Got message:", msg) - end) - - -- Dispatch an event: - signal:Fire("Hello world!") - ``` -]=] -local Signal = {} -Signal.__index = Signal - ---[=[ - Constructs a new Signal - - @return Signal -]=] -function Signal.new(): Signal - local self = setmetatable({ - _handlerListHead = false, - _proxyHandler = nil, - _yieldedThreads = nil, - }, Signal) - - return self -end - ---[=[ - Constructs a new Signal that wraps around an RBXScriptSignal. - - @param rbxScriptSignal RBXScriptSignal -- Existing RBXScriptSignal to wrap - @return Signal - - For example: - ```lua - local signal = Signal.Wrap(workspace.ChildAdded) - signal:Connect(function(part) print(part.Name .. " added") end) - Instance.new("Part").Parent = workspace - ``` -]=] -function Signal.Wrap(rbxScriptSignal: RBXScriptSignal): Signal - assert( - typeof(rbxScriptSignal) == "RBXScriptSignal", - "Argument #1 to Signal.Wrap must be a RBXScriptSignal; got " .. typeof(rbxScriptSignal) - ) - - local signal = Signal.new() - signal._proxyHandler = rbxScriptSignal:Connect(function(...) - signal:Fire(...) - end) - - return signal -end - ---[=[ - Checks if the given object is a Signal. - - @param obj any -- Object to check - @return boolean -- `true` if the object is a Signal. -]=] -function Signal.Is(obj: any): boolean - return type(obj) == "table" and getmetatable(obj) == Signal -end - ---[=[ - @param fn ConnectionFn - @return SignalConnection - - Connects a function to the signal, which will be called anytime the signal is fired. - ```lua - signal:Connect(function(msg, num) - print(msg, num) - end) - - signal:Fire("Hello", 25) - ``` -]=] -function Signal:Connect(fn) - local connection = setmetatable({ - Connected = true, - _signal = self, - _fn = fn, - _next = false, - }, Connection) - - if self._handlerListHead then - connection._next = self._handlerListHead - self._handlerListHead = connection - else - self._handlerListHead = connection - end - - return connection -end - ---[=[ - @deprecated v1.3.0 -- Use `Signal:Once` instead. - @param fn ConnectionFn - @return SignalConnection -]=] -function Signal:ConnectOnce(fn) - return self:Once(fn) -end - ---[=[ - @param fn ConnectionFn - @return SignalConnection - - Connects a function to the signal, which will be called the next time the signal fires. Once - the connection is triggered, it will disconnect itself. - ```lua - signal:Once(function(msg, num) - print(msg, num) - end) - - signal:Fire("Hello", 25) - signal:Fire("This message will not go through", 10) - ``` -]=] -function Signal:Once(fn) - local connection - local done = false - - connection = self:Connect(function(...) - if done then - return - end - - done = true - connection:Disconnect() - fn(...) - end) - - return connection -end - -function Signal:GetConnections() - local items = {} - - local item = self._handlerListHead - while item do - table.insert(items, item) - item = item._next - end - - return items -end - --- Disconnect all handlers. Since we use a linked list it suffices to clear the --- reference to the head handler. ---[=[ - Disconnects all connections from the signal. - ```lua - signal:DisconnectAll() - ``` -]=] -function Signal:DisconnectAll() - local item = self._handlerListHead - while item do - item.Connected = false - item = item._next - end - self._handlerListHead = false - - local yieldedThreads = rawget(self, "_yieldedThreads") - if yieldedThreads then - for thread in yieldedThreads do - if coroutine.status(thread) == "suspended" then - warn(debug.traceback(thread, "signal disconnected; yielded thread cancelled", 2)) - task.cancel(thread) - end - end - table.clear(self._yieldedThreads) - end -end - --- Signal:Fire(...) implemented by running the handler functions on the --- coRunnerThread, and any time the resulting thread yielded without returning --- to us, that means that it yielded to the Roblox scheduler and has been taken --- over by Roblox scheduling, meaning we have to make a new coroutine runner. ---[=[ - @param ... any - - Fire the signal, which will call all of the connected functions with the given arguments. - ```lua - signal:Fire("Hello") - - -- Any number of arguments can be fired: - signal:Fire("Hello", 32, {Test = "Test"}, true) - ``` -]=] -function Signal:Fire(...) - local item = self._handlerListHead - while item do - if item.Connected then - if not freeRunnerThread then - freeRunnerThread = coroutine.create(runEventHandlerInFreeThread) - end - task.spawn(freeRunnerThread, item._fn, ...) - end - item = item._next - end -end - ---[=[ - @param ... any - - Same as `Fire`, but uses `task.defer` internally & doesn't take advantage of thread reuse. - ```lua - signal:FireDeferred("Hello") - ``` -]=] -function Signal:FireDeferred(...) - local item = self._handlerListHead - while item do - local conn = item - task.defer(function(...) - if conn.Connected then - conn._fn(...) - end - end, ...) - item = item._next - end -end - ---[=[ - @return ... any - @yields - - Yields the current thread until the signal is fired, and returns the arguments fired from the signal. - Yielding the current thread is not always desirable. If the desire is to only capture the next event - fired, using `Once` might be a better solution. - ```lua - task.spawn(function() - local msg, num = signal:Wait() - print(msg, num) --> "Hello", 32 - end) - signal:Fire("Hello", 32) - ``` -]=] -function Signal:Wait() - local yieldedThreads = rawget(self, "_yieldedThreads") - if not yieldedThreads then - yieldedThreads = {} - rawset(self, "_yieldedThreads", yieldedThreads) - end - - local thread = coroutine.running() - yieldedThreads[thread] = true - - self:Once(function(...) - yieldedThreads[thread] = nil - task.spawn(thread, ...) - end) - - return coroutine.yield() -end - ---[=[ - Cleans up the signal. - - Technically, this is only necessary if the signal is created using - `Signal.Wrap`. Connections should be properly GC'd once the signal - is no longer referenced anywhere. However, it is still good practice - to include ways to strictly clean up resources. Calling `Destroy` - on a signal will also disconnect all connections immediately. - ```lua - signal:Destroy() - ``` -]=] -function Signal:Destroy() - self:DisconnectAll() - - local proxyHandler = rawget(self, "_proxyHandler") - if proxyHandler then - proxyHandler:Disconnect() - end -end - --- Make signal strict -setmetatable(Signal, { - __index = function(_tb, key) - error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2) - end, - __newindex = function(_tb, key, _value) - error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2) - end, -}) - -return table.freeze({ - new = Signal.new, - Wrap = Signal.Wrap, - Is = Signal.Is, -}) diff --git a/samples/Luau/pathToRegexp.luau b/samples/Luau/pathToRegexp.luau deleted file mode 100644 index 37b528c051..0000000000 --- a/samples/Luau/pathToRegexp.luau +++ /dev/null @@ -1,854 +0,0 @@ ---// Authored by @Roblox (https://github.com/Roblox) ---// Fetched from (https://github.com/Roblox/roact-navigation/blob/master/src/routers/pathToRegexp.lua) ---// Licensed under the MIT License (https://github.com/Roblox/roact-navigation/blob/master/LICENSE) - --- upstream: https://github.com/pillarjs/path-to-regexp/blob/feddb3d3391d843f21ea9cde195f066149dba0be/src/index.ts ---[[ -The MIT License (MIT) - -Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -]] - ---!strict ---!nolint LocalShadow -local root = script.Parent.Parent -local Packages = root.Parent -local LuauPolyfill = require(Packages.LuauPolyfill) -local Array = LuauPolyfill.Array -local RegExp = require(Packages.RegExp) - -type Record = { [T]: U } - -local function TypeError(message) - return message -end - -local exports = {} - --- Roblox deviation: predeclare function variables -local escapeString -local flags - ---[[ - * Tokenizer results. - ]] -type LexToken = { - type: string, - -- | "OPEN" - -- | "CLOSE" - -- | "PATTERN" - -- | "NAME" - -- | "CHAR" - -- | "ESCAPED_CHAR" - -- | "MODIFIER" - -- | "END", - index: number, - value: string, -} - ---[[ - * Tokenize input string. - ]] -local function lexer(str: string): { LexToken } - local tokens: { LexToken } = {} - local i = 1 - - -- Roblox deviation: the original JavaScript contains a lot of `i++`, which - -- does not translate really well to Luau. This function mimic the operation - -- while still being an expression (because it's a function call) - local function preIncrement_i(value) - i += 1 - return value - end - -- Roblox deviation: use this function to translate `str[n]` directly - local function getChar(n) - return string.sub(str, n, n) - end - - local strLength = string.len(str) - - while i <= strLength do - local char = string.sub(str, i, i) - - if char == "*" or char == "+" or char == "?" then - table.insert(tokens, { - type = "MODIFIER", - index = i, - value = getChar(preIncrement_i(i)), - }) - continue - end - - if char == "\\" then - table.insert(tokens, { - type = "ESCAPED_CHAR", - index = preIncrement_i(i), - value = getChar(preIncrement_i(i)), - }) - continue - end - - if char == "{" then - table.insert(tokens, { - type = "OPEN", - index = i, - value = getChar(preIncrement_i(i)), - }) - continue - end - - if char == "}" then - table.insert(tokens, { - type = "CLOSE", - index = i, - value = getChar(preIncrement_i(i)), - }) - continue - end - - if char == ":" then - local name = "" - local j = i + 1 - - -- Roblox deviation: the original JavaScript contains a lot of `j++`, which - -- does not translate really well to Luau. This function mimic the operation - -- while still being an expression (because it's a function call) - local function preIncrement_j(value) - j += 1 - return value - end - - while j <= strLength do - local code = string.byte(str, j) - - if - -- // `0-9` - (code >= 48 and code <= 57) - -- // `A-Z` - or (code >= 65 and code <= 90) - -- // `a-z` - or (code >= 97 and code <= 122) - -- // `_` - or code == 95 - then - name = name .. getChar(preIncrement_j(j)) - continue - end - - break - end - - if name == "" then - error(TypeError(("Missing parameter name at %d"):format(i))) - end - - table.insert(tokens, { - type = "NAME", - index = i, - value = name, - }) - i = j - continue - end - - if char == "(" then - local count = 1 - local pattern = "" - local j = i + 1 - - if getChar(j) == "?" then - error(TypeError(('Pattern cannot start with "?" at %d'):format(j))) - end - - -- Roblox deviation: the original JavaScript contains a lot of `j++`, which - -- does not translate really well to Luau. This function mimic the operation - -- while still being an expression (because it's a function call) - local function preIncrement_j(value) - j += 1 - return value - end - - while j <= strLength do - if getChar(j) == "\\" then - pattern = pattern .. (getChar(preIncrement_j(j)) .. getChar(preIncrement_j(j))) - end - - if getChar(j) == ")" then - count = count - 1 - if count == 0 then - j = j + 1 - break - end - elseif getChar(j) == "(" then - count = count + 1 - - if getChar(j + 1) ~= "?" then - error(TypeError(("Capturing groups are not allowed at %d"):format(j))) - end - end - - pattern = pattern .. getChar(preIncrement_j(j)) - end - - if count ~= 0 then - error(TypeError(("Unbalanced pattern at %d"):format(i))) - end - if pattern == "" then - error(TypeError(("Missing pattern at %d"):format(i))) - end - - table.insert(tokens, { - type = "PATTERN", - index = i, - value = pattern, - }) - i = j - continue - end - - table.insert(tokens, { - type = "CHAR", - index = i, - value = getChar(preIncrement_i(i)), - }) - end - - table.insert(tokens, { - type = "END", - index = i, - value = "", - }) - - return tokens -end - -export type ParseOptions = { - --[[ - * Set the default delimiter for repeat parameters. (default: `'/'`) - ]] - delimiter: string?, - --[[ - * List of characters to automatically consider prefixes when parsing. - ]] - prefixes: string?, -} - ---[[ - * Parse a string for the raw tokens. - ]] -function exports.parse(str: string, optionalOptions: ParseOptions?): { Token } - local options = optionalOptions or {} - local tokens = lexer(str) - - local prefixes = "./" - if options.prefixes ~= nil and options.prefixes ~= "" then - prefixes = options.prefixes - end - local defaultPattern = string.format("[^%s]+?", escapeString(options.delimiter or "/#?")) - local result: { Token } = {} - local key = 0 - local i = 1 - local path = "" - - -- Roblox deviation: the original JavaScript contains a lot of `i++`, which - -- does not translate really well to Luau. This function mimic the operation - -- while still being an expression (because it's a function call) - local function preIncrement_i(value) - i += 1 - return value - end - - -- Roblox deviation: the original JavaScript contains a lot of `key++`, which - -- does not translate really well to Luau. This function mimic the operation - -- while still being an expression (because it's a function call) - local function preIncrement_key(value) - key += 1 - return value - end - - local function tryConsume(type_): string? - if i <= #tokens and tokens[i].type == type_ then - local v = tokens[preIncrement_i(i)].value - return v - end - return nil - end - - local function mustConsume(type_): string - local value = tryConsume(type_) - if value ~= nil then - return value - end - local token = tokens[i] - if token == nil then - error(TypeError(("Expected token %s, got nil"):format(type_))) - end - local nextType = token.type - local index = token.index - error(TypeError(("Unexpected %s at %d, expected %s"):format(nextType, index, type_))) - end - - local function consumeText(): string - local result = "" - local value: string? = tryConsume("CHAR") or tryConsume("ESCAPED_CHAR") - while value and value ~= "" do - result ..= value - value = tryConsume("CHAR") or tryConsume("ESCAPED_CHAR") - end - return result - end - - while i <= #tokens do - local char = tryConsume("CHAR") - local name = tryConsume("NAME") - local pattern = tryConsume("PATTERN") - - if (name and name ~= "") or (pattern and pattern ~= "") then - local prefix = char or "" - - if string.find(prefixes, prefix) == nil then - path = path .. prefix - prefix = "" - end - - if path ~= nil and path ~= "" then - table.insert(result, path) - path = "" - end - - local resultName = name - if name == nil or name == "" then - resultName = preIncrement_key(key) - end - local resultPattern = pattern - if pattern == nil or pattern == "" then - resultPattern = defaultPattern - end - - table.insert(result, { - name = resultName, - prefix = prefix, - suffix = "", - pattern = resultPattern, - modifier = tryConsume("MODIFIER") or "", - }) - continue - end - - local value = char or tryConsume("ESCAPED_CHAR") - - if value and value ~= "" then - path ..= value - continue - end - - if path and path ~= "" then - table.insert(result, path) - path = "" - end - - local open = tryConsume("OPEN") - - if open and open ~= "" then - local prefix = consumeText() - local name = tryConsume("NAME") or "" - local pattern = tryConsume("PATTERN") or "" - local suffix = consumeText() - - mustConsume("CLOSE") - - if name == "" and pattern ~= "" then - name = preIncrement_key(key) - end - -- Roblox deviation: we need to check if name is not 0, because 0 is false in JavaScript, and - -- it could be number because it could be assigned to the key value - if (name ~= "" and name ~= 0) and (pattern == nil or pattern == "") then - pattern = defaultPattern - end - - table.insert(result, { - name = name, - pattern = pattern, - prefix = prefix, - suffix = suffix, - modifier = tryConsume("MODIFIER") or "", - }) - continue - end - - mustConsume("END") - end - - return result -end - -export type TokensToFunctionOptions = { - --[[ - * When `true` the regexp will be case sensitive. (default: `false`) - ]] - sensitive: boolean?, - --[[ - * Function for encoding input strings for output. - ]] - encode: nil | (string, Key) -> string, - --[[ - * When `false` the function can produce an invalid (unmatched) path. (default: `true`) - ]] - validate: boolean?, -} - ---[[ - * Compile a string to a template function for the path. - ]] -function exports.compile(str: string, options: (ParseOptions & TokensToFunctionOptions)?) - return exports.tokensToFunction(exports.parse(str, options), options) -end - -export type PathFunction

= (P?) -> string - ---[[ - * Expose a method for transforming tokens into the path function. - ]] -function exports.tokensToFunction(tokens: { Token }, optionalOptions: TokensToFunctionOptions?) - if optionalOptions == nil then - optionalOptions = {} - end - local options = optionalOptions :: TokensToFunctionOptions - local reFlags = flags(options) - local encode = options.encode or function(x: string): string - return x - end - local validate = options.validate - if validate == nil then - validate = true - end - - -- Compile all the tokens into regexps. - local matches = Array.map(tokens, function(token) - if type(token) == "table" then - return RegExp(("^(?:%s)$"):format(token.pattern), reFlags) - end - return nil - end) - - return function(data: Record?) - local path = "" - - for i, token in tokens do - if type(token) == "string" then - path ..= token - continue - end - - -- Roblox deviation: in JavaScript, indexing an object with a number will coerce the number - -- value into a string - local value = if data then data[tostring(token.name)] else nil - local optional = token.modifier == "?" or token.modifier == "*" - local repeat_ = token.modifier == "*" or token.modifier == "+" - - if Array.isArray(value) then - if not repeat_ then - error(TypeError(('Expected "%s" to not repeat, but got an array'):format(token.name))) - end - - if #value == 0 then - if optional then - continue - end - - error(TypeError(('Expected "%s" to not be empty'):format(token.name))) - end - - for _, element in value do - local segment = encode(element, token) - - if validate and not matches[i]:test(segment) then - error( - TypeError( - ('Expected all "%s" to match "%s", but got "%s"'):format( - token.name, - token.pattern, - segment - ) - ) - ) - end - - path ..= token.prefix .. segment .. token.suffix - end - - continue - end - - local valueType = type(value) - if valueType == "string" or valueType == "number" then - local segment = encode(tostring(value), token) - - if validate and not matches[i]:test(segment) then - error( - TypeError( - ('Expected "%s" to match "%s", but got "%s"'):format(token.name, token.pattern, segment) - ) - ) - end - - path ..= token.prefix .. segment .. token.suffix - continue - end - - if optional then - continue - end - - local typeOfMessage = if repeat_ then "an array" else "a string" - error(TypeError(('Expected "%s" to be %s'):format(tostring(token.name), typeOfMessage))) - end - - return path - end -end - -export type RegexpToFunctionOptions = { - --[[ - * Function for decoding strings for params. - ]] - decode: nil | (string, Key) -> string, -} - ---[[ - * A match result contains data about the path match. - ]] -export type MatchResult

= { - path: string, - index: number, - params: P, -} - ---[[ - * A match is either `false` (no match) or a match result. - ]] --- export type Match

= false | MatchResult

-export type Match

= boolean | MatchResult

- ---[[ - * The match function takes a string and returns whether it matched the path. - ]] -export type MatchFunction

= (string) -> Match

- ---[[ - * Create path match function from `path-to-regexp` spec. - ]] -function exports.match(str, options) - local keys: { Key } = {} - local re = exports.pathToRegexp(str, keys, options) - return exports.regexpToFunction(re, keys, options) -end - ---[[ - * Create a path match function from `path-to-regexp` output. - ]] -function exports.regexpToFunction(re: any, keys: { Key }, options: RegexpToFunctionOptions) - if options == nil then - options = {} - end - local decode = options.decode or function(x: string) - return x - end - - return function(pathname: string) - local matches = re:exec(pathname) - if not matches then - return false - end - - local path = matches[1] - local index = matches.index or 0 - local params = {} - - -- Roblox deviation: start the iteration from 2 instead of 1 because the individual - -- matches start at 2 in our polyfill version of RegExp objects. - for i = 2, matches.n do - if matches[i] == nil then - continue - end - - -- Roblox comment: keep the `-1` because our matches array start from 1, - -- so the loop starts at index 2 (index 1 is the full matched string) - local key = keys[i - 1] - if key.modifier == "*" or key.modifier == "+" then - params[key.name] = Array.map(string.split(matches[i], key.prefix .. key.suffix), function(value) - return decode(value, key) - end) - else - params[key.name] = decode(matches[i], key) - end - end - - return { - path = path, - index = index, - params = params, - } - end -end - ---[[ - * Escape a regular expression string. - ]] -function escapeString(str: string) - return string.gsub(str, "[%.%+%*%?=%^!:${}%(%)%[%]|/\\]", function(match) - return "\\" .. match - end) -end - ---[[ - * Get the flags for a regexp from the options. - ]] -function flags(options: { sensitive: boolean? }?) - if options and options.sensitive then - return "" - else - return "i" - end -end - ---[[ - * Metadata about a key. - ]] -export type Key = { - name: string | number, - prefix: string, - suffix: string, - pattern: string, - modifier: string, -} - ---[[ - * A token is a string (nothing special) or key metadata (capture group). - ]] -export type Token = string | Key - --- Roblox deviation: this functionality is not required so it has been omitted ---[[ - * Pull out keys from a regexp. - ]] --- local function regexpToRegexp(path: string, keys: { Key }?): string --- if not keys then --- return path --- end - --- local groupsRegex = "%(" .. "(%?<(.*)>)?" .. "[^%?]" - --- local index = 0 --- local matchGenerator = path.source:gmatch(groupsRegex) --- -- local execResult = groupsRegex.exec(path.source) --- local execResult = matchGenerator() - --- while execResult do --- error('got match -> ' .. execResult .. " for path = " .. path) --- local name = execResult[1] --- if name then --- name = index --- index += 1 --- end --- table.insert(keys, { --- -- // Use parenthesized substring match if available, index otherwise --- name = name, --- prefix = "", --- suffix = "", --- modifier = "", --- pattern = "", --- }) - --- -- execResult = groupsRegex.exec(path.source) --- execResult = matchGenerator() --- end - --- return path --- end - ---[[ - * Transform an array into a regexp. - ]] -local function arrayToRegexp( - paths: { string }, - keys: { Key }?, - options: (TokensToRegexpOptions & ParseOptions)? -): string - local parts = Array.map(paths, function(path) - return exports.pathToRegexp(path, keys, options).source - end) - - return RegExp(("(?:%s)"):format(table.concat(parts, "|")), flags(options)) -end - ---[[ - * Create a path regexp from string input. - ]] -local function stringToRegexp(path, keys, options) - return exports.tokensToRegexp(exports.parse(path, options), keys, options) -end - -export type TokensToRegexpOptions = { - --[[ - * When `true` the regexp will be case sensitive. (default: `false`) - ]] - sensitive: boolean?, - --[[ - * When `true` the regexp won't allow an optional trailing delimiter to match. (default: `false`) - ]] - strict: boolean?, - --[[ - * When `true` the regexp will match to the end of the string. (default: `true`) - ]] - end_: boolean?, - --[[ - * When `true` the regexp will match from the beginning of the string. (default: `true`) - ]] - start: boolean?, - --[[ - * Sets the final character for non-ending optimistic matches. (default: `/`) - ]] - delimiter: string?, - --[[ - * List of characters that can also be "end" characters. - ]] - endsWith: string?, - --[[ - * Encode path tokens for use in the `RegExp`. - ]] - encode: nil | (string) -> string, -} - ---[[ - * Expose a function for taking tokens and returning a RegExp. - ]] -function exports.tokensToRegexp(tokens: { Token }, keys: { Key }?, optionalOptions: TokensToRegexpOptions?) - local options = {} - if optionalOptions ~= nil then - options = optionalOptions - end - local strict = options.strict - if strict == nil then - strict = false - end - local start = options.start - if start == nil then - start = true - end - local end_ = options.end_ - if end_ == nil then - end_ = true - end - local encode = options.encode or function(x: string) - return x - end - -- Roblox deviation: our Lua regex implementation does not support empty character class - local endsWith = if options.endsWith then ("[%s]|$"):format(escapeString(options.endsWith or "")) else "$" - local delimiter = ("[%s]"):format(escapeString(options.delimiter or "/#?")) - local route = if start then "^" else "" - - -- // Iterate over the tokens and create our regexp string. - for _, token in tokens do - if type(token) == "string" then - route ..= escapeString(encode(token)) - else - local prefix = escapeString(encode(token.prefix)) - local suffix = escapeString(encode(token.suffix)) - - if token.pattern and token.pattern ~= "" then - if keys then - table.insert(keys, token) - end - - if (prefix and prefix ~= "") or (suffix and suffix ~= "") then - if token.modifier == "+" or token.modifier == "*" then - local mod = if token.modifier == "*" then "?" else "" - route ..= ("(?:%s((?:%s)(?:%s%s(?:%s))*)%s)%s"):format( - prefix, - token.pattern, - suffix, - prefix, - token.pattern, - suffix, - mod - ) - else - route ..= ("(?:%s(%s)%s)%s"):format(prefix, token.pattern, suffix, token.modifier) - end - else - route ..= ("(%s)%s"):format(token.pattern, token.modifier) - end - else - route ..= ("(?:%s%s)%s"):format(prefix, suffix, token.modifier) - end - end - end - - if end_ then - if not strict then - route ..= ("%s?"):format(delimiter) - end - - if options.endsWith and options.endsWith ~= "" then - route ..= ("(?=%s)"):format(endsWith) - else - route ..= "$" - end - else - local endToken = tokens[#tokens] - local isEndDelimited = endToken == nil - if type(endToken) == "string" then - isEndDelimited = string.find(delimiter, endToken:sub(-1)) ~= nil - end - - if not strict then - route ..= string.format("(?:%s(?=%s))?", delimiter, endsWith) - end - if not isEndDelimited then - route ..= string.format("(?=%s|%s)", delimiter, endsWith) - end - end - - return RegExp(route, flags(options)) -end - ---[[ - * Supported `path-to-regexp` input types. - ]] -export type Path = string | { string } - ---[[ - * Normalize the given path string, returning a regular expression. - * - * An empty array can be passed in for the keys, which will hold the - * placeholder key descriptions. For example, using `/user/:id`, `keys` will - * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. - ]] -function exports.pathToRegexp(path: Path, keys: { Key }?, options: (TokensToRegexpOptions & ParseOptions)?) - -- if (path instanceof RegExp) return regexpToRegexp(path, keys); - if Array.isArray(path) then - return arrayToRegexp(path :: { string }, keys, options) - end - return stringToRegexp(path, keys, options) -end - -return exports \ No newline at end of file diff --git a/samples/Luau/roblox.luau b/samples/Luau/roblox.luau deleted file mode 100644 index 2b1ea9beb8..0000000000 --- a/samples/Luau/roblox.luau +++ /dev/null @@ -1,512 +0,0 @@ ---// Authored by @Filiptibell (https://github.com/filiptibell) ---// Fetched from (https://github.com/lune-org/lune/blob/main/types/roblox.luau) ---// Licensed under the Mozilla Public License v2.0 (https://github.com/lune-org/lune/blob/main/LICENSE.txt) - ---!strict -export type DatabaseScriptability = "None" | "Custom" | "Read" | "ReadWrite" | "Write" - -export type DatabasePropertyTag = - "Deprecated" - | "Hidden" - | "NotBrowsable" - | "NotReplicated" - | "NotScriptable" - | "ReadOnly" - | "WriteOnly" - -export type DatabaseClassTag = - "Deprecated" - | "NotBrowsable" - | "NotCreatable" - | "NotReplicated" - | "PlayerReplicated" - | "Service" - | "Settings" - | "UserSettings" - -export type DatabaseProperty = { - --[=[ - The name of the property. - ]=] - Name: string, - --[=[ - The datatype of the property. - - For normal datatypes this will be a string such as `string`, `Color3`, ... - - For enums this will be a string formatted as `Enum.EnumName`. - ]=] - Datatype: string, - --[=[ - The scriptability of this property, meaning if it can be written / read at runtime. - - All properties are writable and readable in Lune even if scriptability is not. - ]=] - Scriptability: DatabaseScriptability, - --[=[ - Tags describing the property. - - These include information such as if the property can be replicated to players - at runtime, if the property should be hidden in Roblox Studio, and more. - ]=] - Tags: { DatabasePropertyTag }, -} - -export type DatabaseClass = { - --[=[ - The name of the class. - ]=] - Name: string, - --[=[ - The superclass (parent class) of this class. - - May be nil if no parent class exists. - ]=] - Superclass: string?, - --[=[ - Known properties for this class. - ]=] - Properties: { [string]: DatabaseProperty }, - --[=[ - Default values for properties of this class. - - Note that these default properties use Lune's built-in datatype - userdatas, and that if there is a new datatype that Lune does - not yet know about, it may be missing from this table. - ]=] - DefaultProperties: { [string]: any }, - --[=[ - Tags describing the class. - - These include information such as if the class can be replicated - to players at runtime, and top-level class categories. - ]=] - Tags: { DatabaseClassTag }, -} - -export type DatabaseEnum = { - --[=[ - The name of this enum, for example `PartType` or `UserInputState`. - ]=] - Name: string, - --[=[ - Members of this enum. - - Note that this is a direct map of name -> enum values, - and does not actually use the EnumItem datatype itself. - ]=] - Items: { [string]: number }, -} - -export type Database = { - --[=[ - The current version of the reflection database. - - This will follow the format `x.y.z.w`, which most commonly looks something like `0.567.0.123456789` - ]=] - Version: string, - --[=[ - Retrieves a list of all currently known class names. - ]=] - GetClassNames: (self: Database) -> { string }, - --[=[ - Retrieves a list of all currently known enum names. - ]=] - GetEnumNames: (self: Database) -> { string }, - --[=[ - Gets a class with the exact given name, if one exists. - ]=] - GetClass: (self: Database, name: string) -> DatabaseClass?, - --[=[ - Gets an enum with the exact given name, if one exists. - ]=] - GetEnum: (self: Database, name: string) -> DatabaseEnum?, - --[=[ - Finds a class with the given name. - - This will use case-insensitive matching and ignore leading and trailing whitespace. - ]=] - FindClass: (self: Database, name: string) -> DatabaseClass?, - --[=[ - Finds an enum with the given name. - - This will use case-insensitive matching and ignore leading and trailing whitespace. - ]=] - FindEnum: (self: Database, name: string) -> DatabaseEnum?, -} - -type InstanceProperties = { - Parent: Instance?, - ClassName: string, - Name: string, - -- FIXME: This breaks intellisense, but we need some way to access - -- instance properties without casting the entire instance to any... - -- [string]: any, -} - -type InstanceMetatable = { - Clone: (self: Instance) -> Instance, - Destroy: (self: Instance) -> (), - ClearAllChildren: (self: Instance) -> (), - - GetChildren: (self: Instance) -> { Instance }, - GetDebugId: (self: Instance) -> string, - GetDescendants: (self: Instance) -> { Instance }, - GetFullName: (self: Instance) -> string, - - FindFirstAncestor: (self: Instance, name: string) -> Instance?, - FindFirstAncestorOfClass: (self: Instance, className: string) -> Instance?, - FindFirstAncestorWhichIsA: (self: Instance, className: string) -> Instance?, - FindFirstChild: (self: Instance, name: string, recursive: boolean?) -> Instance?, - FindFirstChildOfClass: (self: Instance, className: string, recursive: boolean?) -> Instance?, - FindFirstChildWhichIsA: (self: Instance, className: string, recursive: boolean?) -> Instance?, - - IsA: (self: Instance, className: string) -> boolean, - IsAncestorOf: (self: Instance, descendant: Instance) -> boolean, - IsDescendantOf: (self: Instance, ancestor: Instance) -> boolean, - - GetAttribute: (self: Instance, name: string) -> any, - GetAttributes: (self: Instance) -> { [string]: any }, - SetAttribute: (self: Instance, name: string, value: any) -> (), - - GetTags: (self: Instance) -> { string }, - HasTag: (self: Instance, name: string) -> boolean, - AddTag: (self: Instance, name: string) -> (), - RemoveTag: (self: Instance, name: string) -> (), -} - -export type Instance = typeof(setmetatable( - (nil :: any) :: InstanceProperties, - (nil :: any) :: { __index: InstanceMetatable } -)) - -export type DataModelProperties = {} -export type DataModelMetatable = { - GetService: (self: DataModel, name: string) -> Instance, - FindService: (self: DataModel, name: string) -> Instance?, -} - -export type DataModel = - Instance - & typeof(setmetatable( - (nil :: any) :: DataModelProperties, - (nil :: any) :: { __index: DataModelMetatable } - )) - ---[=[ - @class Roblox - - Built-in library for manipulating Roblox place & model files - - ### Example usage - - ```lua - local fs = require("@lune/fs") - local roblox = require("@lune/roblox") - - -- Reading a place file - local placeFile = fs.readFile("myPlaceFile.rbxl") - local game = roblox.deserializePlace(placeFile) - - -- Manipulating and reading instances - just like in Roblox! - local workspace = game:GetService("Workspace") - for _, child in workspace:GetChildren() do - print("Found child " .. child.Name .. " of class " .. child.ClassName) - end - - -- Writing a place file - local newPlaceFile = roblox.serializePlace(game) - fs.writeFile("myPlaceFile.rbxl", newPlaceFile) - ``` -]=] -local roblox = {} - ---[=[ - @within Roblox - @tag must_use - - Deserializes a place into a DataModel instance. - - This function accepts a string of contents, *not* a file path. - If reading a place file from a file path is desired, `fs.readFile` - can be used and the resulting string may be passed to this function. - - ### Example usage - - ```lua - local fs = require("@lune/fs") - local roblox = require("@lune/roblox") - - local placeFile = fs.readFile("filePath.rbxl") - local game = roblox.deserializePlace(placeFile) - ``` - - @param contents The contents of the place to read -]=] -function roblox.deserializePlace(contents: string): DataModel - return nil :: any -end - ---[=[ - @within Roblox - @tag must_use - - Deserializes a model into an array of instances. - - This function accepts a string of contents, *not* a file path. - If reading a model file from a file path is desired, `fs.readFile` - can be used and the resulting string may be passed to this function. - - ### Example usage - - ```lua - local fs = require("@lune/fs") - local roblox = require("@lune/roblox") - - local modelFile = fs.readFile("filePath.rbxm") - local instances = roblox.deserializeModel(modelFile) - ``` - - @param contents The contents of the model to read -]=] -function roblox.deserializeModel(contents: string): { Instance } - return nil :: any -end - ---[=[ - @within Roblox - @tag must_use - - Serializes a place from a DataModel instance. - - This string can then be written to a file, or sent over the network. - - ### Example usage - - ```lua - local fs = require("@lune/fs") - local roblox = require("@lune/roblox") - - local placeFile = roblox.serializePlace(game) - fs.writeFile("filePath.rbxl", placeFile) - ``` - - @param dataModel The DataModel for the place to serialize - @param xml If the place should be serialized as xml or not. Defaults to `false`, meaning the place gets serialized using the binary format and not xml. -]=] -function roblox.serializePlace(dataModel: DataModel, xml: boolean?): string - return nil :: any -end - ---[=[ - @within Roblox - @tag must_use - - Serializes one or more instances as a model. - - This string can then be written to a file, or sent over the network. - - ### Example usage - - ```lua - local fs = require("@lune/fs") - local roblox = require("@lune/roblox") - - local modelFile = roblox.serializeModel({ instance1, instance2, ... }) - fs.writeFile("filePath.rbxm", modelFile) - ``` - - @param instances The array of instances to serialize - @param xml If the model should be serialized as xml or not. Defaults to `false`, meaning the model gets serialized using the binary format and not xml. -]=] -function roblox.serializeModel(instances: { Instance }, xml: boolean?): string - return nil :: any -end - ---[=[ - @within Roblox - @tag must_use - - Gets the current auth cookie, for usage with Roblox web APIs. - - Note that this auth cookie is formatted for use as a "Cookie" header, - and that it contains restrictions so that it may only be used for - official Roblox endpoints. To get the raw cookie value without any - additional formatting, you can pass `true` as the first and only parameter. - - ### Example usage - - ```lua - local roblox = require("@lune/roblox") - local net = require("@lune/net") - - local cookie = roblox.getAuthCookie() - assert(cookie ~= nil, "Failed to get roblox auth cookie") - - local myPrivatePlaceId = 1234567890 - - local response = net.request({ - url = "https://assetdelivery.roblox.com/v2/assetId/" .. tostring(myPrivatePlaceId), - headers = { - Cookie = cookie, - }, - }) - - local responseTable = net.jsonDecode(response.body) - local responseLocation = responseTable.locations[1].location - print("Download link to place: " .. responseLocation) - ``` - - @param raw If the cookie should be returned as a pure value or not. Defaults to false -]=] -function roblox.getAuthCookie(raw: boolean?): string? - return nil :: any -end - ---[=[ - @within Roblox - @tag must_use - - Gets the bundled reflection database. - - This database contains information about Roblox enums, classes, and their properties. - - ### Example usage - - ```lua - local roblox = require("@lune/roblox") - - local db = roblox.getReflectionDatabase() - - print("There are", #db:GetClassNames(), "classes in the reflection database") - - print("All base instance properties:") - - local class = db:GetClass("Instance") - for name, prop in class.Properties do - print(string.format( - "- %s with datatype %s and default value %s", - prop.Name, - prop.Datatype, - tostring(class.DefaultProperties[prop.Name]) - )) - end - ``` -]=] -function roblox.getReflectionDatabase(): Database - return nil :: any -end - ---[=[ - @within Roblox - - Implements a property for all instances of the given `className`. - - This takes into account class hierarchies, so implementing a property - for the `BasePart` class will also implement it for `Part` and others, - unless a more specific implementation is added to the `Part` class directly. - - ### Behavior - - The given `getter` callback will be called each time the property is - indexed, with the instance as its one and only argument. The `setter` - callback, if given, will be called each time the property should be set, - with the instance as the first argument and the property value as second. - - ### Example usage - - ```lua - local roblox = require("@lune/roblox") - - local part = roblox.Instance.new("Part") - - local propertyValues = {} - roblox.implementProperty( - "BasePart", - "CoolProp", - function(instance) - if propertyValues[instance] == nil then - propertyValues[instance] = 0 - end - propertyValues[instance] += 1 - return propertyValues[instance] - end, - function(instance, value) - propertyValues[instance] = value - end - ) - - print(part.CoolProp) --> 1 - print(part.CoolProp) --> 2 - print(part.CoolProp) --> 3 - - part.CoolProp = 10 - - print(part.CoolProp) --> 11 - print(part.CoolProp) --> 12 - print(part.CoolProp) --> 13 - ``` - - @param className The class to implement the property for. - @param propertyName The name of the property to implement. - @param getter The function which will be called to get the property value when indexed. - @param setter The function which will be called to set the property value when indexed. Defaults to a function that will error with a message saying the property is read-only. -]=] -function roblox.implementProperty( - className: string, - propertyName: string, - getter: (instance: Instance) -> T, - setter: ((instance: Instance, value: T) -> ())? -) - return nil :: any -end - ---[=[ - @within Roblox - - Implements a method for all instances of the given `className`. - - This takes into account class hierarchies, so implementing a method - for the `BasePart` class will also implement it for `Part` and others, - unless a more specific implementation is added to the `Part` class directly. - - ### Behavior - - The given `callback` will be called every time the method is called, - and will receive the instance it was called on as its first argument. - The remaining arguments will be what the caller passed to the method, and - all values returned from the callback will then be returned to the caller. - - ### Example usage - - ```lua - local roblox = require("@lune/roblox") - - local part = roblox.Instance.new("Part") - - roblox.implementMethod("BasePart", "TestMethod", function(instance, ...) - print("Called TestMethod on instance", instance, "with", ...) - end) - - part:TestMethod("Hello", "world!") - --> Called TestMethod on instance Part with Hello, world! - ``` - - @param className The class to implement the method for. - @param methodName The name of the method to implement. - @param callback The function which will be called when the method is called. -]=] -function roblox.implementMethod( - className: string, - methodName: string, - callback: (instance: Instance, ...any) -> ...any -) - return nil :: any -end - --- TODO: Make typedefs for all of the datatypes as well... -roblox.Instance = (nil :: any) :: { - new: ((className: "DataModel") -> DataModel) & ((className: string) -> Instance), -} - -return roblox diff --git a/samples/Luau/ser.luau b/samples/Luau/ser.luau new file mode 100644 index 0000000000..19116619e2 --- /dev/null +++ b/samples/Luau/ser.luau @@ -0,0 +1,181 @@ +--// Authored by @Sleitnick (https://github.com/sleitnick) +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/ser/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +--!optimize 2 +--!strict +--!native + +-- Ser +-- Stephen Leitnick +-- August 28, 2020 + +--[[ + + Ser is a serialization/deserialization utility module that is used + by Knit to automatically serialize/deserialize values passing + through remote functions and remote events. + + + Ser.Classes = { + [ClassName] = { + Serialize = (value) -> serializedValue + Deserialize = (value) => deserializedValue + } + } + + Ser.SerializeArgs(...) -> table + Ser.SerializeArgsAndUnpack(...) -> Tuple + Ser.DeserializeArgs(...) -> table + Ser.DeserializeArgsAndUnpack(...) -> Tuple + Ser.Serialize(value: any) -> any + Ser.Deserialize(value: any) -> any + Ser.UnpackArgs(args: table) -> Tuple + +--]] + +type Args = { + n: number, + [any]: any, +} + +local Option = require(script.Parent.Option) + +--[=[ + @class Ser + + Library for serializing and deserializing data. + + See the `Classes` property for information on extending the use + of the Ser library to include other classes. + +]=] +local Ser = {} + +--[=[ + @within Ser + @prop Classes table + + A dictionary of classes along with a Serialize and Deserialize function. + For instance, the default class added is the Option class, which looks + like the following: + + ```lua + Ser.Classes.Option = { + Serialize = function(opt) return opt:Serialize() end; + Deserialize = Option.Deserialize; + } + ``` + + Add to this table in order to extend what classes are automatically + serialized/deserialized. + + The Ser library checks every object's `ClassName` field in both serialized + and deserialized data in order to map it to the correct function within + the Classes table. +]=] +Ser.Classes = { + Option = { + Serialize = function(opt) + return opt:Serialize() + end, + Deserialize = Option.Deserialize, + }, +} + +--[=[ + @param ... any + @return args: table + Serializes the arguments and returns the serialized values in a table. +]=] +function Ser.SerializeArgs(...: any): Args + local args = table.pack(...) + for i, arg in ipairs(args) do + if type(arg) == "table" then + local ser = Ser.Classes[arg.ClassName] + if ser then + args[i] = ser.Serialize(arg) + end + end + end + return args +end + +--[=[ + @param ... any + @return args: ...any + Serializes the arguments and returns the serialized values. +]=] +function Ser.SerializeArgsAndUnpack(...: any): ...any + local args = Ser.SerializeArgs(...) + return table.unpack(args, 1, args.n) +end + +--[=[ + @param ... any + @return args: table + Deserializes the arguments and returns the deserialized values in a table. +]=] +function Ser.DeserializeArgs(...: any): Args + local args = table.pack(...) + for i, arg in ipairs(args) do + if type(arg) == "table" then + local ser = Ser.Classes[arg.ClassName] + if ser then + args[i] = ser.Deserialize(arg) + end + end + end + return args +end + +--[=[ + @param ... any + @return args: table + Deserializes the arguments and returns the deserialized values. +]=] +function Ser.DeserializeArgsAndUnpack(...: any): ...any + local args = Ser.DeserializeArgs(...) + return table.unpack(args, 1, args.n) +end + +--[=[ + @param value any + @return any + Serializes the given value. +]=] +function Ser.Serialize(value: any): any + if type(value) == "table" then + local ser = Ser.Classes[value.ClassName] + if ser then + value = ser.Serialize(value) + end + end + return value +end + +--[=[ + @param value any + @return any + Deserializes the given value. +]=] +function Ser.Deserialize(value: any): any + if type(value) == "table" then + local ser = Ser.Classes[value.ClassName] + if ser then + value = ser.Deserialize(value) + end + end + return value +end + +--[=[ + @param value any + @return any + Unpacks the arguments returned by either `SerializeArgs` or `DeserializeArgs`. +]=] +function Ser.UnpackArgs(value: Args): ...any + return table.unpack(value, 1, value.n) +end + +return Ser diff --git a/samples/Luau/waitFor.luau b/samples/Luau/waitFor.luau new file mode 100644 index 0000000000..28c17c044d --- /dev/null +++ b/samples/Luau/waitFor.luau @@ -0,0 +1,265 @@ +--// Authored by @Sleitnick (https://github.com/sleitnick) +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/wait-for/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +--!optimize 2 +--!strict +--!native + +-- WaitFor +-- Stephen Leitnick +-- January 17, 2022 + +local RunService = game:GetService("RunService") + +local Promise = require(script.Parent.Promise) + +local DEFAULT_TIMEOUT = 60 + +--[=[ + @class WaitFor + Utility class for awaiting the existence of instances. + + By default, all promises timeout after 60 seconds, unless the `timeout` + argument is specified. + + :::note + Promises will be rejected if the parent (or any ancestor) is unparented + from the game. + ::: + + :::caution Set name before parent + When waiting for instances based on name (e.g. `WaitFor.Child`), the `WaitFor` + system is listening to events to capture these instances being added. This + means that the name must be set _before_ being parented into the object. + ::: +]=] +local WaitFor = {} + +--[=[ + @within WaitFor + @prop Error {Unparented: string, ParentChanged: string} +]=] +WaitFor.Error = { + Unparented = "Unparented", + ParentChanged = "ParentChanged", +} + +local function PromiseWatchAncestry(instance: Instance, promise) + return Promise.race({ + promise, + Promise.fromEvent(instance.AncestryChanged, function(_, newParent) + return newParent == nil + end):andThen(function() + return Promise.reject(WaitFor.Error.Unparented) + end), + }) +end + +--[=[ + @return Promise + Wait for a child to exist within a given parent based on the child name. + + ```lua + WaitFor.Child(parent, "SomeObject"):andThen(function(someObject) + print(someObject, "now exists") + end):catch(warn) + ``` +]=] +function WaitFor.Child(parent: Instance, childName: string, timeout: number?) + local child = parent:FindFirstChild(childName) + if child then + return Promise.resolve(child) + end + return PromiseWatchAncestry( + parent, + Promise.fromEvent(parent.ChildAdded, function(c) + return c.Name == childName + end):timeout(timeout or DEFAULT_TIMEOUT) + ) +end + +--[=[ + @return Promise<{Instance}> + Wait for all children to exist within the given parent. + + ```lua + WaitFor.Children(parent, {"SomeObject01", "SomeObject02"}):andThen(function(children) + local someObject01, someObject02 = table.unpack(children) + end) + ``` + + :::note + Once all children are found, a second check is made to ensure that all children + are still directly parented to the given `parent` (since one child's parent + might have changed before another child was found). A rejected promise with the + `WaitFor.Error.ParentChanged` error will be thrown if any parents of the children + no longer match the given `parent`. + ::: +]=] +function WaitFor.Children(parent: Instance, childrenNames: { string }, timeout: number?) + local all = table.create(#childrenNames) + for i, childName in ipairs(childrenNames) do + all[i] = WaitFor.Child(parent, childName, timeout) + end + return Promise.all(all):andThen(function(children) + -- Check that all are still parented + for _, child in ipairs(children) do + if child.Parent ~= parent then + return Promise.reject(WaitFor.Error.ParentChanged) + end + end + return children + end) +end + +--[=[ + @return Promise + Wait for a descendant to exist within a given parent. This is similar to + `WaitFor.Child`, except it looks for all descendants instead of immediate + children. + + ```lua + WaitFor.Descendant(parent, "SomeDescendant"):andThen(function(someDescendant) + print("SomeDescendant now exists") + end) + ``` +]=] +function WaitFor.Descendant(parent: Instance, descendantName: string, timeout: number?) + local descendant = parent:FindFirstChild(descendantName, true) + if descendant then + return Promise.resolve(descendant) + end + return PromiseWatchAncestry( + parent, + Promise.fromEvent(parent.DescendantAdded, function(d) + return d.Name == descendantName + end):timeout(timeout or DEFAULT_TIMEOUT) + ) +end + +--[=[ + @return Promise<{Instance}> + Wait for all descendants to exist within a given parent. + + ```lua + WaitFor.Descendants(parent, {"SomeDescendant01", "SomeDescendant02"}):andThen(function(descendants) + local someDescendant01, someDescendant02 = table.unpack(descendants) + end) + ``` + + :::note + Once all descendants are found, a second check is made to ensure that none of the + instances have moved outside of the parent (since one instance might change before + another instance is found). A rejected promise with the `WaitFor.Error.ParentChanged` + error will be thrown if any of the instances are no longer descendants of the given + `parent`. + ::: +]=] +function WaitFor.Descendants(parent: Instance, descendantNames: { string }, timeout: number?) + local all = table.create(#descendantNames) + for i, descendantName in ipairs(descendantNames) do + all[i] = WaitFor.Descendant(parent, descendantName, timeout) + end + return Promise.all(all):andThen(function(descendants) + -- Check that all are still parented + for _, descendant in ipairs(descendants) do + if not descendant:IsDescendantOf(parent) then + return Promise.reject(WaitFor.Error.ParentChanged) + end + end + return descendants + end) +end + +--[=[ + @return Promise + Wait for the PrimaryPart of a model to exist. + + ```lua + WaitFor.PrimaryPart(model):andThen(function(primaryPart) + print(primaryPart == model.PrimaryPart) + end) + ``` +]=] +function WaitFor.PrimaryPart(model: Model, timeout: number?) + local primary = model.PrimaryPart + if primary then + return Promise.resolve(primary) + end + return PromiseWatchAncestry( + model, + Promise.fromEvent(model:GetPropertyChangedSignal("PrimaryPart"), function() + primary = model.PrimaryPart + return primary ~= nil + end) + :andThen(function() + return primary + end) + :timeout(timeout or DEFAULT_TIMEOUT) + ) +end + +--[=[ + @return Promise + Wait for the Value of an ObjectValue to exist. + + ```lua + WaitFor.ObjectValue(someObjectValue):andThen(function(value) + print("someObjectValue's value is", value) + end) + ``` +]=] +function WaitFor.ObjectValue(objectValue: ObjectValue, timeout: number?) + local value = objectValue.Value + if value then + return Promise.resolve(value) + end + return PromiseWatchAncestry( + objectValue, + Promise.fromEvent(objectValue.Changed, function(v) + value = v + return value ~= nil + end) + :andThen(function() + return value + end) + :timeout(timeout or DEFAULT_TIMEOUT) + ) +end + +--[=[ + @return Promise + Wait for the given predicate function to return a non-nil value of + of type `T`. The predicate is fired every RunService Heartbeat step. + + ```lua + -- Example, waiting for some property to be set: + WaitFor.Custom(function() return vectorForce.Attachment0 end):andThen(function(a0) + print(a0) + end) + ``` +]=] +function WaitFor.Custom(predicate: () -> T?, timeout: number?) + local value = predicate() + if value ~= nil then + return Promise.resolve(value) + end + return Promise.new(function(resolve, _reject, onCancel) + local heartbeat + local function OnDone() + heartbeat:Disconnect() + end + local function Update() + local v = predicate() + if v ~= nil then + OnDone() + resolve(v) + end + end + heartbeat = RunService.Heartbeat:Connect(Update) + onCancel(OnDone) + end):timeout(timeout or DEFAULT_TIMEOUT) +end + +return WaitFor From 481ae464c1f927bfc5527cae7a3d9cb03ef4424a Mon Sep 17 00:00:00 2001 From: robloxiandemo <76854027+robloxiandemo@users.noreply.github.com> Date: Wed, 1 May 2024 10:13:48 -0400 Subject: [PATCH 28/29] Revert "Patch: Update old samples and their sources." This reverts commit 2f9a8a20ea944b9f8290a1c0a73e400a89759398. Revert "Enhancement: Update old samples and their sources." This reverts commit d0053792c2016da0f8ea560a2202476e7e6c97c4. --- samples/Luau/EnumList.luau | 109 ------ samples/Luau/Maid.luau | 213 ------------ samples/Luau/createProcessor.luau | 22 ++ samples/Luau/createSignal.luau | 37 +++ samples/Luau/equals.luau | 17 + samples/Luau/getEnv.luau | 39 --- samples/Luau/isEmpty.luau | 21 ++ samples/Luau/none.luau | 21 ++ samples/Luau/replaceMacros.luau | 311 ++++++++++++++++++ samples/Luau/timeStepper.luau | 36 ++ samples/Luau/toSet.luau | 22 ++ vendor/CodeMirror | 2 +- vendor/grammars/Handlebars | 2 +- vendor/grammars/MATLAB-Language-grammar | 2 +- vendor/grammars/Nasal.tmbundle | 2 +- vendor/grammars/NimLime | 2 +- vendor/grammars/Terraform.tmLanguage | 2 +- vendor/grammars/TypeScript-TmLanguage | 2 +- vendor/grammars/VscodeAdblockSyntax | 2 +- vendor/grammars/abap-cds-grammar | 2 +- vendor/grammars/abl-tmlanguage | 2 +- vendor/grammars/aidl-language | 2 +- vendor/grammars/astro | 2 +- vendor/grammars/atom-language-julia | 2 +- vendor/grammars/atom-language-perl6 | 2 +- vendor/grammars/atomic-dreams | 1 - vendor/grammars/bicep | 2 +- vendor/grammars/bikeshed | 2 +- vendor/grammars/cds-textmate-grammar | 2 +- vendor/grammars/csharp-tmLanguage | 2 +- vendor/grammars/d.tmbundle | 2 +- vendor/grammars/d2-vscode | 2 +- vendor/grammars/dart-syntax-highlight | 2 +- vendor/grammars/denizenscript-grammar | 2 +- vendor/grammars/earthfile-grammar | 2 +- vendor/grammars/elixir-tmbundle | 2 +- vendor/grammars/elvish | 2 +- vendor/grammars/gemini-vscode | 2 +- vendor/grammars/genero.tmbundle | 1 - vendor/grammars/godot-vscode-plugin | 2 +- vendor/grammars/graphiql | 2 +- vendor/grammars/ionide-fsgrammar | 2 +- vendor/grammars/language-csound | 2 +- vendor/grammars/language-etc | 2 +- vendor/grammars/language-kotlin | 2 +- vendor/grammars/language-subtitles | 2 +- vendor/grammars/language-viml | 2 +- vendor/grammars/latex.tmbundle | 2 +- vendor/grammars/lua.tmbundle | 2 +- vendor/grammars/markdown-tm-language | 2 +- vendor/grammars/mint-vscode | 2 +- vendor/grammars/nu-grammar | 2 +- vendor/grammars/qsharp-compiler | 2 +- vendor/grammars/rescript-vscode | 2 +- vendor/grammars/rust-syntax | 2 +- vendor/grammars/sas.tmbundle | 2 +- vendor/grammars/selinux-policy-languages | 2 +- vendor/grammars/smithy-vscode | 2 +- vendor/grammars/sourcepawn-vscode | 2 +- vendor/grammars/sublime-odin | 2 +- vendor/grammars/sublime-pony | 2 +- vendor/grammars/sublime-q | 2 +- vendor/grammars/sway-vscode-plugin | 2 +- vendor/grammars/swift.tmbundle | 1 - .../grammars/vscode-antlers-language-server | 2 +- vendor/grammars/vscode-brightscript-language | 2 +- vendor/grammars/vscode-cadence | 2 +- vendor/grammars/vscode-codeql | 2 +- vendor/grammars/vscode-fluent | 2 +- vendor/grammars/vscode-go | 2 +- vendor/grammars/vscode-hack | 2 +- vendor/grammars/vscode-ibmi-languages | 2 +- vendor/grammars/vscode-jest | 2 +- vendor/grammars/vscode-lean | 2 +- vendor/grammars/vscode-motoko | 2 +- vendor/grammars/vscode-move-syntax | 2 +- vendor/grammars/vscode-opa | 2 +- vendor/grammars/vscode-pddl | 2 +- vendor/grammars/vscode-prisma | 2 +- vendor/grammars/vscode-procfile | 2 +- vendor/grammars/vscode-scala-syntax | 2 +- vendor/grammars/vscode-slice | 2 +- vendor/grammars/vscode_cobol | 2 +- vendor/grammars/wgsl-analyzer | 2 +- 84 files changed, 557 insertions(+), 434 deletions(-) delete mode 100644 samples/Luau/EnumList.luau delete mode 100644 samples/Luau/Maid.luau create mode 100644 samples/Luau/createProcessor.luau create mode 100644 samples/Luau/createSignal.luau create mode 100644 samples/Luau/equals.luau delete mode 100644 samples/Luau/getEnv.luau create mode 100644 samples/Luau/isEmpty.luau create mode 100644 samples/Luau/none.luau create mode 100644 samples/Luau/replaceMacros.luau create mode 100644 samples/Luau/timeStepper.luau create mode 100644 samples/Luau/toSet.luau delete mode 160000 vendor/grammars/atomic-dreams delete mode 160000 vendor/grammars/genero.tmbundle delete mode 160000 vendor/grammars/swift.tmbundle diff --git a/samples/Luau/EnumList.luau b/samples/Luau/EnumList.luau deleted file mode 100644 index 6b3403a9ed..0000000000 --- a/samples/Luau/EnumList.luau +++ /dev/null @@ -1,109 +0,0 @@ ---// Authored by @Sleitnick (https://github.com/sleitnick) ---// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/enum-list/init.lua) ---// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) - ---!optimize 2 ---!strict ---!native - --- EnumList --- Stephen Leitnick --- January 08, 2021 - -type EnumNames = { string } - ---[=[ - @interface EnumItem - .Name string - .Value number - .EnumType EnumList - @within EnumList -]=] -export type EnumItem = { - Name: string, - Value: number, - EnumType: any, -} - -local LIST_KEY = newproxy() -local NAME_KEY = newproxy() - -local function CreateEnumItem(name: string, value: number, enum: any): EnumItem - local enumItem = { - Name = name, - Value = value, - EnumType = enum, - } - table.freeze(enumItem) - return enumItem -end - ---[=[ - @class EnumList - Defines a new Enum. -]=] -local EnumList = {} -EnumList.__index = EnumList - ---[=[ - @param name string - @param enums {string} - @return EnumList - Constructs a new EnumList. - - ```lua - local directions = EnumList.new("Directions", { - "Up", - "Down", - "Left", - "Right", - }) - - local direction = directions.Up - ``` -]=] -function EnumList.new(name: string, enums: EnumNames) - assert(type(name) == "string", "Name string required") - assert(type(enums) == "table", "Enums table required") - local self = {} - self[LIST_KEY] = {} - self[NAME_KEY] = name - for i, enumName in ipairs(enums) do - assert(type(enumName) == "string", "Enum name must be a string") - local enumItem = CreateEnumItem(enumName, i, self) - self[enumName] = enumItem - table.insert(self[LIST_KEY], enumItem) - end - return table.freeze(setmetatable(self, EnumList)) -end - ---[=[ - @param obj any - @return boolean - Returns `true` if `obj` belongs to the EnumList. -]=] -function EnumList:BelongsTo(obj: any): boolean - return type(obj) == "table" and obj.EnumType == self -end - ---[=[ - Returns an array of all enum items. - @return {EnumItem} - @since v2.0.0 -]=] -function EnumList:GetEnumItems() - return self[LIST_KEY] -end - ---[=[ - Get the name of the enum. - @return string - @since v2.0.0 -]=] -function EnumList:GetName() - return self[NAME_KEY] -end - -export type EnumList = typeof(EnumList.new(...)) - -return EnumList diff --git a/samples/Luau/Maid.luau b/samples/Luau/Maid.luau deleted file mode 100644 index 0a6b91cd75..0000000000 --- a/samples/Luau/Maid.luau +++ /dev/null @@ -1,213 +0,0 @@ ---// Authored by @Dig1t (https://github.com/dig1t) ---// Fetched from (https://github.com/dig1t/dlib/blob/main/src/Maid.luau) ---// Licensed under the MIT License (https://github.com/dig1t/dlib/blob/main/LICENSE) - ---!optimize 2 ---!strict ---!native - -local Util = require(script.Parent.Util) - -type MaidClass = { - __index: MaidClass, - new: () -> MaidType, - task: (self: MaidType, task: any, cleaner: () -> any) -> string, - removeTask: (self: MaidType, taskToRemove: MaidTask) -> nil, - clean: (self: MaidType) -> nil, - destroy: (any) -> nil, - - MaidType: MaidType -} - -type MaidInstance = { - _tasks: { [string]: MaidTask }, - [any]: any -} - -export type MaidType = typeof(setmetatable( - {} :: MaidInstance, - {} :: MaidClass -)) - -type MaidTask = { - Connected: boolean?, - Disconnect: () -> nil?, - Destroy: (any) -> nil?, - destroy: (any) -> nil?, - destructor: (task: any) -> nil?, - [any]: any -}? | () -> nil? | Instance; - ---[=[ - Task management class for cleaning up things for garbage collection. - - @class Maid -]=] -local Maid: MaidClass = {} :: MaidClass -Maid.__index = Maid - ---[=[ - Creates a new Maid instance. - - ```lua - local maid = Maid.new() - ``` - - @return Maid -]=] -function Maid.new(): MaidType - local self = setmetatable({}, Maid) - - self._tasks = {} - - return self -end - ---[=[ - Adds a task to the Maid instance. - - ```lua - local maid = Maid.new() - - maid:task(function() - print("Hello world!") - end) - ``` - - Multiple types of tasks can be added to the Maid instance. - - ```lua - local maid = Maid.new() - - -- Functions - maid:task(function() - print("Hello world!") - end) - - -- RBXScriptConnections - maid:task(workspace.ChildAdded:Connect(function() - print("Hello world!") - end)) - - -- Instances with "Destroy" methods - maid:task(Instance.new("Part")) - - -- Packages with "Destroy", "destroy", or "destructor" methods - local instance = Class.new({ - PackageVariable = "Hello world!", - Destroy = function() - -- destroy this package instance - end - }) - - maid:task(instance) - ``` - - @param _task any -- The task to add. - @return string -- The task id. -]=] -function Maid:task(_task: MaidTask): string - local taskId = Util.randomString(14) - - self._tasks[taskId] = _task - - return taskId -end - ---[=[ - Removes a task from the Maid instance. - - ```lua - local maid = Maid.new() - - local taskId = maid:task(function() - print("Hello world!") - end) - - maid:removeTask(taskId) - ``` - - @param taskToRemove any -- The task item to remove. - @return nil -]=] -function Maid:removeTask(taskToRemove: string | MaidTask): nil - -- Remove by task id - if typeof(taskToRemove) == "string" then - self._tasks[taskToRemove] = nil - - return - end - - -- Remove by task - for taskId, _task: MaidTask in pairs(self._tasks) do - if _task == taskToRemove then - self._tasks[taskId] = nil - end - end - - return -end - ---[=[ - Cleans up all tasks in the Maid instance. - - ```lua - local maid: typeof(Maid.MaidType) = Maid.new() - - maid:task(function() - print("Hello world!") - end) - - maid:clean() -- Hello world! - ``` - - @return nil -]=] -function Maid:clean(): nil - for taskId, _task: MaidTask in pairs(self._tasks) do - if typeof(_task) == "function" then - _task() -- Run cleaning _task - elseif typeof(_task) == "RBXScriptConnection" and _task.Connected then - _task:Disconnect() - elseif typeof(_task) == "Instance" or (_task and _task.Destroy) then - _task:Destroy() - elseif _task and _task.destroy then - _task:destroy() - elseif typeof(_task) == "table" then - if _task.destructor then - _task.destructor(_task.task) - end - end - - self._tasks[taskId] = nil - end - - return -end - ---[=[ - Destroys the Maid instance. - - ```lua - local maid = Maid.new() - - maid:task(function() - print("Hello world!") - end) - - maid:destroy() - - maid:clean() -- method no longer exists - ``` - - @return nil -]=] -function Maid:destroy(): nil - for key: any, _ in pairs(self) do - self[key] = nil - end - - return nil -end - -return Maid diff --git a/samples/Luau/createProcessor.luau b/samples/Luau/createProcessor.luau new file mode 100644 index 0000000000..d55aba076e --- /dev/null +++ b/samples/Luau/createProcessor.luau @@ -0,0 +1,22 @@ +--// Authored by @sinlerdev (https://github.com/sinlerdev) +--// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/createProcessor.luau) +--// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) + +--!strict +local function createProcessor(valueChecker: (a: any, b: any) -> boolean, cleaner: (taskItem: any) -> ()) + return function(old: T, new: T, isDestroying: boolean) + if isDestroying then + cleaner(old) + return false + end + + if valueChecker(old, new) then + return false + end + + cleaner(old) + return true + end +end + +return createProcessor diff --git a/samples/Luau/createSignal.luau b/samples/Luau/createSignal.luau new file mode 100644 index 0000000000..5ffd461fff --- /dev/null +++ b/samples/Luau/createSignal.luau @@ -0,0 +1,37 @@ +--// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) +--// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/createSignal.luau) +--// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) + +--!strict + +local function createSignal() + local listeners = {} + local signal = {} + + function signal:Connect(listener) + listeners[listener] = true + + local connection = { + Connected = true, + } + + function connection.Disconnect() + connection.Connected = false + listeners[listener] = nil + end + connection.disconnect = connection.Disconnect + + return connection + end + signal.connect = signal.Connect + + local function fire(...) + for listener, _ in pairs(listeners) do + spawn(listener, ...) + end + end + + return signal, fire +end + +return createSignal diff --git a/samples/Luau/equals.luau b/samples/Luau/equals.luau new file mode 100644 index 0000000000..6b259e0359 --- /dev/null +++ b/samples/Luau/equals.luau @@ -0,0 +1,17 @@ +--// Authored by @sinlerdev (https://github.com/sinlerdev) +--// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/equals.luau) +--// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) + +--!strict + +local function equals(a: any, b: any): boolean + -- INFO: Vinum doesn't officially support immutability currently- so, we just assume + -- that every new table entry is not equal. See issue 26 + if type(a) == "table" then + return false + end + + return a == b +end + +return equals diff --git a/samples/Luau/getEnv.luau b/samples/Luau/getEnv.luau deleted file mode 100644 index d4877c3d17..0000000000 --- a/samples/Luau/getEnv.luau +++ /dev/null @@ -1,39 +0,0 @@ ---// Authored by @Vocksel (https://github.com/vocksel) ---// Fetched from (https://github.com/flipbook-labs/module-loader/blob/main/src/init.lua) ---// Licensed under the MIT License (https://github.com/flipbook-labs/module-loader/blob/main/LICENSE) - ---!optimize 2 ---!strict ---!native - -local baseEnv = getfenv() - -local function getEnv(scriptRelativeTo: LuaSourceContainer?, globals: { [any]: any }?) - local newEnv = {} - - setmetatable(newEnv, { - __index = function(_, key) - if key ~= "plugin" then - return baseEnv[key] - else - return nil - end - end, - }) - - newEnv._G = globals - newEnv.script = scriptRelativeTo - - local realDebug = debug - - newEnv.debug = setmetatable({ - traceback = function(message) - -- Block traces to prevent overly verbose TestEZ output - return message or "" - end, - }, { __index = realDebug }) - - return newEnv -end - -return getEnv diff --git a/samples/Luau/isEmpty.luau b/samples/Luau/isEmpty.luau new file mode 100644 index 0000000000..c90e654801 --- /dev/null +++ b/samples/Luau/isEmpty.luau @@ -0,0 +1,21 @@ +--// Authored by @benbrimeyer (https://github.com/benbrimeyer) +--// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/isEmpty.luau) +--// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) + +--!strict +--[=[ + Returns true if the collection is empty. + + ```lua + Freeze.isEmpty({}) + -- true + ``` + + @within Freeze + @function isEmpty + @return boolean +]=] + +return function(collection) + return next(collection) == nil +end diff --git a/samples/Luau/none.luau b/samples/Luau/none.luau new file mode 100644 index 0000000000..118a75d8ce --- /dev/null +++ b/samples/Luau/none.luau @@ -0,0 +1,21 @@ +--// Authored by @benbrimeyer (https://github.com/benbrimeyer) +--// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/None.luau) +--// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) + +--[=[ + @prop None None + @within Freeze + + Since lua tables cannot distinguish between values not being present and a value of nil, + `Freeze.None` exists to represent values that should be interpreted as `nil`. + + This is useful when removing values with functions such as [`Freeze.Dictionary.merge`](../api/Dictionary#merge). +]=] + +local None = newproxy(true) + +getmetatable(None).__tostring = function() + return "Freeze.None" +end + +return None diff --git a/samples/Luau/replaceMacros.luau b/samples/Luau/replaceMacros.luau new file mode 100644 index 0000000000..11daa0b368 --- /dev/null +++ b/samples/Luau/replaceMacros.luau @@ -0,0 +1,311 @@ +--// Authored by @grilme99 (https://github.com/grilme99) +--// Fetched from (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/scripts/replaceMacros.luau) +--// Licensed under the MIT License (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/LICENSE-Brooke) + +-- upstream: https://github.com/dead/typeflex/blob/422cb26/tools/repalce_macros.py + +local function YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) + local ret = [[ +local function YGNodeStyleSet##name(node: YGNode, paramName: type) + if node:getStyle().instanceName ~= paramName then + local style: YGStyle = node:getStyle() + style.instanceName = paramName + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##name = YGNodeStyleSet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL(type, name, paramName, instanceName) + local ret = [[ +local function YGNodeStyleSet##name(node: YGNode, paramName: type) + local value: YGValue = YGValue.new( + YGFloatSanitize(paramName), + if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point + ) + + if + (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) + or node:getStyle().instanceName.unit ~= value.unit + then + local style: YGStyle = node:getStyle() + style.instanceName = value + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##name = YGNodeStyleSet##name + +local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) + local value: YGValue = YGValue.new( + YGFloatSanitize(paramName), + if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent + ) + + if + (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) + or node:getStyle().instanceName.unit ~= value.unit + then + local style: YGStyle = node:getStyle() + style.instanceName = value + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL(type, name, paramName, instanceName) + local ret = [[ +local function YGNodeStyleSet##name(node: YGNode, paramName: type) + local value: YGValue = YGValue.new( + YGFloatSanitize(paramName), + if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point + ) + + if + (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) + or node:getStyle().instanceName.unit ~= value.unit + then + local style: YGStyle = node:getStyle() + style.instanceName = value + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##name = YGNodeStyleSet##name + +local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) + if + node:getStyle().instanceName.value ~= YGFloatSanitize(paramName) + or node:getStyle().instanceName.unit ~= YGUnit.Percent + then + local style: YGStyle = node:getStyle() + style.instanceName.value = YGFloatSanitize(paramName) + style.instanceName.unit = if YGFloatIsUndefined(paramName) then YGUnit.Auto else YGUnit.Percent + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent + +local function YGNodeStyleSet##nameAuto(node: YGNode) + if node:getStyle().instanceName.unit ~= YGUnit.Auto then + local style: YGStyle = node:getStyle() + style.instanceName.value = 0 + style.instanceName.unit = YGUnit.Auto + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_PROPERTY_IMPL(type, name, paramName, instanceName) + local ret = YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) + ret ..= [[ +local function YGNodeStyleGet##name(node: YGNode): type + return node:getStyle().instanceName +end +exports.YGNodeStyleGet##name = YGNodeStyleGet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) + local ret = [[ +local function YGNodeStyleSet##name(node: YGNode, edge: YGEdge, paramName: number) + local value: YGValue = YGValue.new( + YGFloatSanitize(paramName), + if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point + ) + + if + (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) + or node:getStyle().instanceName[edge].unit ~= value.unit + then + local style: YGStyle = node:getStyle() + style.instanceName[edge] = value + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##name = YGNodeStyleSet##name + +local function YGNodeStyleSet##namePercent(node: YGNode, edge: YGEdge, paramName: number) + local value: YGValue = YGValue.new( + YGFloatSanitize(paramName), + if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent + ) + + if + (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) + or node:getStyle().instanceName[edge].unit ~= value.unit + then + local style: YGStyle = node:getStyle() + style.instanceName[edge] = value + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent + +local function YGNodeStyleGet##name(node: YGNode, edge: YGEdge): type + local value: YGValue = node:getStyle().instanceName[edge] + if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then + value.value = YGUndefined + end + + return value +end +exports.YGNodeStyleGet##name = YGNodeStyleGet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL(type, name, instanceName) + local ret = [[ +local function YGNodeStyleSet##nameAuto(node: YGNode, edge: YGEdge) + if node:getStyle().instanceName[edge].unit ~= YGUnit.Auto then + local style: YGStyle = node:getStyle() + style.instanceName[edge].value = 0 + style.instanceName[edge].unit = YGUnit.Auto + node:setStyle(style) + node:markDirtyAndPropogate() + end +end +exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL(type, name, paramName, instanceName) + local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL("number", name, paramName, instanceName) + ret ..= [[ +local function YGNodeStyleGet##name(node: YGNode): type + local value: YGValue = node:getStyle().instanceName + if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then + value.value = YGUndefined + end + return value +end +exports.YGNodeStyleGet##name = YGNodeStyleGet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_STYLE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) + local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL("number", name, paramName, instanceName) + ret ..= [[ +local function YGNodeStyleGet##name(node: YGNode): type + local value: YGValue = node:getStyle().instanceName + if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then + value.value = YGUndefined + end + return value +end +exports.YGNodeStyleGet##name = YGNodeStyleGet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) +end + +local function YG_NODE_LAYOUT_PROPERTY_IMPL(type, name, instanceName) + local ret = [[ +local function YGNodeLayoutGet##name(node: YGNode): type + return node:getLayout().instanceName +end +exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) +end + +local function YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(type, name, instanceName) + local ret = [[ +local function YGNodeLayoutGet##name(node: YGNode, edge: YGEdge): type + -- YGAssertWithNode(node, edge <= YGEdge.End, "Cannot get layout properties of multi-edge shorthands") + + if edge == YGEdge.Start then + if node:getLayout().direction == YGDirection.RTL then + return node:getLayout().instanceName[YGEdge.Right] + else + return node:getLayout().instanceName[YGEdge.Left] + end + end + + if edge == YGEdge.End then + if node:getLayout().direction == YGDirection.RTL then + return node:getLayout().instanceName[YGEdge.Left] + else + return node:getLayout().instanceName[YGEdge.Right] + end + end + + return node:getLayout().instanceName[edge] +end +exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name + +]] + + return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) +end + +local cod = "" + +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDirection", "Direction", "direction", "direction") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGFlexDirection", "FlexDirection", "flexDirection", "flexDirection") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGJustify", "JustifyContent", "justifyContent", "justifyContent") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignContent", "alignContent", "alignContent") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignItems", "alignItems", "alignItems") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignSelf", "alignSelf", "alignSelf") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGPositionType", "PositionType", "positionType", "positionType") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGWrap", "FlexWrap", "flexWrap", "flexWrap") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGOverflow", "Overflow", "overflow", "overflow") +cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDisplay", "Display", "display", "display") +cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Position", "position", "position") +cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Margin", "margin", "margin") +cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Padding", "padding", "padding") +cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Margin", "margin") +cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Width", "width", "dimensions[YGDimension.Width]") +cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Height", "height", "dimensions[YGDimension.Height]") +cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinWidth", "minWidth", "minDimensions[YGDimension.Width]") +cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinHeight", "minHeight", "minDimensions[YGDimension.Height]") +cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxWidth", "maxWidth", "maxDimensions[YGDimension.Width]") +cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxHeight", "maxHeight", "maxDimensions[YGDimension.Height]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Left", "position[YGEdge.Left]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Top", "position[YGEdge.Top]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Right", "position[YGEdge.Right]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Bottom", "position[YGEdge.Bottom]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Width", "dimensions[YGDimension.Width]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Height", "dimensions[YGDimension.Height]") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("YGDirection", "Direction", "direction") +cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("boolean", "HadOverflow", "hadOverflow") +cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Margin", "margin") +cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Border", "border") +cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Padding", "padding") + +print(cod) \ No newline at end of file diff --git a/samples/Luau/timeStepper.luau b/samples/Luau/timeStepper.luau new file mode 100644 index 0000000000..038a614451 --- /dev/null +++ b/samples/Luau/timeStepper.luau @@ -0,0 +1,36 @@ +--// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) +--// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/TimeStepper.luau) +--// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) + +--!strict + +--[[ + +A time stepper is responsible for stepping systems in a deterministic order at a set interval. + +]] + +local TimeStepper = {} +TimeStepper.__index = TimeStepper + +function TimeStepper.new(interval: number, systems) + local self = setmetatable({ + _systems = systems, + _interval = interval, + }, TimeStepper) + + return self +end + +function TimeStepper:start() + coroutine.resume(coroutine.create(function() + while true do + local timeStep, _ = wait(self._interval) + for _, system in ipairs(self._systems) do + system:step(timeStep) + end + end + end)) +end + +return TimeStepper diff --git a/samples/Luau/toSet.luau b/samples/Luau/toSet.luau new file mode 100644 index 0000000000..7d18f99ed2 --- /dev/null +++ b/samples/Luau/toSet.luau @@ -0,0 +1,22 @@ +--// Authored by @benbrimeyer (https://github.com/benbrimeyer) +--// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/List/toSet.luau) +--// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) + +--!strict + +--[=[ + Returns a Set from the given List. + + @within List + @function toSet + @ignore +]=] +return function(list: { Value }): { [Value]: boolean } + local set = {} + + for _, v in list do + set[v] = true + end + + return set +end diff --git a/vendor/CodeMirror b/vendor/CodeMirror index 53faa33ac6..0c8456c3bc 160000 --- a/vendor/CodeMirror +++ b/vendor/CodeMirror @@ -1 +1 @@ -Subproject commit 53faa33ac69598b7495e160824b58ebb8d70fe97 +Subproject commit 0c8456c3bc92fb3085ac636f5ed117df24e22ca7 diff --git a/vendor/grammars/Handlebars b/vendor/grammars/Handlebars index c2c09947b6..9fb01fefe4 160000 --- a/vendor/grammars/Handlebars +++ b/vendor/grammars/Handlebars @@ -1 +1 @@ -Subproject commit c2c09947b6b83d740e9ee94a6e3a0199476f2e15 +Subproject commit 9fb01fefe48532deb901926cd80774215b454ff9 diff --git a/vendor/grammars/MATLAB-Language-grammar b/vendor/grammars/MATLAB-Language-grammar index c2c0e4ec8a..f3533822b2 160000 --- a/vendor/grammars/MATLAB-Language-grammar +++ b/vendor/grammars/MATLAB-Language-grammar @@ -1 +1 @@ -Subproject commit c2c0e4ec8a401b2c6f76e6069aa0b19da3d0b3bd +Subproject commit f3533822b2d740fd4128722854c98b9f1b5d07ee diff --git a/vendor/grammars/Nasal.tmbundle b/vendor/grammars/Nasal.tmbundle index 856bd56438..beb4c5bee4 160000 --- a/vendor/grammars/Nasal.tmbundle +++ b/vendor/grammars/Nasal.tmbundle @@ -1 +1 @@ -Subproject commit 856bd56438b2ca2a82676b461dcebbe74e75600c +Subproject commit beb4c5bee4e83de7cbf25c0cdc25e28489c8a1f7 diff --git a/vendor/grammars/NimLime b/vendor/grammars/NimLime index 6297c32c11..df69515d85 160000 --- a/vendor/grammars/NimLime +++ b/vendor/grammars/NimLime @@ -1 +1 @@ -Subproject commit 6297c32c11fd92eb9bc3c5cd09a9ff79b7f3d4f9 +Subproject commit df69515d853380cde7ad58c54e69cf52e725d3bb diff --git a/vendor/grammars/Terraform.tmLanguage b/vendor/grammars/Terraform.tmLanguage index 7faec10b40..1576c26c2a 160000 --- a/vendor/grammars/Terraform.tmLanguage +++ b/vendor/grammars/Terraform.tmLanguage @@ -1 +1 @@ -Subproject commit 7faec10b4057b95bff82fb585730e560871d26c4 +Subproject commit 1576c26c2ac215f9bed5e1f8a061f123bf63e6b2 diff --git a/vendor/grammars/TypeScript-TmLanguage b/vendor/grammars/TypeScript-TmLanguage index 82dcf60b94..4fdfd38727 160000 --- a/vendor/grammars/TypeScript-TmLanguage +++ b/vendor/grammars/TypeScript-TmLanguage @@ -1 +1 @@ -Subproject commit 82dcf60b94c305088165eff5c7e14b07f6048b22 +Subproject commit 4fdfd387273124944e32582f9f5af019f515158e diff --git a/vendor/grammars/VscodeAdblockSyntax b/vendor/grammars/VscodeAdblockSyntax index bbd868b428..b4a53bee21 160000 --- a/vendor/grammars/VscodeAdblockSyntax +++ b/vendor/grammars/VscodeAdblockSyntax @@ -1 +1 @@ -Subproject commit bbd868b4288bf03cb3a2551b5ef35b2103e5a70f +Subproject commit b4a53bee21c0f5074956a7377a36a45febf97d25 diff --git a/vendor/grammars/abap-cds-grammar b/vendor/grammars/abap-cds-grammar index 14d892fb0e..dd803e6ad2 160000 --- a/vendor/grammars/abap-cds-grammar +++ b/vendor/grammars/abap-cds-grammar @@ -1 +1 @@ -Subproject commit 14d892fb0eb8bd98b0340a2b6d265cbeeb938884 +Subproject commit dd803e6ad2eea202b537a87c0da0536b92f60d3e diff --git a/vendor/grammars/abl-tmlanguage b/vendor/grammars/abl-tmlanguage index e928116b41..bc85d5f1b0 160000 --- a/vendor/grammars/abl-tmlanguage +++ b/vendor/grammars/abl-tmlanguage @@ -1 +1 @@ -Subproject commit e928116b4138bcf191b6f64be5bf92ef9c345d75 +Subproject commit bc85d5f1b0e10ca4a3eb6946d5426d2560a4e328 diff --git a/vendor/grammars/aidl-language b/vendor/grammars/aidl-language index ce07cdf6b9..c2c0074c59 160000 --- a/vendor/grammars/aidl-language +++ b/vendor/grammars/aidl-language @@ -1 +1 @@ -Subproject commit ce07cdf6b9b9dbc4857c4b2fc59a55ac0e17ecd4 +Subproject commit c2c0074c593954bcd27036db118aee6a13abfa20 diff --git a/vendor/grammars/astro b/vendor/grammars/astro index 1e84fac22f..1a55b6f122 160000 --- a/vendor/grammars/astro +++ b/vendor/grammars/astro @@ -1 +1 @@ -Subproject commit 1e84fac22fb3ed6686ec4d5f963a0420ba2d7d82 +Subproject commit 1a55b6f122f35d1e08716627fad6c8bcfeedab2f diff --git a/vendor/grammars/atom-language-julia b/vendor/grammars/atom-language-julia index 134a10664d..0735fb8026 160000 --- a/vendor/grammars/atom-language-julia +++ b/vendor/grammars/atom-language-julia @@ -1 +1 @@ -Subproject commit 134a10664d7b9010af432f4042637c16fd61fe2b +Subproject commit 0735fb802696a2b6d89e12ba302d0a85d901bc17 diff --git a/vendor/grammars/atom-language-perl6 b/vendor/grammars/atom-language-perl6 index 8c196e381e..bd341e83c1 160000 --- a/vendor/grammars/atom-language-perl6 +++ b/vendor/grammars/atom-language-perl6 @@ -1 +1 @@ -Subproject commit 8c196e381edb0e5575db611bad4ca7de8b437d66 +Subproject commit bd341e83c14a69b68cb304fb6931abed8473b05a diff --git a/vendor/grammars/atomic-dreams b/vendor/grammars/atomic-dreams deleted file mode 160000 index 14891385ac..0000000000 --- a/vendor/grammars/atomic-dreams +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 14891385ac0110cdcdb22f2dc1a8930cdf501c91 diff --git a/vendor/grammars/bicep b/vendor/grammars/bicep index d5f69a7dd2..0c0394b9f7 160000 --- a/vendor/grammars/bicep +++ b/vendor/grammars/bicep @@ -1 +1 @@ -Subproject commit d5f69a7dd2e38820b1c8af0fc7ca009a406f6c66 +Subproject commit 0c0394b9f70f59b4a2d31dc9cc9b0ba03493685f diff --git a/vendor/grammars/bikeshed b/vendor/grammars/bikeshed index 4cd224458e..584813e638 160000 --- a/vendor/grammars/bikeshed +++ b/vendor/grammars/bikeshed @@ -1 +1 @@ -Subproject commit 4cd224458e3672f5aa8d521d8ad0ac5658cf1a1c +Subproject commit 584813e6380533a19c6656594c810bf974854e68 diff --git a/vendor/grammars/cds-textmate-grammar b/vendor/grammars/cds-textmate-grammar index b6a736eabd..d538952370 160000 --- a/vendor/grammars/cds-textmate-grammar +++ b/vendor/grammars/cds-textmate-grammar @@ -1 +1 @@ -Subproject commit b6a736eabd7d11ef9a1863cfbc0965e1d9b18c0b +Subproject commit d5389523709ba010cf1b9de78c6c0478bab31bde diff --git a/vendor/grammars/csharp-tmLanguage b/vendor/grammars/csharp-tmLanguage index 1fc58ea221..7a7482ffc7 160000 --- a/vendor/grammars/csharp-tmLanguage +++ b/vendor/grammars/csharp-tmLanguage @@ -1 +1 @@ -Subproject commit 1fc58ea2212577cd5249f4cd540a07942a1e5806 +Subproject commit 7a7482ffc72a6677a87eb1ed76005593a4f7f131 diff --git a/vendor/grammars/d.tmbundle b/vendor/grammars/d.tmbundle index e031d03ce0..9fb354be1c 160000 --- a/vendor/grammars/d.tmbundle +++ b/vendor/grammars/d.tmbundle @@ -1 +1 @@ -Subproject commit e031d03ce0c2fe0f9e064dad1faf670a19bde482 +Subproject commit 9fb354be1c3fbb6a91f543f584d47099d338baf0 diff --git a/vendor/grammars/d2-vscode b/vendor/grammars/d2-vscode index 0b4e66fc23..d722a70f36 160000 --- a/vendor/grammars/d2-vscode +++ b/vendor/grammars/d2-vscode @@ -1 +1 @@ -Subproject commit 0b4e66fc235c494a7bf7423d0b9eab3d7bf92833 +Subproject commit d722a70f36e634790eb60e86b678577af0e569c6 diff --git a/vendor/grammars/dart-syntax-highlight b/vendor/grammars/dart-syntax-highlight index 6ec2cb7fa6..1b307d29d4 160000 --- a/vendor/grammars/dart-syntax-highlight +++ b/vendor/grammars/dart-syntax-highlight @@ -1 +1 @@ -Subproject commit 6ec2cb7fa65c0e0bed366223920171c0a02db29d +Subproject commit 1b307d29d454a382f1c143de516335b1a885484b diff --git a/vendor/grammars/denizenscript-grammar b/vendor/grammars/denizenscript-grammar index 069cb69265..a9a9523202 160000 --- a/vendor/grammars/denizenscript-grammar +++ b/vendor/grammars/denizenscript-grammar @@ -1 +1 @@ -Subproject commit 069cb69265ec500e690e8df20a58addc968fbc31 +Subproject commit a9a95232026192c0d5eb23557882f9b69a40d66e diff --git a/vendor/grammars/earthfile-grammar b/vendor/grammars/earthfile-grammar index 4afcb6f2cb..3ab3ea3b51 160000 --- a/vendor/grammars/earthfile-grammar +++ b/vendor/grammars/earthfile-grammar @@ -1 +1 @@ -Subproject commit 4afcb6f2cb55d560d94d9ab2b827b6a784d08428 +Subproject commit 3ab3ea3b512ce54fb71f8070c7e03926ef259c03 diff --git a/vendor/grammars/elixir-tmbundle b/vendor/grammars/elixir-tmbundle index b01fffc491..f6867d6aee 160000 --- a/vendor/grammars/elixir-tmbundle +++ b/vendor/grammars/elixir-tmbundle @@ -1 +1 @@ -Subproject commit b01fffc49179bdec936ca19b53ba4fc7c51a2cc0 +Subproject commit f6867d6aee89c23a9803d958c62eef1c60170e28 diff --git a/vendor/grammars/elvish b/vendor/grammars/elvish index 1c0cffbfed..eadae7fc65 160000 --- a/vendor/grammars/elvish +++ b/vendor/grammars/elvish @@ -1 +1 @@ -Subproject commit 1c0cffbfed892aa7129ae0a19dad1617255a9faa +Subproject commit eadae7fc651345f925e6e412628e1a463954ae5f diff --git a/vendor/grammars/gemini-vscode b/vendor/grammars/gemini-vscode index 172351ff9e..72ef757a0c 160000 --- a/vendor/grammars/gemini-vscode +++ b/vendor/grammars/gemini-vscode @@ -1 +1 @@ -Subproject commit 172351ff9e2cbf1296c29ab1ea475cb0fa3344c2 +Subproject commit 72ef757a0cb5d1e7137edd97579a92588f7e54cb diff --git a/vendor/grammars/genero.tmbundle b/vendor/grammars/genero.tmbundle deleted file mode 160000 index ea111e21c5..0000000000 --- a/vendor/grammars/genero.tmbundle +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ea111e21c5424b09330197696428cfe1dee65df4 diff --git a/vendor/grammars/godot-vscode-plugin b/vendor/grammars/godot-vscode-plugin index acfcfdbdab..5cef963162 160000 --- a/vendor/grammars/godot-vscode-plugin +++ b/vendor/grammars/godot-vscode-plugin @@ -1 +1 @@ -Subproject commit acfcfdbdabff9bed65024d3807ed3c53f5e3a29d +Subproject commit 5cef96316208d76795c9763291889b92f2d84d4b diff --git a/vendor/grammars/graphiql b/vendor/grammars/graphiql index 57b5fcd874..ece99f63f5 160000 --- a/vendor/grammars/graphiql +++ b/vendor/grammars/graphiql @@ -1 +1 @@ -Subproject commit 57b5fcd8749bd1c54f4a6a04a9ab07316fd05e71 +Subproject commit ece99f63f5d8d01057b735e90a6957edea3e42b9 diff --git a/vendor/grammars/ionide-fsgrammar b/vendor/grammars/ionide-fsgrammar index 8740e610a3..7d029a46f1 160000 --- a/vendor/grammars/ionide-fsgrammar +++ b/vendor/grammars/ionide-fsgrammar @@ -1 +1 @@ -Subproject commit 8740e610a367c5e3f15be716acc7207655ced4cf +Subproject commit 7d029a46f17637228b2ee85dd02e511c3e8039b3 diff --git a/vendor/grammars/language-csound b/vendor/grammars/language-csound index 975436138f..55beb6eab9 160000 --- a/vendor/grammars/language-csound +++ b/vendor/grammars/language-csound @@ -1 +1 @@ -Subproject commit 975436138f2ab808e4a2c3897586ccb0b3c958c2 +Subproject commit 55beb6eab9ad3783336fbefac52d759245954bc1 diff --git a/vendor/grammars/language-etc b/vendor/grammars/language-etc index 6d176400d6..1fd00541cc 160000 --- a/vendor/grammars/language-etc +++ b/vendor/grammars/language-etc @@ -1 +1 @@ -Subproject commit 6d176400d627b0c723180c46bec862b078250401 +Subproject commit 1fd00541cc0cd25b319c2a749d55e0144a91451c diff --git a/vendor/grammars/language-kotlin b/vendor/grammars/language-kotlin index bad0234aa6..b65a18d4c2 160000 --- a/vendor/grammars/language-kotlin +++ b/vendor/grammars/language-kotlin @@ -1 +1 @@ -Subproject commit bad0234aa658d1eb7e18881f71930fd7d70701a4 +Subproject commit b65a18d4c23eedcd2bc280cd04282d29d191721a diff --git a/vendor/grammars/language-subtitles b/vendor/grammars/language-subtitles index 70c9d731c1..82cf7686f8 160000 --- a/vendor/grammars/language-subtitles +++ b/vendor/grammars/language-subtitles @@ -1 +1 @@ -Subproject commit 70c9d731c1ba24f058c6630824cf1188cbee9843 +Subproject commit 82cf7686f8f15c19c80e612c8b7da57d87eeb5b7 diff --git a/vendor/grammars/language-viml b/vendor/grammars/language-viml index 5030985ab9..979e1c6223 160000 --- a/vendor/grammars/language-viml +++ b/vendor/grammars/language-viml @@ -1 +1 @@ -Subproject commit 5030985ab9f5a84a3a18d999d7c3a82f581b0283 +Subproject commit 979e1c62230bbf29fd6963e3620028ccba6848ad diff --git a/vendor/grammars/latex.tmbundle b/vendor/grammars/latex.tmbundle index 8e472548d1..90d3383ff8 160000 --- a/vendor/grammars/latex.tmbundle +++ b/vendor/grammars/latex.tmbundle @@ -1 +1 @@ -Subproject commit 8e472548d189604712739a925dbdd735069e0668 +Subproject commit 90d3383ff86b7f4495e33c7c5240dde7308376ee diff --git a/vendor/grammars/lua.tmbundle b/vendor/grammars/lua.tmbundle index 8ae5641365..94ce82cc4d 160000 --- a/vendor/grammars/lua.tmbundle +++ b/vendor/grammars/lua.tmbundle @@ -1 +1 @@ -Subproject commit 8ae5641365b28f697121ba1133890e8d81f5b00e +Subproject commit 94ce82cc4d45f82641a5252d7a7fd9e28c875adc diff --git a/vendor/grammars/markdown-tm-language b/vendor/grammars/markdown-tm-language index e14b65157a..371d61df9d 160000 --- a/vendor/grammars/markdown-tm-language +++ b/vendor/grammars/markdown-tm-language @@ -1 +1 @@ -Subproject commit e14b65157a0b738430309b14b2f025f3bb37526a +Subproject commit 371d61df9ddc3850e12aabe61b602d02e259e8a4 diff --git a/vendor/grammars/mint-vscode b/vendor/grammars/mint-vscode index 4ea454c6be..2de0c6ae29 160000 --- a/vendor/grammars/mint-vscode +++ b/vendor/grammars/mint-vscode @@ -1 +1 @@ -Subproject commit 4ea454c6beff96deccb7abfc6cf04464b3ce8963 +Subproject commit 2de0c6ae292a3b23d71fa2eebfbb48f0a0cfb66b diff --git a/vendor/grammars/nu-grammar b/vendor/grammars/nu-grammar index 03a70db4a8..1ee4b15bd2 160000 --- a/vendor/grammars/nu-grammar +++ b/vendor/grammars/nu-grammar @@ -1 +1 @@ -Subproject commit 03a70db4a8cb3a0e2113fc63c48b8780b0cecd0d +Subproject commit 1ee4b15bd214c951b75270d55c3e273b486f0a75 diff --git a/vendor/grammars/qsharp-compiler b/vendor/grammars/qsharp-compiler index ce9622e4e4..1b4270217a 160000 --- a/vendor/grammars/qsharp-compiler +++ b/vendor/grammars/qsharp-compiler @@ -1 +1 @@ -Subproject commit ce9622e4e49c0fc5e4e887509e1651ac884c1a04 +Subproject commit 1b4270217aff846fb8d2d1f24094bd7bb36514a5 diff --git a/vendor/grammars/rescript-vscode b/vendor/grammars/rescript-vscode index 59855be802..69bfb269cd 160000 --- a/vendor/grammars/rescript-vscode +++ b/vendor/grammars/rescript-vscode @@ -1 +1 @@ -Subproject commit 59855be8021f78e6c24e98ecccdb303498381c5f +Subproject commit 69bfb269cde531e2d2cd3ad8e6768ae4a7d62d1f diff --git a/vendor/grammars/rust-syntax b/vendor/grammars/rust-syntax index 8f2bc1cb0c..cf3c686a50 160000 --- a/vendor/grammars/rust-syntax +++ b/vendor/grammars/rust-syntax @@ -1 +1 @@ -Subproject commit 8f2bc1cb0cf329f1292248bc546b605f01695308 +Subproject commit cf3c686a50295380ce9994218138691f8767870c diff --git a/vendor/grammars/sas.tmbundle b/vendor/grammars/sas.tmbundle index 7a70af2284..9d84eddcbf 160000 --- a/vendor/grammars/sas.tmbundle +++ b/vendor/grammars/sas.tmbundle @@ -1 +1 @@ -Subproject commit 7a70af22840d2fe04e17634685bab7b529613822 +Subproject commit 9d84eddcbffb86df3091fc88d0983bc33ec358a0 diff --git a/vendor/grammars/selinux-policy-languages b/vendor/grammars/selinux-policy-languages index 7bd17d9c91..502c96ccf3 160000 --- a/vendor/grammars/selinux-policy-languages +++ b/vendor/grammars/selinux-policy-languages @@ -1 +1 @@ -Subproject commit 7bd17d9c9160cd8168a159c6cc574d45cb67b125 +Subproject commit 502c96ccf38476a627897209bf726fd4e40296b8 diff --git a/vendor/grammars/smithy-vscode b/vendor/grammars/smithy-vscode index f205110486..43b3416a4d 160000 --- a/vendor/grammars/smithy-vscode +++ b/vendor/grammars/smithy-vscode @@ -1 +1 @@ -Subproject commit f205110486d3bac531b0d655992d96d3fd928401 +Subproject commit 43b3416a4ddedb57c3992b831bed6346e974d7d3 diff --git a/vendor/grammars/sourcepawn-vscode b/vendor/grammars/sourcepawn-vscode index 88a48c8263..339ceeb9d3 160000 --- a/vendor/grammars/sourcepawn-vscode +++ b/vendor/grammars/sourcepawn-vscode @@ -1 +1 @@ -Subproject commit 88a48c8263acd92cc877d624b3918cecfcb18d03 +Subproject commit 339ceeb9d3c3c61706a743d14284821f33f04d26 diff --git a/vendor/grammars/sublime-odin b/vendor/grammars/sublime-odin index 1746bce31b..c4b626f17c 160000 --- a/vendor/grammars/sublime-odin +++ b/vendor/grammars/sublime-odin @@ -1 +1 @@ -Subproject commit 1746bce31b2872906a5fab6282d0855dbefae9a1 +Subproject commit c4b626f17c97542c922b55a33618aa953cff4771 diff --git a/vendor/grammars/sublime-pony b/vendor/grammars/sublime-pony index 13f1b55340..80f6c4dfbf 160000 --- a/vendor/grammars/sublime-pony +++ b/vendor/grammars/sublime-pony @@ -1 +1 @@ -Subproject commit 13f1b55340ddd46c7dfeac6446030dfe8d105f90 +Subproject commit 80f6c4dfbff2edc4e8546b33c983f4314de06630 diff --git a/vendor/grammars/sublime-q b/vendor/grammars/sublime-q index c4f98f62b4..236b80c1f3 160000 --- a/vendor/grammars/sublime-q +++ b/vendor/grammars/sublime-q @@ -1 +1 @@ -Subproject commit c4f98f62b4911d27611c883c0428694dc984126d +Subproject commit 236b80c1f32fbcb16d9ee7487a6f35b62a946af0 diff --git a/vendor/grammars/sway-vscode-plugin b/vendor/grammars/sway-vscode-plugin index 3b3b13357b..a6bb690d0a 160000 --- a/vendor/grammars/sway-vscode-plugin +++ b/vendor/grammars/sway-vscode-plugin @@ -1 +1 @@ -Subproject commit 3b3b13357b5a5cdc3b353a90e48f500c19767009 +Subproject commit a6bb690d0a9839450971eae22a0f207c654126a5 diff --git a/vendor/grammars/swift.tmbundle b/vendor/grammars/swift.tmbundle deleted file mode 160000 index aa73c3410d..0000000000 --- a/vendor/grammars/swift.tmbundle +++ /dev/null @@ -1 +0,0 @@ -Subproject commit aa73c3410d26dea8d39c834fd8e9bec4319dad2c diff --git a/vendor/grammars/vscode-antlers-language-server b/vendor/grammars/vscode-antlers-language-server index 25b1f58a94..bff5066893 160000 --- a/vendor/grammars/vscode-antlers-language-server +++ b/vendor/grammars/vscode-antlers-language-server @@ -1 +1 @@ -Subproject commit 25b1f58a9429697b4a1631324ad8583dfa64270b +Subproject commit bff50668934abb41b197d75650a13fb5e300132e diff --git a/vendor/grammars/vscode-brightscript-language b/vendor/grammars/vscode-brightscript-language index bdae42c2e4..b05f3a31a8 160000 --- a/vendor/grammars/vscode-brightscript-language +++ b/vendor/grammars/vscode-brightscript-language @@ -1 +1 @@ -Subproject commit bdae42c2e4a42ec8504aa29e6bbbbf8e6d8755a2 +Subproject commit b05f3a31a8877a8fa0e00ff554a3e98328050c44 diff --git a/vendor/grammars/vscode-cadence b/vendor/grammars/vscode-cadence index 650029d38c..2ce921581a 160000 --- a/vendor/grammars/vscode-cadence +++ b/vendor/grammars/vscode-cadence @@ -1 +1 @@ -Subproject commit 650029d38c6ac2cddb7ce9fd4054461f4b140f5a +Subproject commit 2ce921581a804fa32317b829c54c1c0fd904975a diff --git a/vendor/grammars/vscode-codeql b/vendor/grammars/vscode-codeql index 93fc90fcd0..3005dacf4e 160000 --- a/vendor/grammars/vscode-codeql +++ b/vendor/grammars/vscode-codeql @@ -1 +1 @@ -Subproject commit 93fc90fcd081f8d049440f3575e1bbbe74ff2db7 +Subproject commit 3005dacf4ed480aa76e541b7d3697f5a34571faf diff --git a/vendor/grammars/vscode-fluent b/vendor/grammars/vscode-fluent index cae702ed1b..51ceeb59d6 160000 --- a/vendor/grammars/vscode-fluent +++ b/vendor/grammars/vscode-fluent @@ -1 +1 @@ -Subproject commit cae702ed1b3a6a2b6a58260a8e1194bc40421218 +Subproject commit 51ceeb59d645d0d1ec9b4080175bc1ce4d709075 diff --git a/vendor/grammars/vscode-go b/vendor/grammars/vscode-go index d9015c19ed..b4b68a76fa 160000 --- a/vendor/grammars/vscode-go +++ b/vendor/grammars/vscode-go @@ -1 +1 @@ -Subproject commit d9015c19ed5be58bb51f3c53b651fe2468540086 +Subproject commit b4b68a76fac190ca12179ef9ef24ecffc57ea9d6 diff --git a/vendor/grammars/vscode-hack b/vendor/grammars/vscode-hack index d75dd72a5d..dd79d7146c 160000 --- a/vendor/grammars/vscode-hack +++ b/vendor/grammars/vscode-hack @@ -1 +1 @@ -Subproject commit d75dd72a5d52436d208a627a2ead5423c94eb3e9 +Subproject commit dd79d7146ccffb3e0a1020e9f8790242c37b6c21 diff --git a/vendor/grammars/vscode-ibmi-languages b/vendor/grammars/vscode-ibmi-languages index e9d72cece7..7ed423b793 160000 --- a/vendor/grammars/vscode-ibmi-languages +++ b/vendor/grammars/vscode-ibmi-languages @@ -1 +1 @@ -Subproject commit e9d72cece714ae968802802f09bdc089f79d7d4b +Subproject commit 7ed423b7930e652f8b3fdf36d3f77d6e122ced41 diff --git a/vendor/grammars/vscode-jest b/vendor/grammars/vscode-jest index ad719cb5b9..0c57873f91 160000 --- a/vendor/grammars/vscode-jest +++ b/vendor/grammars/vscode-jest @@ -1 +1 @@ -Subproject commit ad719cb5b9509715047f1cae8ea7204db5f8faa5 +Subproject commit 0c57873f91138961f90fedeb5fd1f0da9d45c601 diff --git a/vendor/grammars/vscode-lean b/vendor/grammars/vscode-lean index 0696b29285..1589ca3a65 160000 --- a/vendor/grammars/vscode-lean +++ b/vendor/grammars/vscode-lean @@ -1 +1 @@ -Subproject commit 0696b292855c7cae711e00d69c79b2687c336f25 +Subproject commit 1589ca3a65e394b3789409707febbd2d166c9344 diff --git a/vendor/grammars/vscode-motoko b/vendor/grammars/vscode-motoko index 4ecca55b8d..1d256baa0c 160000 --- a/vendor/grammars/vscode-motoko +++ b/vendor/grammars/vscode-motoko @@ -1 +1 @@ -Subproject commit 4ecca55b8dd1fcebce92c80e67f0dcaf9ac4fdf3 +Subproject commit 1d256baa0cfec3cb3083c519d926a0453f201623 diff --git a/vendor/grammars/vscode-move-syntax b/vendor/grammars/vscode-move-syntax index de9244e815..f9436ad72c 160000 --- a/vendor/grammars/vscode-move-syntax +++ b/vendor/grammars/vscode-move-syntax @@ -1 +1 @@ -Subproject commit de9244e81505ff9154dc9422dbce434ac86fb981 +Subproject commit f9436ad72cc7d3c6848eb02759c808ae0663295d diff --git a/vendor/grammars/vscode-opa b/vendor/grammars/vscode-opa index 0282a611d1..dcf4476992 160000 --- a/vendor/grammars/vscode-opa +++ b/vendor/grammars/vscode-opa @@ -1 +1 @@ -Subproject commit 0282a611d1c3e70ffc353fd2003f0027a1c9ccfe +Subproject commit dcf447699243ef2984d8dcb0b009b35b3fe74400 diff --git a/vendor/grammars/vscode-pddl b/vendor/grammars/vscode-pddl index 4fc7fc6d1a..73b0cb1b72 160000 --- a/vendor/grammars/vscode-pddl +++ b/vendor/grammars/vscode-pddl @@ -1 +1 @@ -Subproject commit 4fc7fc6d1a5f1378ea5039f77e8074da1a882918 +Subproject commit 73b0cb1b72c72ff795f173ba18e9f4ae810b0250 diff --git a/vendor/grammars/vscode-prisma b/vendor/grammars/vscode-prisma index 0b8d95ba11..bf28282fd0 160000 --- a/vendor/grammars/vscode-prisma +++ b/vendor/grammars/vscode-prisma @@ -1 +1 @@ -Subproject commit 0b8d95ba11276ba694b73dcf568a747d13a882e5 +Subproject commit bf28282fd07c675b79282cafb63b4912bc6c7156 diff --git a/vendor/grammars/vscode-procfile b/vendor/grammars/vscode-procfile index b1ddcf5c34..4e149fd5e7 160000 --- a/vendor/grammars/vscode-procfile +++ b/vendor/grammars/vscode-procfile @@ -1 +1 @@ -Subproject commit b1ddcf5c349acaa005fabdcf7111f34443d552f3 +Subproject commit 4e149fd5e757352aa5867b59934acc16e19182a1 diff --git a/vendor/grammars/vscode-scala-syntax b/vendor/grammars/vscode-scala-syntax index d9036418a5..fb73e8a0bf 160000 --- a/vendor/grammars/vscode-scala-syntax +++ b/vendor/grammars/vscode-scala-syntax @@ -1 +1 @@ -Subproject commit d9036418a5e1f5c0cb7e3225e7bd05753d4934d6 +Subproject commit fb73e8a0bfcd9a3c45f2c1b712e00c35865a9178 diff --git a/vendor/grammars/vscode-slice b/vendor/grammars/vscode-slice index 45660f594c..a4aacfa8bc 160000 --- a/vendor/grammars/vscode-slice +++ b/vendor/grammars/vscode-slice @@ -1 +1 @@ -Subproject commit 45660f594c4dc3b4eaf9f14b5a6cd7a5ce01aee2 +Subproject commit a4aacfa8bc1ec6cc925dfd891dd75dfbf9df207d diff --git a/vendor/grammars/vscode_cobol b/vendor/grammars/vscode_cobol index 8bded749c0..b09f64b00e 160000 --- a/vendor/grammars/vscode_cobol +++ b/vendor/grammars/vscode_cobol @@ -1 +1 @@ -Subproject commit 8bded749c0cf77ffd665b73afbe7ff8b7bc7f938 +Subproject commit b09f64b00eee66dca496a69ea68f4c2c5c78e359 diff --git a/vendor/grammars/wgsl-analyzer b/vendor/grammars/wgsl-analyzer index 92219fd398..8851962fc1 160000 --- a/vendor/grammars/wgsl-analyzer +++ b/vendor/grammars/wgsl-analyzer @@ -1 +1 @@ -Subproject commit 92219fd3987158b8399d0b2c1dfb426af77f9c51 +Subproject commit 8851962fc191aa5ea85a25b0ebd10cb9f70627b3 From 7bfe7ca27a4e45bc44e67ae6c94793728e6a6be1 Mon Sep 17 00:00:00 2001 From: robloxiandemo <76854027+robloxiandemo@users.noreply.github.com> Date: Wed, 1 May 2024 12:07:08 -0400 Subject: [PATCH 29/29] Test: New samples, sadly one source. --- samples/Luau/EnumList.luau | 116 +++++++++++ samples/Luau/Option.luau | 229 ++++++++++++++++++++++ samples/Luau/Symbol.luau | 61 ++++++ samples/Luau/Tree.luau | 146 ++++++++++++++ samples/Luau/createProcessor.luau | 22 --- samples/Luau/createSignal.luau | 37 ---- samples/Luau/equals.luau | 17 -- samples/Luau/isEmpty.luau | 21 -- samples/Luau/none.luau | 21 -- samples/Luau/replaceMacros.luau | 311 ------------------------------ samples/Luau/ser.luau | 23 +-- samples/Luau/timeStepper.luau | 36 ---- samples/Luau/toSet.luau | 22 --- samples/Luau/waitFor.luau | 265 ------------------------- 14 files changed, 562 insertions(+), 765 deletions(-) create mode 100644 samples/Luau/EnumList.luau create mode 100644 samples/Luau/Option.luau create mode 100644 samples/Luau/Symbol.luau create mode 100644 samples/Luau/Tree.luau delete mode 100644 samples/Luau/createProcessor.luau delete mode 100644 samples/Luau/createSignal.luau delete mode 100644 samples/Luau/equals.luau delete mode 100644 samples/Luau/isEmpty.luau delete mode 100644 samples/Luau/none.luau delete mode 100644 samples/Luau/replaceMacros.luau delete mode 100644 samples/Luau/timeStepper.luau delete mode 100644 samples/Luau/toSet.luau delete mode 100644 samples/Luau/waitFor.luau diff --git a/samples/Luau/EnumList.luau b/samples/Luau/EnumList.luau new file mode 100644 index 0000000000..b96d931570 --- /dev/null +++ b/samples/Luau/EnumList.luau @@ -0,0 +1,116 @@ +--!optimize 2 +--!strict +--!native + +--// EnumList v2.1.0 +--// Authored by @sleitnick and modified by @robloxiandemo +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/enum-list/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +type EnumNames = { string } + +--[=[ + @interface EnumItem + .Name string + .Value number + .EnumType EnumList + @within EnumList +]=] +export type EnumItem = { + Name: string, + Value: number, + EnumType: any, +} + +local LIST_KEY = newproxy() +local NAME_KEY = newproxy() + +local function makeReadOnly(_table: ITable) + return setmetatable({}, { + __index = _table, + __newindex = function() + error("Attempt to modify read-only table", 2) + end, + __metatable = false, + }) +end + +local function CreateEnumItem(name: string, value: number, enum: any): EnumItem + local enumItem = { + Name = name, + Value = value, + EnumType = enum, + } + makeReadOnly(enumItem) + return enumItem +end + +--[=[ + @class EnumList + Defines a new Enum. +]=] +local EnumList = {} +EnumList.__index = EnumList + +--[=[ + @param name string + @param enums {string} + @return EnumList + Constructs a new EnumList. + + ```lua + local directions = EnumList.new("Directions", { + "Up", + "Down", + "Left", + "Right", + }) + + local direction = directions.Up + ``` +]=] +function EnumList.new(name: string, enums: EnumNames) + assert(type(name) == "string", "Name string required") + assert(type(enums) == "table", "Enums table required") + local self = {} + self[LIST_KEY] = {} + self[NAME_KEY] = name + for i, enumName in ipairs(enums) do + assert(type(enumName) == "string", "Enum name must be a string") + local enumItem = CreateEnumItem(enumName, i, self) + self[enumName] = enumItem + table.insert(self[LIST_KEY], enumItem) + end + return makeReadOnly(setmetatable(self, EnumList)) +end + +--[=[ + @param obj any + @return boolean + Returns `true` if `obj` belongs to the EnumList. +]=] +function EnumList:BelongsTo(obj: any): boolean + return type(obj) == "table" and obj.EnumType == self +end + +--[=[ + Returns an array of all enum items. + @return {EnumItem} + @since v2.0.0 +]=] +function EnumList:GetEnumItems() + return self[LIST_KEY] +end + +--[=[ + Get the name of the enum. + @return string + @since v2.0.0 +]=] +function EnumList:GetName() + return self[NAME_KEY] +end + +export type EnumList = typeof(EnumList.new(...)) + +return EnumList diff --git a/samples/Luau/Option.luau b/samples/Luau/Option.luau new file mode 100644 index 0000000000..90dcb91be7 --- /dev/null +++ b/samples/Luau/Option.luau @@ -0,0 +1,229 @@ +--!optimize 2 +--!strict +--!native + +--// EnumList v1.0.5 +--// Authored by @sleitnick and modified by @robloxiandemo +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/option/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +export type MatchTable = { + Some: (value: T) -> any, + None: () -> any, +} + +export type MatchFn = (matches: MatchTable) -> any + +export type DefaultFn = () -> T + +export type AndThenFn = (value: T) -> Option + +export type OrElseFn = () -> Option + +export type Option = typeof(setmetatable( + {} :: { + Match: (self: Option) -> MatchFn, + IsSome: (self: Option) -> boolean, + IsNone: (self: Option) -> boolean, + Contains: (self: Option, value: T) -> boolean, + Unwrap: (self: Option) -> T, + Expect: (self: Option, errMsg: string) -> T, + ExpectNone: (self: Option, errMsg: string) -> nil, + UnwrapOr: (self: Option, default: T) -> T, + UnwrapOrElse: (self: Option, defaultFn: DefaultFn) -> T, + And: (self: Option, opt2: Option) -> Option, + AndThen: (self: Option, predicate: AndThenFn) -> Option, + Or: (self: Option, opt2: Option) -> Option, + OrElse: (self: Option, orElseFunc: OrElseFn) -> Option, + XOr: (self: Option, opt2: Option) -> Option, + }, + {} :: { + __index: Option, + } +)) + +local CLASSNAME = "Option" + +local Option = {} +Option.__index = Option + +function Option._new(value) + local self = setmetatable({ + ClassName = CLASSNAME, + _v = value, + _s = (value ~= nil), + }, Option) + return self +end + +function Option.Some(value) + assert(value ~= nil, "Option.Some() value cannot be nil") + return Option._new(value) +end + +function Option.Wrap(value) + if value == nil then + return Option.None + else + return Option.Some(value) + end +end + +function Option.Is(obj) + return type(obj) == "table" and getmetatable(obj) == Option +end + +function Option.Assert(obj) + assert(Option.Is(obj), "Result was not of type Option") +end + +function Option.Deserialize(data) -- type data = {ClassName: string, Value: any} + assert(type(data) == "table" and data.ClassName == CLASSNAME, "Invalid data for deserializing Option") + return data.Value == nil and Option.None or Option.Some(data.Value) +end + +function Option:Serialize() + return { + ClassName = self.ClassName, + Value = self._v, + } +end + +function Option:Match(matches) + local onSome = matches.Some + local onNone = matches.None + assert(type(onSome) == "function", "Missing 'Some' match") + assert(type(onNone) == "function", "Missing 'None' match") + if self:IsSome() then + return onSome(self:Unwrap()) + else + return onNone() + end +end + +function Option:IsSome() + return self._s +end + +function Option:IsNone() + return not self._s +end + +function Option:Expect(msg) + assert(self:IsSome(), msg) + return self._v +end + +function Option:ExpectNone(msg) + assert(self:IsNone(), msg) +end + +function Option:Unwrap() + return self:Expect("Cannot unwrap option of None type") +end + +function Option:UnwrapOr(default) + if self:IsSome() then + return self:Unwrap() + else + return default + end +end + +function Option:UnwrapOrElse(defaultFn) + if self:IsSome() then + return self:Unwrap() + else + return defaultFn() + end +end + +function Option:And(optionB) + if self:IsSome() then + return optionB + else + return Option.None + end +end + +function Option:AndThen(andThenFn) + if self:IsSome() then + local result = andThenFn(self:Unwrap()) + Option.Assert(result) + return result + else + return Option.None + end +end + +function Option:Or(optionB) + if self:IsSome() then + return self + else + return optionB + end +end + +function Option:OrElse(orElseFn) + if self:IsSome() then + return self + else + local result = orElseFn() + Option.Assert(result) + return result + end +end + +function Option:XOr(optionB) + local someOptA = self:IsSome() + local someOptB = optionB:IsSome() + if someOptA == someOptB then + return Option.None + elseif someOptA then + return self + else + return optionB + end +end + +function Option:Filter(predicate) + if self:IsNone() or not predicate(self._v) then + return Option.None + else + return self + end +end + +function Option:Contains(value) + return self:IsSome() and self._v == value +end + +function Option:__tostring() + if self:IsSome() then + return ("Option<" .. type(self._v) .. ">") + else + return "Option" + end +end + +function Option:__eq(opt) + if Option.Is(opt) then + if self:IsSome() and opt:IsSome() then + return (self:Unwrap() == opt:Unwrap()) + elseif self:IsNone() and opt:IsNone() then + return true + end + end + return false +end + +Option.None = Option._new() + +return (Option :: any) :: { + Some: (value: T) -> Option, + Wrap: (value: T) -> Option, + + Is: (obj: any) -> boolean, + + None: Option, +} diff --git a/samples/Luau/Symbol.luau b/samples/Luau/Symbol.luau new file mode 100644 index 0000000000..22bb01c596 --- /dev/null +++ b/samples/Luau/Symbol.luau @@ -0,0 +1,61 @@ +--!optimize 2 +--!strict +--!native + +--// Symbol v2.0.1 +--// Authored by @sleitnick and modified by @robloxiandemo +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/symbol/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +--[=[ + @class Symbol + + Represents a unique object. + + Symbols are often used as unique keys in tables. This is useful to avoid possible collisions + with a key in a table, since the symbol will always be unique and cannot be reconstructed. + + + :::note All Unique + Every creation of a symbol is unique, even if the + given names are the same. + ::: + + ```lua + local Symbol = require(packages.Symbol) + + -- Create a symbol: + local symbol = Symbol("MySymbol") + + -- The name is optional: + local anotherSymbol = Symbol() + + -- Comparison: + print(symbol == symbol) --> true + + -- All symbol constructions are unique, regardless of the name: + print(Symbol("Hello") == Symbol("Hello")) --> false + + -- Commonly used as unique keys in a table: + local DATA_KEY = Symbol("Data") + local t = { + -- Can only be accessed using the DATA_KEY symbol: + [DATA_KEY] = {} + } + + print(t[DATA_KEY]) --> {} + ``` +]=] + +local function Symbol(name: string?) + local symbol = newproxy(true) + if not name then + name = "" + end + getmetatable(symbol).__tostring = function() + return "Symbol(" .. name .. ")" + end + return symbol +end + +return Symbol diff --git a/samples/Luau/Tree.luau b/samples/Luau/Tree.luau new file mode 100644 index 0000000000..ecf000bd2d --- /dev/null +++ b/samples/Luau/Tree.luau @@ -0,0 +1,146 @@ +--!optimize 2 +--!strict +--!native + +--// Tree v1.1.0 +--// Authored by @sleitnick and modified by @robloxiandemo +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/tree/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) + +local DELIM = "/" + +local function FullNameToPath(instance: Instance): string + return instance:GetFullName():gsub("%.", DELIM) +end + +--[=[ + @class Tree +]=] +local Tree = {} + +--[=[ + Similar to FindFirstChild, with a few key differences: + - An error is thrown if the instance is not found + - A path to the instance can be provided, delimited by forward slashes (e.g. `Path/To/Child`) + - Optionally, the instance's type can be asserted using `IsA` + + ```lua + -- Find "Child" directly under parent: + local instance = Tree.Find(parent, "Child") + + -- Find "Child" descendant: + local instance = Tree.Find(parent, "Path/To/Child") + + -- Find "Child" descendant and assert that it's a BasePart: + local instance = Tree.Find(parent, "Path/To/Child", "BasePart") :: BasePart + ``` +]=] +function Tree.Find(parent: Instance, path: string, assertIsA: string?): Instance + local instance = parent + local paths = path:split(DELIM) + + for _, p in paths do + -- Error for empty path parts: + if p == "" then + error(`Invalid path: {path}`, 2) + end + + instance = instance:FindFirstChild(p) + + -- Error if instance is not found: + if instance == nil then + error(`Failed to find {path} in {FullNameToPath(parent)}`, 2) + end + end + + -- Assert class type if argument is supplied: + if assertIsA and not instance:IsA(assertIsA) then + error(`Got class {instance.ClassName}; expected to be of type {assertIsA}`, 2) + end + + return instance +end + +--[=[ + Returns `true` if the instance is found. Similar to `Tree.Find`, except this returns `true|false`. No error is thrown unless the path is invalid. + + ```lua + -- Check if "Child" exists directly in `parent`: + if Tree.Exists(parent, "Child") then ... end + + -- Check if "Child" descendant exists at `parent.Path.To.Child`: + if Tree.Exists(parent, "Path/To/Child") then ... end + + -- Check if "Child" descendant exists at `parent.Path.To.Child` and is a BasePart: + if Tree.Exists(parent, "Path/To/Child", "BasePart") then ... end + ``` +]=] +function Tree.Exists(parent: Instance, path: string, assertIsA: string?): boolean + local instance = parent + local paths = path:split(DELIM) + + for _, p in paths do + -- Error for empty path parts: + if p == "" then + error(`Invalid path: {path}`, 2) + end + + instance = instance:FindFirstChild(p) + + if instance == nil then + return false + end + end + + if assertIsA and not instance:IsA(assertIsA) then + return false + end + + return true +end + +--[=[ + @yields + Waits for the path to exist within the parent instance. Similar to `Tree.Find`, except `WaitForChild` + is used internally. An optional `timeout` can be supplied, which is passed along to each call to + `WaitForChild`. + + An error is thrown if the path fails to resolve. This will only happen if the path is invalid _or_ if + the supplied timeout is reached. + + :::caution Indefinite Yield Possible + If the `timeout` parameter is not supplied, then the internal call to `WaitForChild` will yield + indefinitely until the child is found. It is good practice to supply a timeout parameter. + ::: + + ```lua + local child = Tree.Await(parent, "Path/To/Child", 30) + ``` +]=] +function Tree.Await(parent: Instance, path: string, timeout: number?, assertIsA: string?): Instance + local instance = parent + local paths = path:split(DELIM) + + for _, p in paths do + -- Error for empty path parts: + if p == "" then + error(`Invalid path: {path}`, 2) + end + + instance = instance:WaitForChild(p, timeout) + + -- Error if instance is not found: + if instance == nil then + error(`Failed to await {path} in {FullNameToPath(parent)} (timeout reached)`, 2) + end + end + + -- Assert class type if argument is supplied: + if assertIsA and not instance:IsA(assertIsA) then + error(`Got class {instance.ClassName}; expected to be of type {assertIsA}`, 2) + end + + return instance +end + +return Tree diff --git a/samples/Luau/createProcessor.luau b/samples/Luau/createProcessor.luau deleted file mode 100644 index d55aba076e..0000000000 --- a/samples/Luau/createProcessor.luau +++ /dev/null @@ -1,22 +0,0 @@ ---// Authored by @sinlerdev (https://github.com/sinlerdev) ---// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/createProcessor.luau) ---// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) - ---!strict -local function createProcessor(valueChecker: (a: any, b: any) -> boolean, cleaner: (taskItem: any) -> ()) - return function(old: T, new: T, isDestroying: boolean) - if isDestroying then - cleaner(old) - return false - end - - if valueChecker(old, new) then - return false - end - - cleaner(old) - return true - end -end - -return createProcessor diff --git a/samples/Luau/createSignal.luau b/samples/Luau/createSignal.luau deleted file mode 100644 index 5ffd461fff..0000000000 --- a/samples/Luau/createSignal.luau +++ /dev/null @@ -1,37 +0,0 @@ ---// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) ---// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/createSignal.luau) ---// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) - ---!strict - -local function createSignal() - local listeners = {} - local signal = {} - - function signal:Connect(listener) - listeners[listener] = true - - local connection = { - Connected = true, - } - - function connection.Disconnect() - connection.Connected = false - listeners[listener] = nil - end - connection.disconnect = connection.Disconnect - - return connection - end - signal.connect = signal.Connect - - local function fire(...) - for listener, _ in pairs(listeners) do - spawn(listener, ...) - end - end - - return signal, fire -end - -return createSignal diff --git a/samples/Luau/equals.luau b/samples/Luau/equals.luau deleted file mode 100644 index 6b259e0359..0000000000 --- a/samples/Luau/equals.luau +++ /dev/null @@ -1,17 +0,0 @@ ---// Authored by @sinlerdev (https://github.com/sinlerdev) ---// Fetched from (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/src/Utils/equals.luau) ---// Licensed under the MIT License (https://github.com/vinum-team/Vinum/blob/b80a6c194e6901f9d400968293607218598e1386/LICENSE) - ---!strict - -local function equals(a: any, b: any): boolean - -- INFO: Vinum doesn't officially support immutability currently- so, we just assume - -- that every new table entry is not equal. See issue 26 - if type(a) == "table" then - return false - end - - return a == b -end - -return equals diff --git a/samples/Luau/isEmpty.luau b/samples/Luau/isEmpty.luau deleted file mode 100644 index c90e654801..0000000000 --- a/samples/Luau/isEmpty.luau +++ /dev/null @@ -1,21 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/isEmpty.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---!strict ---[=[ - Returns true if the collection is empty. - - ```lua - Freeze.isEmpty({}) - -- true - ``` - - @within Freeze - @function isEmpty - @return boolean -]=] - -return function(collection) - return next(collection) == nil -end diff --git a/samples/Luau/none.luau b/samples/Luau/none.luau deleted file mode 100644 index 118a75d8ce..0000000000 --- a/samples/Luau/none.luau +++ /dev/null @@ -1,21 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/None.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---[=[ - @prop None None - @within Freeze - - Since lua tables cannot distinguish between values not being present and a value of nil, - `Freeze.None` exists to represent values that should be interpreted as `nil`. - - This is useful when removing values with functions such as [`Freeze.Dictionary.merge`](../api/Dictionary#merge). -]=] - -local None = newproxy(true) - -getmetatable(None).__tostring = function() - return "Freeze.None" -end - -return None diff --git a/samples/Luau/replaceMacros.luau b/samples/Luau/replaceMacros.luau deleted file mode 100644 index 11daa0b368..0000000000 --- a/samples/Luau/replaceMacros.luau +++ /dev/null @@ -1,311 +0,0 @@ ---// Authored by @grilme99 (https://github.com/grilme99) ---// Fetched from (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/scripts/replaceMacros.luau) ---// Licensed under the MIT License (https://github.com/grilme99/Flow/blob/973ac39fe52de9ec9407d7ae3d9ab62867b9e982/LICENSE-Brooke) - --- upstream: https://github.com/dead/typeflex/blob/422cb26/tools/repalce_macros.py - -local function YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - if node:getStyle().instanceName ~= paramName then - local style: YGStyle = node:getStyle() - style.instanceName = paramName - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, paramName: type) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName.value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName.unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, paramName: type) - if - node:getStyle().instanceName.value ~= YGFloatSanitize(paramName) - or node:getStyle().instanceName.unit ~= YGUnit.Percent - then - local style: YGStyle = node:getStyle() - style.instanceName.value = YGFloatSanitize(paramName) - style.instanceName.unit = if YGFloatIsUndefined(paramName) then YGUnit.Auto else YGUnit.Percent - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -local function YGNodeStyleSet##nameAuto(node: YGNode) - if node:getStyle().instanceName.unit ~= YGUnit.Auto then - local style: YGStyle = node:getStyle() - style.instanceName.value = 0 - style.instanceName.unit = YGUnit.Auto - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_IMPL(type, name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - return node:getStyle().instanceName -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) - local ret = [[ -local function YGNodeStyleSet##name(node: YGNode, edge: YGEdge, paramName: number) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Point - ) - - if - (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName[edge].unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName[edge] = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##name = YGNodeStyleSet##name - -local function YGNodeStyleSet##namePercent(node: YGNode, edge: YGEdge, paramName: number) - local value: YGValue = YGValue.new( - YGFloatSanitize(paramName), - if YGFloatIsUndefined(paramName) then YGUnit.Undefined else YGUnit.Percent - ) - - if - (node:getStyle().instanceName[edge].value ~= value.value and value.unit ~= YGUnit.Undefined) - or node:getStyle().instanceName[edge].unit ~= value.unit - then - local style: YGStyle = node:getStyle() - style.instanceName[edge] = value - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##namePercent = YGNodeStyleSet##namePercent - -local function YGNodeStyleGet##name(node: YGNode, edge: YGEdge): type - local value: YGValue = node:getStyle().instanceName[edge] - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeStyleSet##nameAuto(node: YGNode, edge: YGEdge) - if node:getStyle().instanceName[edge].unit ~= YGUnit.Auto then - local style: YGStyle = node:getStyle() - style.instanceName[edge].value = 0 - style.instanceName[edge].unit = YGUnit.Auto - node:setStyle(style) - node:markDirtyAndPropogate() - end -end -exports.YGNodeStyleSet##nameAuto = YGNodeStyleSet##nameAuto - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_AUTO_IMPL("number", name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - local value: YGValue = node:getStyle().instanceName - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_STYLE_PROPERTY_UNIT_IMPL(type, name, paramName, instanceName) - local ret = YG_NODE_STYLE_PROPERTY_SETTER_UNIT_IMPL("number", name, paramName, instanceName) - ret ..= [[ -local function YGNodeStyleGet##name(node: YGNode): type - local value: YGValue = node:getStyle().instanceName - if value.unit == YGUnit.Undefined or value.unit == YGUnit.Auto then - value.value = YGUndefined - end - return value -end -exports.YGNodeStyleGet##name = YGNodeStyleGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("paramName", paramName):gsub("instanceName", instanceName) -end - -local function YG_NODE_LAYOUT_PROPERTY_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeLayoutGet##name(node: YGNode): type - return node:getLayout().instanceName -end -exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local function YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL(type, name, instanceName) - local ret = [[ -local function YGNodeLayoutGet##name(node: YGNode, edge: YGEdge): type - -- YGAssertWithNode(node, edge <= YGEdge.End, "Cannot get layout properties of multi-edge shorthands") - - if edge == YGEdge.Start then - if node:getLayout().direction == YGDirection.RTL then - return node:getLayout().instanceName[YGEdge.Right] - else - return node:getLayout().instanceName[YGEdge.Left] - end - end - - if edge == YGEdge.End then - if node:getLayout().direction == YGDirection.RTL then - return node:getLayout().instanceName[YGEdge.Left] - else - return node:getLayout().instanceName[YGEdge.Right] - end - end - - return node:getLayout().instanceName[edge] -end -exports.YGNodeLayoutGet##name = YGNodeLayoutGet##name - -]] - - return ret:gsub("type", type):gsub("##name", name):gsub("instanceName", instanceName) -end - -local cod = "" - -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDirection", "Direction", "direction", "direction") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGFlexDirection", "FlexDirection", "flexDirection", "flexDirection") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGJustify", "JustifyContent", "justifyContent", "justifyContent") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignContent", "alignContent", "alignContent") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignItems", "alignItems", "alignItems") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGAlign", "AlignSelf", "alignSelf", "alignSelf") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGPositionType", "PositionType", "positionType", "positionType") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGWrap", "FlexWrap", "flexWrap", "flexWrap") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGOverflow", "Overflow", "overflow", "overflow") -cod ..= YG_NODE_STYLE_PROPERTY_IMPL("YGDisplay", "Display", "display", "display") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Position", "position", "position") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Margin", "margin", "margin") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_IMPL("YGValue", "Padding", "padding", "padding") -cod ..= YG_NODE_STYLE_EDGE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Margin", "margin") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Width", "width", "dimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_AUTO_IMPL("YGValue", "Height", "height", "dimensions[YGDimension.Height]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinWidth", "minWidth", "minDimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MinHeight", "minHeight", "minDimensions[YGDimension.Height]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxWidth", "maxWidth", "maxDimensions[YGDimension.Width]") -cod ..= YG_NODE_STYLE_PROPERTY_UNIT_IMPL("YGValue", "MaxHeight", "maxHeight", "maxDimensions[YGDimension.Height]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Left", "position[YGEdge.Left]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Top", "position[YGEdge.Top]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Right", "position[YGEdge.Right]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Bottom", "position[YGEdge.Bottom]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Width", "dimensions[YGDimension.Width]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("number", "Height", "dimensions[YGDimension.Height]") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("YGDirection", "Direction", "direction") -cod ..= YG_NODE_LAYOUT_PROPERTY_IMPL("boolean", "HadOverflow", "hadOverflow") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Margin", "margin") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Border", "border") -cod ..= YG_NODE_LAYOUT_RESOLVED_PROPERTY_IMPL("number", "Padding", "padding") - -print(cod) \ No newline at end of file diff --git a/samples/Luau/ser.luau b/samples/Luau/ser.luau index 19116619e2..62e02fae17 100644 --- a/samples/Luau/ser.luau +++ b/samples/Luau/ser.luau @@ -1,14 +1,11 @@ ---// Authored by @Sleitnick (https://github.com/sleitnick) ---// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/ser/init.lua) ---// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) - --!optimize 2 --!strict --!native --- Ser --- Stephen Leitnick --- August 28, 2020 +--// Ser v1.0.5 +--// Authored by @sleitnick and modified by @robloxiandemo +--// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/ser/init.lua) +--// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) --[[ @@ -39,7 +36,7 @@ type Args = { [any]: any, } -local Option = require(script.Parent.Option) +local Option = require("samples/Luau/Option.luau") --[=[ @class Ser @@ -89,7 +86,7 @@ Ser.Classes = { Serializes the arguments and returns the serialized values in a table. ]=] function Ser.SerializeArgs(...: any): Args - local args = table.pack(...) + local args = { (...) } for i, arg in ipairs(args) do if type(arg) == "table" then local ser = Ser.Classes[arg.ClassName] @@ -108,7 +105,7 @@ end ]=] function Ser.SerializeArgsAndUnpack(...: any): ...any local args = Ser.SerializeArgs(...) - return table.unpack(args, 1, args.n) + return unpack(args, 1, args.n) end --[=[ @@ -117,7 +114,7 @@ end Deserializes the arguments and returns the deserialized values in a table. ]=] function Ser.DeserializeArgs(...: any): Args - local args = table.pack(...) + local args = { (...) } for i, arg in ipairs(args) do if type(arg) == "table" then local ser = Ser.Classes[arg.ClassName] @@ -136,7 +133,7 @@ end ]=] function Ser.DeserializeArgsAndUnpack(...: any): ...any local args = Ser.DeserializeArgs(...) - return table.unpack(args, 1, args.n) + return unpack(args, 1, args.n) end --[=[ @@ -175,7 +172,7 @@ end Unpacks the arguments returned by either `SerializeArgs` or `DeserializeArgs`. ]=] function Ser.UnpackArgs(value: Args): ...any - return table.unpack(value, 1, value.n) + return unpack(value, 1, value.n) end return Ser diff --git a/samples/Luau/timeStepper.luau b/samples/Luau/timeStepper.luau deleted file mode 100644 index 038a614451..0000000000 --- a/samples/Luau/timeStepper.luau +++ /dev/null @@ -1,36 +0,0 @@ ---// Authored by @AmaranthineCodices (https://github.com/AmaranthineCodices) ---// Fetched from (https://github.com/Floral-Abyss/recs/blob/be123afef8f1fddbd9464b7d34d5178393601ae3/recs/TimeStepper.luau) ---// Licensed under the MIT License (https://github.com/Floral-Abyss/recs/blob/master/LICENSE.md) - ---!strict - ---[[ - -A time stepper is responsible for stepping systems in a deterministic order at a set interval. - -]] - -local TimeStepper = {} -TimeStepper.__index = TimeStepper - -function TimeStepper.new(interval: number, systems) - local self = setmetatable({ - _systems = systems, - _interval = interval, - }, TimeStepper) - - return self -end - -function TimeStepper:start() - coroutine.resume(coroutine.create(function() - while true do - local timeStep, _ = wait(self._interval) - for _, system in ipairs(self._systems) do - system:step(timeStep) - end - end - end)) -end - -return TimeStepper diff --git a/samples/Luau/toSet.luau b/samples/Luau/toSet.luau deleted file mode 100644 index 7d18f99ed2..0000000000 --- a/samples/Luau/toSet.luau +++ /dev/null @@ -1,22 +0,0 @@ ---// Authored by @benbrimeyer (https://github.com/benbrimeyer) ---// Fetched from (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/source/List/toSet.luau) ---// Licensed under the MIT License (https://github.com/duckarmor/Freeze/blob/670bb6593d8cc54cdcbb96cd94d1273ee0420abb/LICENSE) - ---!strict - ---[=[ - Returns a Set from the given List. - - @within List - @function toSet - @ignore -]=] -return function(list: { Value }): { [Value]: boolean } - local set = {} - - for _, v in list do - set[v] = true - end - - return set -end diff --git a/samples/Luau/waitFor.luau b/samples/Luau/waitFor.luau deleted file mode 100644 index 28c17c044d..0000000000 --- a/samples/Luau/waitFor.luau +++ /dev/null @@ -1,265 +0,0 @@ ---// Authored by @Sleitnick (https://github.com/sleitnick) ---// Fetched from (https://github.com/Sleitnick/RbxUtil/blob/main/modules/wait-for/init.lua) ---// Licensed under the MIT License (https://github.com/Sleitnick/RbxUtil/blob/main/LICENSE.md) - ---!optimize 2 ---!strict ---!native - --- WaitFor --- Stephen Leitnick --- January 17, 2022 - -local RunService = game:GetService("RunService") - -local Promise = require(script.Parent.Promise) - -local DEFAULT_TIMEOUT = 60 - ---[=[ - @class WaitFor - Utility class for awaiting the existence of instances. - - By default, all promises timeout after 60 seconds, unless the `timeout` - argument is specified. - - :::note - Promises will be rejected if the parent (or any ancestor) is unparented - from the game. - ::: - - :::caution Set name before parent - When waiting for instances based on name (e.g. `WaitFor.Child`), the `WaitFor` - system is listening to events to capture these instances being added. This - means that the name must be set _before_ being parented into the object. - ::: -]=] -local WaitFor = {} - ---[=[ - @within WaitFor - @prop Error {Unparented: string, ParentChanged: string} -]=] -WaitFor.Error = { - Unparented = "Unparented", - ParentChanged = "ParentChanged", -} - -local function PromiseWatchAncestry(instance: Instance, promise) - return Promise.race({ - promise, - Promise.fromEvent(instance.AncestryChanged, function(_, newParent) - return newParent == nil - end):andThen(function() - return Promise.reject(WaitFor.Error.Unparented) - end), - }) -end - ---[=[ - @return Promise - Wait for a child to exist within a given parent based on the child name. - - ```lua - WaitFor.Child(parent, "SomeObject"):andThen(function(someObject) - print(someObject, "now exists") - end):catch(warn) - ``` -]=] -function WaitFor.Child(parent: Instance, childName: string, timeout: number?) - local child = parent:FindFirstChild(childName) - if child then - return Promise.resolve(child) - end - return PromiseWatchAncestry( - parent, - Promise.fromEvent(parent.ChildAdded, function(c) - return c.Name == childName - end):timeout(timeout or DEFAULT_TIMEOUT) - ) -end - ---[=[ - @return Promise<{Instance}> - Wait for all children to exist within the given parent. - - ```lua - WaitFor.Children(parent, {"SomeObject01", "SomeObject02"}):andThen(function(children) - local someObject01, someObject02 = table.unpack(children) - end) - ``` - - :::note - Once all children are found, a second check is made to ensure that all children - are still directly parented to the given `parent` (since one child's parent - might have changed before another child was found). A rejected promise with the - `WaitFor.Error.ParentChanged` error will be thrown if any parents of the children - no longer match the given `parent`. - ::: -]=] -function WaitFor.Children(parent: Instance, childrenNames: { string }, timeout: number?) - local all = table.create(#childrenNames) - for i, childName in ipairs(childrenNames) do - all[i] = WaitFor.Child(parent, childName, timeout) - end - return Promise.all(all):andThen(function(children) - -- Check that all are still parented - for _, child in ipairs(children) do - if child.Parent ~= parent then - return Promise.reject(WaitFor.Error.ParentChanged) - end - end - return children - end) -end - ---[=[ - @return Promise - Wait for a descendant to exist within a given parent. This is similar to - `WaitFor.Child`, except it looks for all descendants instead of immediate - children. - - ```lua - WaitFor.Descendant(parent, "SomeDescendant"):andThen(function(someDescendant) - print("SomeDescendant now exists") - end) - ``` -]=] -function WaitFor.Descendant(parent: Instance, descendantName: string, timeout: number?) - local descendant = parent:FindFirstChild(descendantName, true) - if descendant then - return Promise.resolve(descendant) - end - return PromiseWatchAncestry( - parent, - Promise.fromEvent(parent.DescendantAdded, function(d) - return d.Name == descendantName - end):timeout(timeout or DEFAULT_TIMEOUT) - ) -end - ---[=[ - @return Promise<{Instance}> - Wait for all descendants to exist within a given parent. - - ```lua - WaitFor.Descendants(parent, {"SomeDescendant01", "SomeDescendant02"}):andThen(function(descendants) - local someDescendant01, someDescendant02 = table.unpack(descendants) - end) - ``` - - :::note - Once all descendants are found, a second check is made to ensure that none of the - instances have moved outside of the parent (since one instance might change before - another instance is found). A rejected promise with the `WaitFor.Error.ParentChanged` - error will be thrown if any of the instances are no longer descendants of the given - `parent`. - ::: -]=] -function WaitFor.Descendants(parent: Instance, descendantNames: { string }, timeout: number?) - local all = table.create(#descendantNames) - for i, descendantName in ipairs(descendantNames) do - all[i] = WaitFor.Descendant(parent, descendantName, timeout) - end - return Promise.all(all):andThen(function(descendants) - -- Check that all are still parented - for _, descendant in ipairs(descendants) do - if not descendant:IsDescendantOf(parent) then - return Promise.reject(WaitFor.Error.ParentChanged) - end - end - return descendants - end) -end - ---[=[ - @return Promise - Wait for the PrimaryPart of a model to exist. - - ```lua - WaitFor.PrimaryPart(model):andThen(function(primaryPart) - print(primaryPart == model.PrimaryPart) - end) - ``` -]=] -function WaitFor.PrimaryPart(model: Model, timeout: number?) - local primary = model.PrimaryPart - if primary then - return Promise.resolve(primary) - end - return PromiseWatchAncestry( - model, - Promise.fromEvent(model:GetPropertyChangedSignal("PrimaryPart"), function() - primary = model.PrimaryPart - return primary ~= nil - end) - :andThen(function() - return primary - end) - :timeout(timeout or DEFAULT_TIMEOUT) - ) -end - ---[=[ - @return Promise - Wait for the Value of an ObjectValue to exist. - - ```lua - WaitFor.ObjectValue(someObjectValue):andThen(function(value) - print("someObjectValue's value is", value) - end) - ``` -]=] -function WaitFor.ObjectValue(objectValue: ObjectValue, timeout: number?) - local value = objectValue.Value - if value then - return Promise.resolve(value) - end - return PromiseWatchAncestry( - objectValue, - Promise.fromEvent(objectValue.Changed, function(v) - value = v - return value ~= nil - end) - :andThen(function() - return value - end) - :timeout(timeout or DEFAULT_TIMEOUT) - ) -end - ---[=[ - @return Promise - Wait for the given predicate function to return a non-nil value of - of type `T`. The predicate is fired every RunService Heartbeat step. - - ```lua - -- Example, waiting for some property to be set: - WaitFor.Custom(function() return vectorForce.Attachment0 end):andThen(function(a0) - print(a0) - end) - ``` -]=] -function WaitFor.Custom(predicate: () -> T?, timeout: number?) - local value = predicate() - if value ~= nil then - return Promise.resolve(value) - end - return Promise.new(function(resolve, _reject, onCancel) - local heartbeat - local function OnDone() - heartbeat:Disconnect() - end - local function Update() - local v = predicate() - if v ~= nil then - OnDone() - resolve(v) - end - end - heartbeat = RunService.Heartbeat:Connect(Update) - onCancel(OnDone) - end):timeout(timeout or DEFAULT_TIMEOUT) -end - -return WaitFor