feat: include initial match expression impl

FIXME: Moonwave doc comments are broken due to indentation issues.

Implements the rust match idiom/syntax.
This commit is contained in:
Erica Marigold 2024-07-15 18:44:10 +05:30
parent 4d3e945b49
commit 6d39f55e01
No known key found for this signature in database
GPG key ID: 2768CC0C23D245D1

122
lib/match.luau Normal file
View file

@ -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",
_ = "<unknown>",
}
assert(word == "hi")
```
]=]
local Match = {}
--[=[
@private
@interface Arm<L, R>
@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<L, R> = R | (L?) -> R
--[=[
@private
@interface Arms<T, U>
@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, U> = ({ [T]: Arm<T, U> }) -> 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<T, U> -- A matcher function to call with the match arms
```
]=]
function Match.match<T, U>(value: T): Arms<T, U>
local function handleArmType(arm: Arm<T, U>, 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