From 0204ffa798f695e4eefcebac6628f01469cb9537 Mon Sep 17 00:00:00 2001 From: Demo Date: Fri, 7 Jun 2024 06:29:07 -0400 Subject: [PATCH] Add Luau language (#6612) * Feature: Add `Luau` language. * Featire: Add `Luau` code samples. * Feature: Add the grammar. * Feature: Add `Luau` to grammar index. * Fix: Add comment block and change the interpreter. * Feature: Add new samples in place of the old ones. * Patch: Slight typo in the header. * Patch: Header typos again, I'm such a genius. * Patch: Introduce the languageId. * Patch: Resolve grammar conflict. * Patch: Resolve grammar conflict. * Patch: Update the hex value. * Patch: Update the hex value. * Sort * Patch: Update the submodule to the latest commit. * Patch: Resolve merge conflicts. * Patch: Resolve further conflicts. * Patch: Remove conflict. * Patch: Reintroduce Luau into grammar index. * Revert "Patch: Reintroduce Luau into grammar index." This reverts commit f5eaf45e6de506b4c12555ef0617f168fc0bf383. * Patch: Retry resolving the conflict issue. * Update vendor/licenses/git_submodule/Luau.tmLanguage.dep.yml * Enhancement: Update old samples and their sources. * Patch: Update old samples and their sources. * Patch: Update old samples and their sources. * Patch: Update old samples and their sources. * Patch: Update the samples further. Enhancement: Replace the signal sample. * Revert "Patch: Update old samples and their sources." This reverts commit 2f9a8a20ea944b9f8290a1c0a73e400a89759398. Revert "Enhancement: Update old samples and their sources." This reverts commit d0053792c2016da0f8ea560a2202476e7e6c97c4. * Test: New samples, sadly one source. --------- Co-authored-by: Colin Seymour Co-authored-by: Colin Seymour --- .gitmodules | 3 + grammars.yml | 2 + lib/linguist/languages.yml | 12 + samples/Luau/EnumList.luau | 116 +++++++++ samples/Luau/Option.luau | 229 ++++++++++++++++++ samples/Luau/Symbol.luau | 61 +++++ samples/Luau/Tree.luau | 146 +++++++++++ samples/Luau/ser.luau | 178 ++++++++++++++ vendor/README.md | 1 + vendor/grammars/Luau.tmLanguage | 1 + .../git_submodule/Luau.tmLanguage.dep.yml | 57 +++++ 11 files changed, 806 insertions(+) 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 create mode 100644 samples/Luau/ser.luau 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 d952dabc01..4f46fa5aac 100644 --- a/.gitmodules +++ b/.gitmodules @@ -64,6 +64,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 diff --git a/grammars.yml b/grammars.yml index 6092a1f229..f9d10fd5a2 100644 --- a/grammars.yml +++ b/grammars.yml @@ -53,6 +53,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: diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index bfe0ebe9cc..6a28770afd 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -3931,6 +3931,18 @@ 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: "#00A2FF" + extensions: + - ".luau" + interpreters: + - luau + language_id: 365050359 M: type: programming aliases: 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/ser.luau b/samples/Luau/ser.luau new file mode 100644 index 0000000000..62e02fae17 --- /dev/null +++ b/samples/Luau/ser.luau @@ -0,0 +1,178 @@ +--!optimize 2 +--!strict +--!native + +--// 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) + +--[[ + + 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("samples/Luau/Option.luau") + +--[=[ + @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 = { (...) } + 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 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 = { (...) } + 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 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 unpack(value, 1, value.n) +end + +return Ser 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) diff --git a/vendor/grammars/Luau.tmLanguage b/vendor/grammars/Luau.tmLanguage new file mode 160000 index 0000000000..bc4170802a --- /dev/null +++ b/vendor/grammars/Luau.tmLanguage @@ -0,0 +1 @@ +Subproject commit bc4170802aab182127f2b6b80b0e1164f50d78c1 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..5de924bb05 --- /dev/null +++ b/vendor/licenses/git_submodule/Luau.tmLanguage.dep.yml @@ -0,0 +1,57 @@ +--- +name: Luau.tmLanguage +version: bc4170802aab182127f2b6b80b0e1164f50d78c1 +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: []