From 6d39f55e01a66aae65b47a8e06d7abb806f59317 Mon Sep 17 00:00:00 2001 From: Erica Marigold Date: Mon, 15 Jul 2024 18:44:10 +0530 Subject: [PATCH] feat: include initial match expression impl FIXME: Moonwave doc comments are broken due to indentation issues. Implements the rust match idiom/syntax. --- lib/match.luau | 122 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 lib/match.luau diff --git a/lib/match.luau b/lib/match.luau new file mode 100644 index 0000000..ae8fc2a --- /dev/null +++ b/lib/match.luau @@ -0,0 +1,122 @@ +--[=[ + @class Match + + A match expression branches on a pattern. + + Match expressions must be exhaustive, meaning that every possible + pattern must be matched, i.e., have an arm handling it. The catch all + arm (`_`), which matches any value, can be used to do this. + + Match expressions also ensure that all of the left-sided arms match the + same type of the scrutinee expression, and the right-sided arms are all + of the same type. + + ```lua + local word = match "hello" { + ["hello"] = "hi", + ["bonjour"] = "salut", + ["hola"] = "hola", + _ = "", + } + + assert(word == "hi") + ``` +]=] +local Match = {} + +--[=[ + @private + @interface Arm + @within Match + + Represents an arm (right-side) of a match expression. + + @type type L -- The type of the scrutinee expression or the left side of the arm + @field result R -- The resolved value of the match expression or the right side of the arm + +]=] +type Arm = R | (L?) -> R + +--[=[ + @private + @interface Arms + @within Match + + Represents a constructed matcher. + + @type type T -- The type of the scrutinee expression or the left side of the arm + @field result U -- The resolved value of the match expression or the right side of the arm + +]=] +type Arms = ({ [T]: Arm }) -> U + +--[=[ + @function match + @within Match + + A match expression branches on a pattern. A match expression has a + scrutinee expression (the value to match on) and a list of patterns. + + :::note + Currently, most traditional pattern matching is not supported, with + the exception of the catch all pattern (`_`). + ::: + + A common use-case of match is to prevent repetitive `if` statements, when + checking against various possible values of a variable: + + ```lua + local function getGreetingNumber(greeting: string): number + return match(greeting) { + ["hello, world"] = 1, + ["hello, mom"] = 2, + _ = function(val) + return #val + end, + } + end + + assert(getGreetingNumber("hello, world") == 1) + assert(getGreetingNumber("hello, mom") == 2) + assert(getGreetingNumber("hello, john") == 11) + + @param value T -- The value to match on + @return matcher Arms -- A matcher function to call with the match arms + ``` +]=] +function Match.match(value: T): Arms + local function handleArmType(arm: Arm, fnArg: T?): U + if typeof(arm) == "function" then + return arm(fnArg) + end + + return arm + end + + return function(arms) + for l, r in arms do + -- Skip the catch all arm for now, get back to it + -- when we have finished checking for all other + -- arms + if l == "_" then + continue + end + + if value == l then + local ret = handleArmType(r, nil) + return ret + end + end + + -- Since we didn't get any matches, we invoke the catch + -- all arm, giving it the value we have + local catchAll = arms["_"] + or error( + `Non exhaustive match pattern, arm not satisfied for: {value}` + ) + + return handleArmType(catchAll, value) + end +end + +return Match.match