mirror of
https://github.com/CompeyDev/rusty-luau.git
synced 2025-04-04 18:00:54 +01:00
Compare commits
51 commits
Author | SHA1 | Date | |
---|---|---|---|
2df6d06c41 | |||
6d39f55e01 | |||
4d3e945b49 | |||
dde8ad0893 | |||
b65aa29de3 | |||
cf0caddcad | |||
a38422d084 | |||
f3b77f2575 | |||
a2a1e9bbf1 | |||
8f7cb3e2a0 | |||
814fc51e16 | |||
660806dff8 | |||
35ada9759c | |||
edcef2015f | |||
c42e4ad3fe | |||
376b7ae3a7 | |||
7804701d6b | |||
317b0cde87 | |||
e4b35bd5cb | |||
37c348535a | |||
44f79b9cf4 | |||
abf05fa7d3 | |||
d2b0cbf190 | |||
6fab3b64ce | |||
b0caea58dc | |||
f1d5358e39 | |||
5274ae9390 | |||
ea8bd316de | |||
058877513c | |||
113a77b8d8 | |||
8a68eda2f7 | |||
dcbc2208c3 | |||
107406d50f | |||
0072a9fd87 | |||
fb97755cad | |||
4c7a4ee0fd | |||
6b30f08388 | |||
50c5a0967f | |||
5dfc707b29 | |||
d85faed644 | |||
8632d052a9 | |||
64f73540ed | |||
b11ac303ea | |||
cd10bd86fc | |||
6f02130e51 | |||
b5ba4d9e38 | |||
01229741f6 | |||
38704d18af | |||
c7bf127222 | |||
de25b4d19c | |||
353ce041ea |
24 changed files with 2473 additions and 366 deletions
3
.gitattributes
vendored
3
.gitattributes
vendored
|
@ -1,3 +0,0 @@
|
|||
# Temporarily highlight luau as normal lua files
|
||||
# until we get native linguist support for Luau
|
||||
*.luau linguist-language=Lua
|
49
.github/workflows/ci.yaml
vendored
Normal file
49
.github/workflows/ci.yaml
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install tooling
|
||||
uses: ok-nick/setup-aftman@v0.4.2
|
||||
with:
|
||||
cache: true
|
||||
|
||||
- name: Check formatting
|
||||
run: stylua -c -v .
|
||||
|
||||
lint:
|
||||
needs: ["fmt"]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install tooling
|
||||
uses: ok-nick/setup-aftman@v0.4.2
|
||||
with:
|
||||
cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: wally install
|
||||
|
||||
- name: Setup lune typedefs
|
||||
run: lune setup
|
||||
|
||||
- name: Analyze
|
||||
run: luau-lsp analyze --ignore="Packages/**" --settings=".vscode/settings.json" lib/ examples/ mod.luau
|
||||
|
||||
- name: lint
|
||||
run: selene .
|
45
.github/workflows/docs.yaml
vendored
Normal file
45
.github/workflows/docs.yaml
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
name: Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v4.0.2
|
||||
|
||||
- name: Install Moonwave CLI
|
||||
run: npm i -g moonwave
|
||||
|
||||
- name: Build static site
|
||||
run: moonwave build
|
||||
|
||||
- name: Setup GitHub pages
|
||||
uses: actions/configure-pages@v3
|
||||
|
||||
- name: Upload repository artifact
|
||||
uses: actions/upload-pages-artifact@v2
|
||||
with:
|
||||
path: './build'
|
||||
|
||||
- name: Deploy to GitHub pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v2
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -1 +1,5 @@
|
|||
Packages/
|
||||
# Installed wally packages
|
||||
Packages/
|
||||
|
||||
# Moonwave compiled docs
|
||||
build
|
||||
|
|
42
.moonwave/custom.css
Normal file
42
.moonwave/custom.css
Normal file
|
@ -0,0 +1,42 @@
|
|||
@import url("https://Cinnab0nBak3ry.github.io/AppleFontsCSS/Apple-fonts.css");
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap');
|
||||
|
||||
:root {
|
||||
--ifm-color-primary: #cba6f7;
|
||||
--ifm-color-primary-dark: #b580f4;
|
||||
--ifm-color-primary-darker: #aa6df2;
|
||||
--ifm-color-primary-darkest: #8934ed;
|
||||
--ifm-color-primary-light: #e1ccfa;
|
||||
--ifm-color-primary-lighter: #ecdffc;
|
||||
--ifm-color-primary-lightest: #ffffff;
|
||||
|
||||
--ifm-footer-color: ##a6adc8;
|
||||
--ifm-footer-background-color: #45475a;
|
||||
|
||||
--ifm-font-family-base: "SF Pro Display";
|
||||
--ifm-font-family-monospace: "JetBrains Mono";
|
||||
--ifm-font-family-monospace: "JetBrains Mono";
|
||||
--ifm-code-font-size: 80%;
|
||||
}
|
||||
|
||||
/* Catppuccin Mocha from https://github.com/catppuccin/catppuccin?tab=readme-ov-file#-palette */
|
||||
|
||||
[data-theme='dark']:root {
|
||||
--ifm-color-scheme: dark;
|
||||
--ifm-background-color: #1e1e2e;
|
||||
--ifm-background-surface-color: #313244;
|
||||
--ifm-color-black: #11111b;
|
||||
--ifm-font-color-base: #cdd6f4;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
TODO: Light theme catppuccin latte
|
||||
[data-theme='light']:root {
|
||||
--ifm-color-scheme: light;
|
||||
--ifm-background-color: ##eff1f5;
|
||||
--ifm-background-surface-color: #ccd0da;
|
||||
--ifm-color-white: #e6e9ef;
|
||||
--ifm-font-color-base: ##4c4f69;
|
||||
}
|
||||
*/
|
BIN
.moonwave/static/logo.png
Normal file
BIN
.moonwave/static/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 50 KiB |
31
CHANGELOG.md
31
CHANGELOG.md
|
@ -8,6 +8,37 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## `0.2.0` - June 21st, 2024
|
||||
|
||||
This release includes various changes to API interfaces, documentation, and includes new
|
||||
implementations.
|
||||
|
||||
- Fixed inconsistencies `Option` & `Result` implementations
|
||||
- Implemented `Future`, a pollable asynchronous idiom, alternative to promises
|
||||
|
||||
```luau
|
||||
local net = require("@lune/net")
|
||||
|
||||
local fut: Future<Result<string, string>> = Future.try(function(url)
|
||||
local resp = net.request({
|
||||
url = url,
|
||||
method = "GET",
|
||||
})
|
||||
|
||||
assert(resp.ok)
|
||||
|
||||
return resp.body
|
||||
end, { "https://jsonplaceholder.typicode.com/posts/1" })
|
||||
|
||||
local resp: Result<string, string> = fut:await()
|
||||
print(net.jsonDecode(resp:unwrap()))
|
||||
```
|
||||
|
||||
- Added documentation for all available implementations
|
||||
- Included CI action
|
||||
- Added examples for `Result`
|
||||
- Removed incomplete `Iter` implementation
|
||||
|
||||
## `0.1.0` - April 2nd, 2024
|
||||
|
||||
The very first release of rusty-luau.
|
||||
|
|
26
README.md
Normal file
26
README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
<img align="right" src="https://rusty-luau.devcomp.xyz/logo.png" />
|
||||
|
||||
[![wally][wally-image]][wally-link]
|
||||
[![AGPL-3.0 licensed][license-image]][license-link]
|
||||
[![docs][docs-image]][docs-link]
|
||||
[![CI][ci-image]][ci-link]
|
||||
|
||||
## [read the docs](https://rusty-luau.devcomp.xyz/api/)
|
||||
|
||||
strongly typed implementations of various rust idioms in luau.
|
||||
|
||||
currently, the following implementations are available:
|
||||
- [Future](https://rusty-luau.devcomp.xyz/api/Future)
|
||||
- [Option](https://rusty-luau.devcomp.xyz/api/Option)
|
||||
- [Result](https://rusty-luau.devcomp.xyz/api/Result)
|
||||
|
||||
[//]: # (badges)
|
||||
|
||||
[wally-image]: https://img.shields.io/github/v/tag/CompeyDev/rusty-luau?label=wally&logo=lua
|
||||
[wally-link]: https://wally.run/package/compeydev/rusty-luau
|
||||
[docs-image]: https://github.com/CompeyDev/rusty-luau/actions/workflows/docs.yaml/badge.svg
|
||||
[docs-link]: https://rusty-luau.devcomp.xyz/
|
||||
[license-image]: https://img.shields.io/github/license/CompeyDev/rusty-luau
|
||||
[license-link]: https://github.com/CompeyDev/rusty-luau/blob/main/LICENSE.md
|
||||
[ci-image]: https://github.com/CompeyDev/rusty-luau/actions/workflows/ci.yaml/badge.svg
|
||||
[ci-link]: https://github.com/CompeyDev/rusty-luau/actions/workflows/ci.yaml
|
|
@ -1,6 +1,7 @@
|
|||
[tools]
|
||||
lune = "lune-org/lune@0.8.2"
|
||||
lune = "lune-org/lune@0.8.5"
|
||||
stylua = "JohnnyMorganz/StyLua@0.20.0"
|
||||
luau-lsp = "JohnnyMorganz/luau-lsp@1.27.0"
|
||||
darklua = "seaofvoices/darklua@0.13.0"
|
||||
wally = "UpliftGames/wally@0.3.2"
|
||||
selene = "Kampfkarren/selene@0.26.1"
|
||||
|
|
20
examples/russianRoullete.luau
Normal file
20
examples/russianRoullete.luau
Normal file
|
@ -0,0 +1,20 @@
|
|||
local result = require("../lib/result")
|
||||
type Result<T, E> = result.Result<T, E>
|
||||
local Ok = result.Ok
|
||||
local Err = result.Err
|
||||
|
||||
local function canError(): Result<number, string>
|
||||
if math.round(math.random()) == 1 then
|
||||
return Err("you DIED")
|
||||
end
|
||||
|
||||
return Ok(69)
|
||||
end
|
||||
|
||||
function main()
|
||||
local val = canError():unwrap()
|
||||
|
||||
print("got value: ", val)
|
||||
end
|
||||
|
||||
return main()
|
|
@ -19,79 +19,197 @@ export type Result<T, E> = result.Result<T, E>
|
|||
local Ok = result.Ok
|
||||
local Err = result.Err
|
||||
|
||||
--[=[
|
||||
@within Result
|
||||
|
||||
Converts from [Result]`<T, E>` to [Option]`<T>`.
|
||||
|
||||
Converts `self` into an [Option]`<T>`, and discarding the error, if any.
|
||||
|
||||
```lua
|
||||
local x: Result<number, string> = Ok(2)
|
||||
assert(x:ok() == Some(2))
|
||||
|
||||
x = Err("Nothing here")
|
||||
assert(x:ok() == None())
|
||||
```
|
||||
|
||||
@param self Result<T, E>
|
||||
@return Option<T>
|
||||
]=]
|
||||
function Result.ok<T, E>(self: Result<T, E>): Option<T>
|
||||
if self:isOk() then
|
||||
if self._value == nil then
|
||||
return None()
|
||||
end
|
||||
if self:isOk() then
|
||||
if self._value == nil then
|
||||
return None()
|
||||
end
|
||||
|
||||
return Some(self._value)
|
||||
end
|
||||
return Some(self._value)
|
||||
end
|
||||
|
||||
return None()
|
||||
return None()
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within Result
|
||||
|
||||
Converts from [Result]`<T, E>` to [Option]`<E>`.
|
||||
|
||||
Converts `self` into an [Option]`<E>`, and discarding the success value, if any.
|
||||
|
||||
```lua
|
||||
local x: Result<number, string> = Ok(2)
|
||||
assert(x:ok() == Some(2))
|
||||
|
||||
x = Err("Nothing here")
|
||||
assert(x:ok() == None())
|
||||
```
|
||||
|
||||
@param self Result<T, E>
|
||||
@return Option<E>
|
||||
]=]
|
||||
function Result.err<T, E>(self: Result<T, E>): Option<E>
|
||||
if self:isErr() then
|
||||
return Option.new(self._error) :: Option<E>
|
||||
end
|
||||
if self:isErr() then
|
||||
return Option.new(self._error) :: Option<E>
|
||||
end
|
||||
|
||||
return None()
|
||||
return None()
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within Result
|
||||
|
||||
Transposes a [Result] of an [Option] into an [Option] of a [Result].
|
||||
|
||||
[Result:Ok]\([Option:None]\) will be mapped to [Option:None].
|
||||
[Result:Ok]\([Option:Some]`(_)`\) and [Result:Err]\(`_`\) will be mapped to
|
||||
[Option:Some]\([Result:Ok]`(_)`\) and [Option:Some]\([Option:Err]`(_)`\).
|
||||
|
||||
```lua
|
||||
type SomeErr = {}
|
||||
|
||||
local x: Result<Option<number>, SomeErr> = Ok(Some(2))
|
||||
local y: Option<Result<number, SomeErr>> = Some(Ok(2))
|
||||
assert(x:transpose() == y)
|
||||
```
|
||||
|
||||
@param self Result<Option<T>, E>
|
||||
@return Option<Result<T, E>>
|
||||
]=]
|
||||
function Result.transpose<T, E>(self: Result<Option<T>, E>): Option<Result<T, E>>
|
||||
-- TODO: Instead of checking whether values are nil, use
|
||||
-- utility methods for Options once available
|
||||
if self._value == None() then
|
||||
return None()
|
||||
elseif self:isOkAnd(function(val): boolean
|
||||
return val._optValue == nil
|
||||
end) then
|
||||
return Some(Ok(self._value._optValue))
|
||||
elseif self:isErr() then
|
||||
return Some(Err(self._error))
|
||||
end
|
||||
if self._value == None() then
|
||||
return None()
|
||||
elseif
|
||||
self:isOkAnd(function(val): boolean
|
||||
return val._optValue == nil
|
||||
end)
|
||||
then
|
||||
return Some(Ok(self._value._optValue))
|
||||
elseif self:isErr() then
|
||||
return Some(Err(self._error))
|
||||
end
|
||||
|
||||
error("`Result` is not transposable")
|
||||
error("`Result` is not transposable")
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within Option
|
||||
|
||||
Transforms the [Option]`<T>` into a [Result]`<T, E>`, mapping [Option:Some]`(v)`
|
||||
to [Result:Ok]`(v)` and [Option:None] to [Result:Err]`(err)`.
|
||||
|
||||
Arguments passed to [Option:okOr] are eagerly evaluated; if you are passing the result
|
||||
of a function call, it is recommended to use [Option:okOrElse], which is lazily evaluated.
|
||||
|
||||
```lua
|
||||
local x: Option<string> = Some("foo")
|
||||
assert(x:okOr(0) == Ok("foo"))
|
||||
|
||||
x = None()
|
||||
assert(x:okOr(0) == Err(0))
|
||||
```
|
||||
|
||||
@param self Option<T>
|
||||
@param err E
|
||||
@return Result<T, E>
|
||||
]=]
|
||||
function Option.okOr<T, E>(self: Option<T>, err: E): Result<T, E>
|
||||
if self:isSome() then
|
||||
return Ok(self._optValue)
|
||||
end
|
||||
if self:isSome() then
|
||||
return Ok(self._optValue)
|
||||
end
|
||||
|
||||
return Err(err)
|
||||
return Err(err)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within Option
|
||||
|
||||
Transforms the [Option]`<T>` into a [Result]`<T, E>`, mapping [Option:Some]`(v)` to
|
||||
[Result:Ok]`(v)` and [Option:None] to [Result:Err]`(err())`.
|
||||
|
||||
```lua
|
||||
local x: Option<string> = Some("foo")
|
||||
assert(x:okOrElse(function() return 0 end) == Ok("foo"))
|
||||
|
||||
x = None()
|
||||
assert(x:okOrElse(function() return 0 end) == Err(0))
|
||||
```
|
||||
|
||||
@param self Option<T>
|
||||
@param err () -> E
|
||||
@return Result<T, E>
|
||||
]=]
|
||||
function Option.okOrElse<T, E>(self: Option<T>, err: () -> E): Result<T, E>
|
||||
if self:isSome() then
|
||||
return Ok(self._optValue :: T)
|
||||
end
|
||||
if self:isSome() then
|
||||
return Ok(self._optValue :: T)
|
||||
end
|
||||
|
||||
return Err(err())
|
||||
return Err(err())
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within Option
|
||||
|
||||
Transposes a [Option] of an [Result] into an [Result] of a [Option].
|
||||
|
||||
[Option:None] will be mapped to [Result:Ok]\([Option:None]\).
|
||||
[Option:Some]\([Result:Ok]`(_)`\) and [Option:Some]\([Result:Err]\(`_`\)\) will
|
||||
be mapped to [Result:Ok]\([Option:Some]`(_)`\) and [Result:Err]`(_)`.
|
||||
|
||||
```lua
|
||||
type SomeErr = {}
|
||||
|
||||
local x: Result<Option<number>, SomeErr> = Ok(Some(5))
|
||||
local y: Option<Result<number, SomeErr>> = Some(Ok(5))
|
||||
assert(x == y:transpose())
|
||||
```
|
||||
|
||||
@param self Option<Result<T, E>>
|
||||
@return Result<Option<T>, E>
|
||||
]=]
|
||||
function Option.transpose<T, E>(self: Option<Result<T, E>>): Result<Option<T>, E>
|
||||
if self:isSome() then
|
||||
local inner = self._optValue
|
||||
assert(self.typeId == "Option" and inner.typeId == "Result", "Only an `Option` of a `Result` can be transposed")
|
||||
if self:isSome() then
|
||||
local inner = self._optValue
|
||||
assert(
|
||||
self.typeId == "Option" and inner.typeId == "Result",
|
||||
"Only an `Option` of a `Result` can be transposed"
|
||||
)
|
||||
|
||||
if inner:isOk() then
|
||||
return Some(Ok(inner._value))
|
||||
elseif inner:isErr() then
|
||||
return Some(Err(inner._error))
|
||||
end
|
||||
end
|
||||
if inner:isOk() then
|
||||
return Some(Ok(inner._value))
|
||||
elseif inner:isErr() then
|
||||
return Some(Err(inner._error))
|
||||
end
|
||||
end
|
||||
|
||||
return Ok(None())
|
||||
return Ok(None())
|
||||
end
|
||||
|
||||
return {
|
||||
Ok = Ok,
|
||||
Err = Err,
|
||||
Result = Result,
|
||||
Ok = Ok,
|
||||
Err = Err,
|
||||
Result = Result,
|
||||
|
||||
Some = Some,
|
||||
None = None,
|
||||
Option = Option,
|
||||
Some = Some,
|
||||
None = None,
|
||||
Option = Option,
|
||||
}
|
||||
|
|
217
lib/future.luau
Normal file
217
lib/future.luau
Normal file
|
@ -0,0 +1,217 @@
|
|||
local task = require("@lune/task")
|
||||
|
||||
local mod = require("../mod")
|
||||
local Signal = mod.signal
|
||||
type Signal<T...> = mod.Signal<T...>
|
||||
|
||||
local result = require("result")
|
||||
type Result<T, E> = result.Result<T, E>
|
||||
local Ok = result.Ok
|
||||
local Err = result.Err
|
||||
|
||||
local option = require("option")
|
||||
type Option<T> = option.Option<T>
|
||||
local None = option.None
|
||||
local Some = option.Some
|
||||
|
||||
--[=[
|
||||
@class Future
|
||||
|
||||
A future represents an asynchronous computation.
|
||||
|
||||
A future is a value that might not have finished computing yet. This kind of “asynchronous value”
|
||||
makes it possible for a thread to continue doing useful work while it waits for the value to
|
||||
become available.
|
||||
|
||||
### The [Future:poll] Method
|
||||
The core method of future, poll, attempts to resolve the future into a final value. This method does
|
||||
not block if the value is not ready. Instead, the current task is executed in the background, and
|
||||
its progress is reported when polled. When using a future, you generally won’t call poll directly,
|
||||
but instead [Future:await] the value.
|
||||
|
||||
```lua
|
||||
local net = require("@lune/net")
|
||||
|
||||
local fut: Future<Result<string, string>> = Future.try(function(url)
|
||||
local resp = net.request({
|
||||
url = url,
|
||||
method = "GET",
|
||||
})
|
||||
|
||||
assert(resp.ok)
|
||||
|
||||
return resp.body
|
||||
end, { "https://jsonplaceholder.typicode.com/posts/1" })
|
||||
|
||||
local resp: Result<string, string> = fut:await()
|
||||
print(net.jsonDecode(resp:unwrap()))
|
||||
```
|
||||
]=]
|
||||
local Future = {}
|
||||
|
||||
--[=[
|
||||
@private
|
||||
@type Status "initialized" | "pending" | "cancelled" | "ready"
|
||||
@within Future
|
||||
|
||||
Represents the status of a [Future].
|
||||
]=]
|
||||
export type Status = "initialized" | "pending" | "cancelled" | "ready"
|
||||
|
||||
--[=[
|
||||
@private
|
||||
@interface Future<T>
|
||||
@within Future
|
||||
|
||||
Represents the internal state of a [Future].
|
||||
|
||||
@field _thread thread -- The background coroutine spawned for execution
|
||||
@field _ret T -- The value returned once execution has halted
|
||||
@field _spawnEvt Signal<()> -- Event for internal communication among threads pre execution
|
||||
@field _retEvt Signal<T | Result<T, string>, Status> -- Event for internal communication among threads post execution
|
||||
@field _status Status -- The status of the Future
|
||||
|
||||
]=]
|
||||
export type Future<T> = typeof(Future) & {
|
||||
_ret: T,
|
||||
_thread: thread,
|
||||
_spawnEvt: Signal<()>,
|
||||
_retEvt: Signal<T | Result<T, string>, Status>,
|
||||
_status: Status,
|
||||
}
|
||||
|
||||
local function _constructor<T>(fn: (Signal<()>, Signal<T, Status>) -> ())
|
||||
return setmetatable(
|
||||
{
|
||||
_thread = coroutine.create(fn),
|
||||
|
||||
_spawnEvt = Signal.new(),
|
||||
_retEvt = Signal.new(),
|
||||
_status = "initialized",
|
||||
} :: Future<T>,
|
||||
{
|
||||
__index = Future,
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within Future
|
||||
|
||||
Constructs a [Future] from a function to be run asynchronously.
|
||||
|
||||
:::caution
|
||||
If a the provided function has the possibility to throw an error, instead of any
|
||||
other rusty-luau types like [Result] or [Option], use [Future:try] instead.
|
||||
:::
|
||||
|
||||
@param fn -- The function to be executed asynchronously
|
||||
@param args -- The arguments table to be passed to to the function
|
||||
@return Future<T> -- The constructed future
|
||||
]=]
|
||||
function Future.new<T>(fn: (...any) -> T, args: { any })
|
||||
return _constructor(
|
||||
function(spawnEvt: Signal<()>, retEvt: Signal<T, Status>)
|
||||
spawnEvt:Fire()
|
||||
|
||||
local ret = fn(table.unpack(args))
|
||||
retEvt:Fire(ret, "ready")
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within Future
|
||||
|
||||
Constructs a fallible [Future] from a function to be run asynchronously.
|
||||
|
||||
@param fn -- The fallible function to be executed asynchronously
|
||||
@param args -- The arguments table to be passed to to the function
|
||||
@return Future<Result<T>> -- The constructed future
|
||||
]=]
|
||||
function Future.try<T>(fn: (...any) -> T, args: { any })
|
||||
return _constructor(
|
||||
function(
|
||||
spawnEvt: Signal<()>,
|
||||
retEvt: Signal<Result<T, string>, Status>
|
||||
)
|
||||
spawnEvt:Fire()
|
||||
|
||||
local ok, ret = pcall(fn, table.unpack(args))
|
||||
local res: Result<T, string> = if ok then Ok(ret) else Err(ret)
|
||||
retEvt:Fire(res, "ready")
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within Future
|
||||
|
||||
Polls a [Future] to completion.
|
||||
|
||||
@param self Future<T>
|
||||
@return (Status, Option<T>) -- Returns the [Status] and an optional return if completed
|
||||
]=]
|
||||
function Future.poll<T>(self: Future<T>): (Status, Option<T>)
|
||||
if self._status == "initialized" then
|
||||
self._retEvt:Connect(function(firedRet, status: Status)
|
||||
self._status = status
|
||||
self._ret = firedRet
|
||||
|
||||
-- Cleanup
|
||||
coroutine.yield(self._thread)
|
||||
coroutine.close(self._thread)
|
||||
|
||||
self._spawnEvt:DisconnectAll()
|
||||
self._retEvt:DisconnectAll()
|
||||
end)
|
||||
|
||||
self._spawnEvt:Connect(function()
|
||||
self._status = "pending"
|
||||
end)
|
||||
|
||||
coroutine.resume(self._thread, self._spawnEvt, self._retEvt)
|
||||
end
|
||||
|
||||
if self._status == "pending" then
|
||||
-- Just wait a bit more for the signal to fire
|
||||
task.wait(0.01)
|
||||
end
|
||||
|
||||
local retOpt = if self._ret == nil then None() else Some(self._ret)
|
||||
return self._status, retOpt
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within Future
|
||||
|
||||
Cancels a [Future].
|
||||
|
||||
@param self Future<T>
|
||||
]=]
|
||||
function Future.cancel<T>(self: Future<T>)
|
||||
self._retEvt:Fire(nil :: any, "cancelled")
|
||||
self._status = "cancelled"
|
||||
end
|
||||
|
||||
--[=[
|
||||
@within Future
|
||||
|
||||
Suspend execution until the result of a [Future] is ready.
|
||||
This method continuosly polls a [Future] until it reaches completion.
|
||||
|
||||
@param self Future<T>
|
||||
@return T -- The value returned by the function on completion
|
||||
]=]
|
||||
function Future.await<T>(self: Future<T>): T
|
||||
while true do
|
||||
local status: Status, ret: Option<T> = self:poll()
|
||||
|
||||
if status == "ready" then
|
||||
-- Safe to unwrap, we know it must not be nil
|
||||
return ret:unwrap()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Future
|
122
lib/match.luau
Normal file
122
lib/match.luau
Normal 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
|
976
lib/option.luau
976
lib/option.luau
File diff suppressed because it is too large
Load diff
945
lib/result.luau
945
lib/result.luau
File diff suppressed because it is too large
Load diff
38
lib/util.luau
Normal file
38
lib/util.luau
Normal file
|
@ -0,0 +1,38 @@
|
|||
-- From https://gist.github.com/sapphyrus/fd9aeb871e3ce966cc4b0b969f62f539
|
||||
local function tableEq(tbl1, tbl2)
|
||||
if tbl1 == tbl2 then
|
||||
return true
|
||||
elseif type(tbl1) == "table" and type(tbl2) == "table" then
|
||||
for key1, value1 in pairs(tbl1) do
|
||||
local value2 = tbl2[key1]
|
||||
|
||||
if value2 == nil then
|
||||
-- avoid the type call for missing keys in tbl2 by directly comparing with nil
|
||||
return false
|
||||
elseif value1 ~= value2 then
|
||||
if type(value1) == "table" and type(value2) == "table" then
|
||||
if not tableEq(value1, value2) then
|
||||
return false
|
||||
end
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- check for missing keys in tbl1
|
||||
for key2, _ in pairs(tbl2) do
|
||||
if tbl1[key2] == nil then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return {
|
||||
tableEq = tableEq,
|
||||
}
|
6
lune.yml
Normal file
6
lune.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
base: luau
|
||||
globals:
|
||||
warn:
|
||||
args:
|
||||
- type: ...
|
16
mod.luau
Normal file
16
mod.luau
Normal file
|
@ -0,0 +1,16 @@
|
|||
local fs = require("@lune/fs")
|
||||
local luau = require("@lune/luau")
|
||||
|
||||
local SIGNAL_PATH =
|
||||
"Packages/_Index/ffrostflame_luausignal@0.2.4/luausignal/src/init.luau"
|
||||
local _signal = require(SIGNAL_PATH)
|
||||
export type Signal<T...> = _signal.luauSignal<T...>
|
||||
local signal: {
|
||||
new: <T...>() -> Signal<T...>,
|
||||
} = luau.load(
|
||||
'local task = require("@lune/task")\n' .. fs.readFile(SIGNAL_PATH)
|
||||
)()
|
||||
|
||||
return {
|
||||
signal = signal,
|
||||
}
|
24
moonwave.toml
Normal file
24
moonwave.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
title = "rusty-luau"
|
||||
gitRepoUrl = "https://github.com/CompeyDev/rusty-luau"
|
||||
|
||||
gitSourceBranch = "main"
|
||||
changelog = true
|
||||
|
||||
[docusaurus]
|
||||
onBrokenLinks = "throw"
|
||||
onBrokenMarkdownLinks = "warn"
|
||||
favicon = "/logo.png"
|
||||
organizationName = "CompeyDev"
|
||||
url = "https://rusty-luau.devcomp.xyz"
|
||||
baseUrl = "/"
|
||||
tagline = "Strongly typed implementations of various rust idioms in luau."
|
||||
|
||||
# TODO: Disable theme toggle
|
||||
# [docusaurus.themeConfig.colorMode]
|
||||
# defaultMode = "dark"
|
||||
# disableSwitch = true
|
||||
# respectPrefersColorScheme = false
|
||||
|
||||
[footer]
|
||||
style = "light"
|
||||
copyright = "Copyright © 2021 Erica Marigold."
|
2
selene.toml
Normal file
2
selene.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
std = "lune"
|
||||
exclude = ["Packages/*"]
|
10
stylua.toml
Normal file
10
stylua.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
line_endings = "Unix"
|
||||
quote_style = "AutoPreferDouble"
|
||||
indent_type = "Spaces"
|
||||
call_parentheses = "NoSingleTable"
|
||||
|
||||
indent_width = 4
|
||||
column_width = 80
|
||||
|
||||
[sort_requires]
|
||||
enabled = true
|
20
test.luau
20
test.luau
|
@ -1,20 +0,0 @@
|
|||
local result = require("lib/result")
|
||||
type Result<T, E> = result.Result<T, E>
|
||||
local Ok = result.Ok
|
||||
local Err = result.Err
|
||||
|
||||
local function canError(): Result<number, string>
|
||||
if math.round(math.random()) == 1 then
|
||||
return Err("you DIED")
|
||||
end
|
||||
|
||||
return Ok(69)
|
||||
end
|
||||
|
||||
function main()
|
||||
local val = canError():unwrap()
|
||||
|
||||
print("got value: ", val)
|
||||
end
|
||||
|
||||
return main()
|
13
wally.lock
Normal file
13
wally.lock
Normal file
|
@ -0,0 +1,13 @@
|
|||
# This file is automatically @generated by Wally.
|
||||
# It is not intended for manual editing.
|
||||
registry = "test"
|
||||
|
||||
[[package]]
|
||||
name = "compeydev/rusty-luau"
|
||||
version = "0.1.0"
|
||||
dependencies = [["luausignal", "ffrostflame/luausignal@0.2.4"]]
|
||||
|
||||
[[package]]
|
||||
name = "ffrostflame/luausignal"
|
||||
version = "0.2.4"
|
||||
dependencies = []
|
13
wally.toml
13
wally.toml
|
@ -4,7 +4,16 @@ version = "0.1.0"
|
|||
registry = "https://github.com/UpliftGames/wally-index"
|
||||
realm = "shared"
|
||||
license = "AGPL-3.0"
|
||||
exclude = ["tests/**", "examples/**", "**"]
|
||||
include = ["lib/**", "LICENSE.md", "README.md", "wally.toml"]
|
||||
exclude = [
|
||||
"tests/**",
|
||||
"examples/**",
|
||||
".vscode/**",
|
||||
"aftman.toml",
|
||||
".gitignore",
|
||||
".gitattributes",
|
||||
"CHANGELOG.md",
|
||||
]
|
||||
include = ["lib/*.luau", "LICENSE.md", "README.md", "wally.toml"]
|
||||
|
||||
[dependencies]
|
||||
luausignal = "ffrostflame/luausignal@0.2.4"
|
||||
|
|
Loading…
Add table
Reference in a new issue