Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion moon.mod.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rami3l/js-ffi",
"version": "0.3.0",
"version": "0.3.1",
"readme": "README.md",
"repository": "",
"license": "Apache-2.0",
Expand Down
33 changes: 18 additions & 15 deletions src/js/async.mbt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
///|
pub async fn[T, E : Error] suspend(
f : ((T) -> Unit, (E) -> Unit) -> Unit
) -> T!E = "%async.suspend"
) -> T raise E = "%async.suspend"

///|
pub fn async_run(f : async () -> Unit) -> Unit = "%async.run"
Expand All @@ -14,7 +14,8 @@ pub fn async_run(f : async () -> Unit) -> Unit = "%async.run"
/// (i.e. this `Promise` is not handled by an external JavaScript API).
/// This makes sure that when the operation in the this `Promise` errs out,
/// the error is caught by the MoonBit runtime.
pub extern type Promise
#external
pub type Promise

///|
extern "js" fn Promise::wait_ffi(
Expand All @@ -25,8 +26,8 @@ extern "js" fn Promise::wait_ffi(
#| (self, on_ok, on_err) => self.then((t) => on_ok(t), (e) => on_err(e))

///|
pub async fn Promise::wait(self : Promise) -> Value! {
suspend!(fn(k, ke) { Promise::wait_ffi(self, k, fn(e) { ke(Error_(e)) }) })
pub async fn Promise::wait(self : Promise) -> Value raise {
suspend(fn(k, ke) { Promise::wait_ffi(self, k, fn(e) { ke(Error_(e)) }) })
}

///|
Expand All @@ -38,18 +39,18 @@ pub async fn Promise::wait(self : Promise) -> Value! {
/// This makes sure that when `op` errs out, the error is caught by the MoonBit runtime.
///
/// If you don't care about the result of the operation, you can use `spawn_detach` instead.
pub fn[T] Promise::unsafe_new(op : async () -> T!) -> Promise {
Promise::new_ffi(fn() { Value::cast_from(op!()) })
pub fn[T] Promise::unsafe_new(op : async () -> T raise) -> Promise {
Promise::new_ffi(fn() { Value::cast_from(op()) })
}

///|
extern "js" fn Promise::new_ffi(op : async () -> Value!) -> Promise =
extern "js" fn Promise::new_ffi(op : async () -> Value raise) -> Promise =
#| (op) => new Promise((k, ke) => op(k, ke))

///|
pub fn[T, E : Error] spawn_detach(op : async () -> T!E) -> Unit {
pub fn[T, E : Error] spawn_detach(op : async () -> T raise E) -> Unit {
async_run(fn() {
try op!() |> ignore catch {
try op() |> ignore catch {
_ => ()
}
})
Expand All @@ -62,21 +63,23 @@ pub extern "js" fn Promise::all(promises : Array[Promise]) -> Promise = "(ps) =>

///|
/// Wraps each given `async fn` in a `Promise` and waits for all of them to resolve.
pub async fn[T] async_all!(ops : Array[async () -> T!]) -> Array[T] {
async_all_raw!(ops.map(fn(op) { async fn!() { Value::cast_from(op!()) } })).map(
pub async fn[T] async_all(ops : Array[async () -> T raise]) -> Array[T] raise {
async_all_raw(ops.map(fn(op) { async fn() raise { Value::cast_from(op()) } })).map(
Value::cast,
)
}

///|
async fn async_all_raw!(ops : Array[async () -> Value!]) -> Array[Value] {
Promise::all(ops.map(Promise::unsafe_new)).wait!().cast()
async fn async_all_raw(
ops : Array[async () -> Value raise]
) -> Array[Value] raise {
Promise::all(ops.map(Promise::unsafe_new)).wait().cast()
}

///|
pub fn async_test(op : async () -> Unit!) -> Unit {
pub fn async_test(op : async () -> Unit raise) -> Unit {
async_run(async fn() {
try op!() catch {
op() catch {
e => {
println("ERROR in `async_test`: \{e}")
panic()
Expand Down
19 changes: 10 additions & 9 deletions src/js/async_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ async fn op1() -> String? {

///|
async fn op2() -> String? {
guard op1!() is Some(prefix)
guard op1() is Some(prefix)
Some(prefix + ", World")
}

Expand All @@ -16,19 +16,20 @@ async fn op3() -> String? {

///|
test "Promise::wait" {
@js.async_test(fn!() {
@js.async_test(fn() raise {
// Promise::unsafe_new + Promise::wait is a noop.
let res = @js.Promise::unsafe_new(fn() { op1!() }).wait!()
assert_eq!(res.cast(), Some("Hello"))
let res = @js.Promise::unsafe_new(fn() { op1() }).wait()
assert_eq(res.cast(), Some("Hello"))
})
}

///|
test "async_all" {
@js.async_test(fn!() {
assert_eq!(
@js.async_all!([fn() { op2!() }, fn() { op1!() }, fn() { op3!() }]),
[Some("Hello, World"), Some("Hello"), None],
)
@js.async_test(fn() raise {
assert_eq(@js.async_all([fn() { op2() }, fn() { op1() }, fn() { op3() }]), [
Some("Hello, World"),
Some("Hello"),
None,
])
})
}
4 changes: 2 additions & 2 deletions src/js/error.mbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
///|
pub type! Error_ Value
pub suberror Error_ Value

///|
extern "js" fn Error_::cause_ffi(self : Value) -> Value = "(self) => self.cause"
Expand Down Expand Up @@ -34,7 +34,7 @@ extern "js" fn Error_::wrap_ffi(
pub fn[T] Error_::wrap(
op : () -> Value,
map_ok~ : (Value) -> T = Value::cast
) -> T!Error_ {
) -> T raise Error_ {
let mut res : Result[Value, Error_] = Ok(Value::undefined())
Error_::wrap_ffi(op, fn(v) { res = Ok(v) }, fn(e) { res = Err(Error_(e)) })
match res {
Expand Down
63 changes: 21 additions & 42 deletions src/js/js.mbti
Original file line number Diff line number Diff line change
Expand Up @@ -5,64 +5,38 @@ import(
)

// Values
async fn[T] async_all(Array[() -> T!]) -> Array[T]!
async fn[T] async_all(Array[() -> T raise]) -> Array[T] raise

let async_iterator : Symbol

fn async_run(() -> Unit) -> Unit

fn async_test(() -> Unit!) -> Unit

fn extends(Value, Value) -> Value

fn[T] get_with_index(Value, Int) -> T

fn[T] get_with_string(Value, String) -> T

fn[T] get_with_symbol(Value, Symbol) -> T
fn async_test(() -> Unit raise) -> Unit

let globalThis : Value

fn is_bool(Value) -> Bool

fn is_null(Value) -> Bool

fn is_number(Value) -> Bool

fn is_object(Value) -> Bool

fn is_string(Value) -> Bool

fn is_symbol(Value) -> Bool

fn is_undefined(Value) -> Bool

let iterator : Symbol

fn require(String, keys~ : Array[String] = ..) -> Value

fn[T] set_with_index(Value, Int, T) -> Unit

fn[T] set_with_string(Value, String, T) -> Unit

fn[T] set_with_symbol(Value, Symbol, T) -> Unit

fn[T, E : Error] spawn_detach(() -> T!E) -> Unit
fn[T, E : Error] spawn_detach(() -> T raise E) -> Unit

async fn[T, E : Error] suspend(((T) -> Unit, (E) -> Unit) -> Unit) -> T!E
async fn[T, E : Error] suspend(((T) -> Unit, (E) -> Unit) -> Unit) -> T raise E

// Types and methods
pub type! Error_ Value
pub suberror Error_ Value
fn Error_::cause(Self) -> Value?
fn[T] Error_::wrap(() -> Value, map_ok~ : (Value) -> T = ..) -> T!Self
fn[T] Error_::wrap(() -> Value, map_ok~ : (Value) -> T = ..) -> T raise Self
impl Show for Error_

type Nullable[_]
fn[T] Nullable::from_option(T?) -> Self[T]
#deprecated
fn[T] Nullable::get_exn(Self[T]) -> T
fn[T] Nullable::is_null(Self[T]) -> Bool
fn[T] Nullable::null() -> Self[T]
fn[T] Nullable::to_option(Self[T]) -> T?
fn[T] Nullable::unwrap(Self[T]) -> T

pub type Object Value
fn[K, V] Object::extend_iter(Self, Iter[(K, V)]) -> Unit
Expand All @@ -72,22 +46,26 @@ fn[K, V] Object::from_iter(Iter[(K, V)]) -> Self
fn[K, V] Object::from_iter2(Iter2[K, V]) -> Self
fn Object::from_value(Value) -> Optional[Self]
fn Object::from_value_unchecked(Value) -> Self
fn Object::inner(Self) -> Value
fn Object::new() -> Self
fn[K, V] Object::op_get(Self, K) -> V
fn[K, V] Object::op_set(Self, K, V) -> Unit
fn Object::to_value(Self) -> Value

type Optional[_]
fn[T] Optional::from_option(T?) -> Self[T]
#deprecated
fn[T] Optional::get_exn(Self[T]) -> T
fn[T] Optional::is_undefined(Self[T]) -> Bool
fn[T] Optional::to_option(Self[T]) -> T?
fn[T] Optional::undefined() -> Self[T]
fn[T] Optional::unwrap(Self[T]) -> T

pub extern type Promise
#external
pub type Promise
fn Promise::all(Array[Self]) -> Self
fn[T] Promise::unsafe_new(() -> T!) -> Self
async fn Promise::wait(Self) -> Value!
fn[T] Promise::unsafe_new(() -> T raise) -> Self
async fn Promise::wait(Self) -> Value raise

type Symbol
fn Symbol::make() -> Self
Expand Down Expand Up @@ -179,16 +157,17 @@ fn[A, B, C, D, E, F : Cast, G, H] Union8::to5(Self[A, B, C, D, E, F, G, H]) -> F
fn[A, B, C, D, E, F, G : Cast, H] Union8::to6(Self[A, B, C, D, E, F, G, H]) -> G?
fn[A, B, C, D, E, F, G, H : Cast] Union8::to7(Self[A, B, C, D, E, F, G, H]) -> H?

pub extern type Value
#external
pub type Value
fn[Arg, Result] Value::apply(Self, Array[Arg]) -> Result
fn[Arg, Result] Value::apply_with_index(Self, Int, Array[Arg]) -> Result
fn[Arg, Result] Value::apply_with_string(Self, String, Array[Arg]) -> Result
fn[Arg, Result] Value::apply_with_symbol(Self, Symbol, Array[Arg]) -> Result
fn[T] Value::cast(Self) -> T
fn[T] Value::cast_from(T) -> Self
fn Value::extends(Self, Self) -> Self
fn Value::from_json(Json) -> Self!
fn Value::from_json_string(String) -> Self!
fn Value::from_json(Json) -> Self raise
fn Value::from_json_string(String) -> Self raise
fn[T] Value::get_with_index(Self, Int) -> T
fn[T] Value::get_with_string(Self, String) -> T
fn[T] Value::get_with_symbol(Self, Symbol) -> T
Expand All @@ -206,8 +185,8 @@ fn[Arg, Result] Value::new_with_symbol(Self, Symbol, Array[Arg]) -> Result
fn[T] Value::set_with_index(Self, Int, T) -> Unit
fn[T] Value::set_with_string(Self, String, T) -> Unit
fn[T] Value::set_with_symbol(Self, Symbol, T) -> Unit
fn Value::to_json(Self) -> Json!
fn Value::to_json_string(Self) -> String!
fn Value::to_json(Self) -> Json raise
fn Value::to_json_string(Self) -> String raise
fn Value::to_string(Self) -> String
impl Show for Value
impl @json.FromJson for Value
Expand Down
12 changes: 11 additions & 1 deletion src/js/null.mbt
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
///|
extern type Nullable[_]
#external
type Nullable[_]

///|
pub fn[T] Nullable::is_null(self : Nullable[T]) -> Bool {
Value::is_null(Value::cast_from(self))
}

///|
#deprecated("get_exn does not check for null values. Use unwrap instead")
pub fn[T] Nullable::get_exn(self : Nullable[T]) -> T = "%identity"

///| Unwraps the nullable value, panicking if it is null.
pub fn[T] Nullable::unwrap(self : Nullable[T]) -> T {
if self.is_null() {
abort("Cannot unwrap a null value")
}
self.get_exn()
}

///|
pub fn[T] Nullable::to_option(self : Nullable[T]) -> T? {
guard not(Value::cast_from(self).is_null()) else { None }
Expand Down
6 changes: 3 additions & 3 deletions src/js/object.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub fn Object::from_value_unchecked(value : Value) -> Object {

///|
pub fn Object::to_value(self : Object) -> Value {
self._
self.inner()
}

///|
Expand Down Expand Up @@ -52,10 +52,10 @@ pub fn[K, V] Object::from_iter2(it : Iter2[K, V]) -> Object {

///|
pub fn[K, V] Object::op_get(self : Object, key : K) -> V {
self._.get_ffi(Value::cast_from(key)).cast()
self.inner().get_ffi(Value::cast_from(key)).cast()
}

///|
pub fn[K, V] Object::op_set(self : Object, key : K, value : V) -> Unit {
self._.set_ffi(Value::cast_from(key), Value::cast_from(value))
self.inner().set_ffi(Value::cast_from(key), Value::cast_from(value))
}
6 changes: 3 additions & 3 deletions src/js/object_test.mbt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
///|
test "Object::extend_object" {
let obj = @js.Object::from_value_unchecked(
@json.from_json!({ "a": 1, "b": 2, "c": 3 }),
@json.from_json({ "a": 1, "b": 2, "c": 3 }),
)
let obj1 = @js.Object::from_value_unchecked(
@json.from_json!({ "b": [6, 7], "d": 44, "a": 55 }),
@json.from_json({ "b": [6, 7], "d": 44, "a": 55 }),
)
@json.inspect!(obj.extend_object(obj1)._.to_json!(), content={
@json.inspect(obj.extend_object(obj1).inner().to_json(), content={
"a": 55,
"b": [6, 7],
"c": 3,
Expand Down
14 changes: 12 additions & 2 deletions src/js/optional.mbt
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
///|
extern type Optional[_]
#external
type Optional[_]

///|
pub fn[T] Optional::is_undefined(self : Optional[T]) -> Bool {
self |> Value::cast_from |> Value::is_undefined
}

///|
///|
#deprecated("get_exn does not check for undefined values. Use unwrap instead")
pub fn[T] Optional::get_exn(self : Optional[T]) -> T = "%identity"

///| Unwraps the optional value, panicking if it is undefined.
pub fn[T] Optional::unwrap(self : Self[T]) -> T {
if self.is_undefined() {
abort("Cannot unwrap an undefined value")
}
self.get_exn()
}

///|
pub fn[T] Optional::to_option(self : Optional[T]) -> T? {
if Value::cast_from(self).is_undefined() {
Expand Down
6 changes: 3 additions & 3 deletions src/js/require.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ extern "js" fn require_ffi(path : String) -> Value = "(path) => require(path)"

///|
pub fn require(path : String, keys~ : Array[String] = []) -> Value {
keys.fold(init=require_ffi(path), fn {
acc, key => acc.get_ffi(Value::cast_from(key))
})
keys.fold(init=require_ffi(path), (acc, key) => acc.get_ffi(
Value::cast_from(key),
))
}
3 changes: 2 additions & 1 deletion src/js/symbol.mbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
///|
extern type Symbol
#external
type Symbol

///|
pub fn Symbol::make() -> Symbol {
Expand Down
Loading