diff --git a/src/glua.gleam b/src/glua.gleam index b2b740c..d9715a1 100644 --- a/src/glua.gleam +++ b/src/glua.gleam @@ -3,6 +3,7 @@ //// Gleam wrapper around [Luerl](https://github.com/rvirding/luerl). import gleam/bool +import gleam/dict import gleam/dynamic import gleam/dynamic/decode import gleam/int @@ -16,7 +17,7 @@ import gleam/string pub type Lua /// Represents the errors than can happend during the parsing and execution of Lua code -pub type LuaError(error) { +pub type Error(error) { /// The compilation process of the Lua code failed because of the presence of one or more compile errors. LuaCompileFailure(errors: List(LuaCompileError)) /// The Lua environment threw an exception during code execution. @@ -64,7 +65,7 @@ pub type LuaRuntimeExceptionKind { UnknownException } -/// Turns a `glua.LuaError` value into a human-readable string +/// Turns a `glua.Error` value into a human-readable string /// /// ## Examples /// @@ -117,7 +118,7 @@ pub type LuaRuntimeExceptionKind { /// glua.format_error(e) /// // -> "Expected String, but found Int" /// ``` -pub fn format_error(error: LuaError(e)) -> String { +pub fn format_error(error: Error(e)) -> String { case error { LuaCompileFailure(errors) -> "Lua compile error: " @@ -241,17 +242,16 @@ fn format_unknown_error(error: dynamic.Dynamic) -> String /// let state = glua.new() /// let result = { /// use #(new_state, _) <- result.try( -/// glua.run(state, glua.set(keys: ["a_number"], value: glua.int(36))) +/// glua.exec(state, glua.set(keys: ["a_number"], value: glua.int(36))) /// ) /// use #(new_state, ret) <- result.try( -/// glua.run(state, glua.eval("return math.sqrt(a_number)")) +/// glua.exec(state, glua.eval("return math.sqrt(a_number)")) /// ) /// /// // we know that `math.sqrt` only returns one value /// let assert [ref] = ret /// /// glua.run(new_state, glua.dereference(ref:, using: decode.float)) -/// |> result.map(pair.second) /// } /// result /// // -> Ok(6.0) @@ -275,7 +275,7 @@ fn format_unknown_error(error: dynamic.Dynamic) -> String /// glua.dereference(ref:, using: decode.float) /// } /// -/// glua.run(state, action) |> result.map(pair.second) +/// glua.run(state, action) /// // -> Ok(6.0) /// ``` /// @@ -283,7 +283,7 @@ fn format_unknown_error(error: dynamic.Dynamic) -> String /// would return in case it succeeds, and `error` is the type of custom errors that /// the `Action` could return. pub opaque type Action(return, error) { - Action(function: fn(Lua) -> Result(#(Lua, return), LuaError(error))) + Action(function: fn(Lua) -> Result(#(Lua, return), Error(error))) } /// Runs an `Action` within a Lua environment. @@ -292,17 +292,40 @@ pub opaque type Action(return, error) { /// /// ```gleam /// let state = glua.new() -/// glua.run(state, { -/// use ret <- glua.then(glua.eval("return 'Hello from Lua!'")) -/// use ref <- glua.try(list.first(ret)) -/// glua.dereference(ref:, using: decode.string) -/// }) -/// // -> Ok(#(_state, "Hello from Lua!")) +/// +/// glua.eval(code: "return 'Hello from Lua!'") +/// |> glua.returning_multi(using: decode.string) +/// |> glua.run(state, _) +/// // -> Ok("Hello from Lua!") /// ``` pub fn run( state lua: Lua, action action: Action(return, error), -) -> Result(#(Lua, return), LuaError(error)) { +) -> Result(return, Error(error)) { + exec(lua, action) |> result.map(pair.second) +} + +/// Runs an `Action` within a Lua environment and returns both the result +/// and the updated Lua state in case of no errors. +/// +/// ## Examples +/// +/// ```gleam +/// let state = glua.new() +/// let assert Ok(#(new_state, Nil)) = +/// glua.exec(state, glua.set(["my_value"], glua.string("Hello!"))) +/// +/// glua.exec( +/// state:, +/// action: glua.eval(code: "return my_value") |> glua.returning_multi(decode.string) +/// ) +/// +/// // -> Ok(#(_state, ["Hello!"])) +/// ``` +pub fn exec( + state lua: Lua, + action action: Action(return, error), +) -> Result(#(Lua, return), Error(error)) { action.function(lua) } @@ -320,8 +343,7 @@ pub fn run( /// let my_value = 1 /// let assert Ok(#(_state, ret)) = glua.run(glua.new(), { /// use _ <- glua.then(glua.set(keys: ["my_value"], value: glua.int(my_value))) -/// use ref <- glua.then(glua.get(keys: ["my_value"])) -/// glua.dereference(ref:, using: decode.int) +/// glua.get(keys: ["my_value"]) |> glua.returning(decode.int) /// }) /// /// assert ret == my_value @@ -341,19 +363,16 @@ pub fn then(action: Action(a, e), next: fn(a) -> Action(b, e)) -> Action(b, e) { next(ret).function(new) } -/// Transforms the provided result into an `Action` by passing its value to a function -/// that yields an `Action`. -/// -/// If the input is an `Error`, then the function is not called and instead a failing `Action` -/// is returned with the original error. +/// Tries to update the return value of an `Action` by passing it to a function +/// that yields a result. /// /// This is a shorthand for writing a case with `glua.then`: /// /// ```gleam /// use fun <- glua.then(glua.get(["string", "reverse"])) -/// use return <- glua.then(glua.call_function(fun:, args: [glua.string("Hello")])) -/// use value <- glua.try(list.first(return)) -/// glua.dereference(ref: value, using: decode.string) +/// glua.call_function(fun:, args: [glua.string("Hello")]) +/// |> glua.try(list.first) +/// |> glua.returning(decode.string) /// ``` /// /// as opposed to this: @@ -366,10 +385,14 @@ pub fn then(action: Action(a, e), next: fn(a) -> Action(b, e)) -> Action(b, e) { /// _ -> glua.failure(Nil) /// } /// ``` -pub fn try(result: Result(a, e), next: fn(a) -> Action(b, e)) -> Action(b, e) { - case result { - Ok(ret) -> Action(next(ret).function) - Error(err) -> failure(err) +pub fn try( + action action: Action(a, e), + apply fun: fn(a) -> Result(b, e), +) -> Action(b, e) { + use ret <- then(action) + case fun(ret) { + Ok(x) -> success(x) + Error(e) -> failure(e) } } @@ -383,7 +406,7 @@ pub fn try(result: Result(a, e), next: fn(a) -> Action(b, e)) -> Action(b, e) { /// use ret <- glua.then(glua.eval(code: "local a = 1")) /// use <- glua.guard(when: ret == [], return: "expected at least one value from Lua") /// -/// glua.fold(ret, glua.dereference(_, using: decode.int)) +/// glua.fold(ret, glua.dereference(_, using: decode.int)) /// }) /// // -> Error(glua.CustomError("expected at least one value from Lua")) /// ``` @@ -401,7 +424,7 @@ pub fn guard( /// /// ```gleam /// glua.run(glua.new(), glua.success("my value")) -/// // -> Ok(#(_state, "my_value")) +/// // -> Ok("my_value") /// ``` pub fn success(value: a) -> Action(a, e) { use state <- Action @@ -442,12 +465,13 @@ pub fn error_with_level(message: String, level: Int) -> Action(List(Value), e) { /// ## Examples /// /// ```gleam -/// glua.run(glua.new(), { -/// use ref <- glua.then(glua.get(keys: ["_VERSION"])) -/// use version <- glua.map(glua.dereference(ref:, using: decode.string)) +/// glua.get(keys: ["_VERSION"]) +/// |> glua.returning(using: decode.string) +/// |> glua.map(fn(version) { /// "glua supports " <> version /// }) -/// // -> Ok(#(_state, "glua supports Lua 5.3")) +/// |> glua.run(glua.new(), _) +/// // -> Ok("glua supports Lua 5.3") /// ``` /// /// ```gleam @@ -472,12 +496,11 @@ pub fn map(over action: Action(a, e), with fun: fn(a) -> b) -> Action(b, e) { /// let numbers = [9, 16, 25] /// let keys = ["math", "sqrt"] /// glua.run(glua.new(), glua.fold(numbers, fn(n) { -/// use ret <- glua.then(glua.call_function_by_name(keys:, args: [glua.int(n)])) -/// -/// let assert [ref] = ret -/// glua.dereference(ref:, using: decode.float) +/// glua.call_function_by_name(keys:, args: [glua.int(n)]) +/// |> glua.try(list.first) +/// |> glua.returning(using: decode.float) /// })) -/// // -> Ok(#(_state, [3.0, 4.0, 5.0])) +/// // -> Ok([3.0, 4.0, 5.0]) /// ``` pub fn fold( over list: List(a), @@ -521,17 +544,21 @@ pub fn table(values: List(#(Value, Value))) -> Action(Value, e) { @external(erlang, "luerl_heap", "alloc_table") fn do_table(values: List(#(Value, Value)), lua: Lua) -> #(Value, Lua) -pub fn table_decoder( - keys_decoder: decode.Decoder(a), - values_decoder: decode.Decoder(b), -) -> decode.Decoder(List(#(a, b))) { - let inner = { - use key <- decode.field(0, keys_decoder) - use val <- decode.field(1, values_decoder) - decode.success(#(key, val)) - } - - decode.list(of: inner) +/// A decoder for list-style Lua tables. +/// +/// ## Examples +/// +/// ```gleam +/// glua.eval("return { 1, 2, 3 }") +/// |> glua.try(list.first) +/// |> glua.returning(glua.table_list_decoder(decode.int)) +/// |> glua.run(glua.new(), _) +/// // -> Ok([1, 2, 3]) +/// ``` +pub fn table_list_decoder( + inner decoder: decode.Decoder(a), +) -> decode.Decoder(List(a)) { + decode.dict(decode.int, decoder) |> decode.map(dict.values) } /// Encodes a Gleam function into a Lua function. @@ -570,10 +597,6 @@ fn decode_lua_function( fn(List(Value)) -> Action(List(Value), e), ) -pub fn list(encoder: fn(a) -> Value, values: List(a)) -> List(Value) { - list.map(values, encoder) -} - /// Encodes any Gleam value as a reference that can be passed to a Lua program. /// /// Deferencing a userdata value inside Lua code will cause a Lua exception. @@ -598,12 +621,11 @@ pub fn list(encoder: fn(a) -> Value, values: List(a)) -> List(Value) { /// value: userdata /// )) /// -/// use ret <- glua.then(glua.eval(code: "return a_user")) -/// use ref <- glua.try(list.first(ret)) -/// -/// glua.dereference(ref:, using: user_decoder) +/// glua.eval(code: "return a_user") +/// |> glua.try(list.first) +/// |> glua.returning(using: user_decoder) /// }) -/// // -> Ok(#(_state, User("Jhon Doe", False))) +/// // -> Ok(User("Jhon Doe", False)) /// ``` /// /// ```gleam @@ -640,24 +662,26 @@ fn do_function(fun: fn(List(Value)) -> Action(List(Value), e)) -> Value /// ## Examples /// /// ```gleam -/// glua.run(glua.new(), { -/// use ret <- glua.then(glua.eval(code: "return 'Hello from Lua!'")) -/// use ref <- glua.try(list.first(ret)) +/// glua.run(glua.new(), { +/// use ref <- glua.then( +/// glua.eval(code: "return 'Hello from Lua!'") +/// |> glua.try(list.first) +/// ) /// /// glua.dereference(ref:, using: decode.string) -/// } -/// // -> Ok(#(_state, "Hello from Lua!")) +/// }) +/// // -> Ok("Hello from Lua!") /// ``` /// /// ```gleam -/// let assert Ok(#(state, [ref1, ref2])) = glua.run( +/// let assert Ok(#(state, [ref1, ref2])) = glua.exec( /// glua.new(), /// glua.eval(code: "return 1, true") /// ) /// -/// let assert Ok(#(_state, 1)) = +/// let assert Ok(1) = /// glua.run(state, glua.dereference(ref: ref1, using: decode.int)) -/// let assert Ok(#(_state, True)) = +/// let assert Ok(True) = /// glua.run(state, glua.dereference(ref: ref2, using: decode.bool)) /// ``` pub fn dereference( @@ -677,6 +701,23 @@ pub fn dereference( @external(erlang, "glua_ffi", "dereference") fn do_dereference(lua: Lua, ref: Value) -> dynamic.Dynamic +/// Transforms an `Action` that returns a reference to a Lua value into an `Action` that returns +/// a typed Gleam value. +/// +/// ## Examples +/// +/// ```gleam +/// let decoder = +/// decode.dict(decode.string, decode.int) +/// |> decode.map(dict.to_list) +/// +/// glua.eval(code: "return { a = 1, b = 2 }") +/// |> glua.try(apply: list.first) +/// |> glua.returning(using: decoder) +/// |> glua.run(glua.new(), _) +/// +/// // -> Ok([#("a", 1), #("b", 2)]) +/// ``` pub fn returning( action act: Action(Value, e), using decoder: decode.Decoder(a), @@ -685,7 +726,9 @@ pub fn returning( dereference(ref, decoder) } -pub fn returning_list( +/// Same as `glua.returning`, but works on an `Action` that returns multiple references to Lua values +/// instead of a single one. +pub fn returning_multi( action act: Action(List(Value), e), using decoder: decode.Decoder(a), ) -> Action(List(a), e) { @@ -724,7 +767,7 @@ pub const default_sandbox = [ /// In case you want to sandbox more Lua values, pass to `glua.sandbox` the returned Lua state. pub fn new_sandboxed( allow excluded: List(List(String)), -) -> Result(Lua, LuaError(e)) { +) -> Result(Lua, Error(e)) { list_substraction(default_sandbox, excluded) |> list.try_fold(from: new(), with: sandbox) } @@ -746,10 +789,7 @@ fn list_substraction(a: List(a), b: List(a)) -> List(a) /// // 'important_file' was not deleted /// assert exception == glua.ErrorCall(["os.execute is sandboxed"]) /// ``` -pub fn sandbox( - state lua: Lua, - keys keys: List(String), -) -> Result(Lua, LuaError(e)) { +pub fn sandbox(state lua: Lua, keys keys: List(String)) -> Result(Lua, Error(e)) { let msg = string.join(keys, with: ".") <> " is sandboxed" set(["_G", ..keys], sandbox_fun(msg)).function(lua) @@ -764,11 +804,10 @@ fn sandbox_fun(msg: String) -> Value /// ## Examples /// /// ```gleam -/// glua.run(glua.new(), { -/// use ref <- glua.then(glua.get(keys: ["_VERSION"])) -/// glua.dereference(ref:, using: decode.string) -/// }) -/// // -> Ok(#(_state, "Lua 5.3")) +/// glua.get(keys: ["_VERSION"]) +/// |> glua.returning(using: decode.string) +/// |> glua.run(glua.new(), _) +/// // -> Ok("Lua 5.3") /// ``` /// /// ```gleam @@ -777,11 +816,11 @@ fn sandbox_fun(msg: String) -> Value /// keys: ["my_table", "my_value"], /// value: glua.bool(True) /// )) -/// use ref <- glua.then(glua.get(keys: ["my_table", "my_value"])) /// -/// glua.dereference(ref:, using: decode.bool) +/// glua.get(keys: ["my_table", "my_value"])) +/// |> glua.returning(using: decode.bool) /// }) -/// // -> Ok(#(_state, True)) +/// // -> Ok(True) /// ``` /// /// ```gleam @@ -795,7 +834,7 @@ pub fn get(keys keys: List(String)) -> Action(Value, e) { } @external(erlang, "glua_ffi", "get_table_keys") -fn do_get(lua: Lua, keys: List(String)) -> Result(Value, LuaError(e)) +fn do_get(lua: Lua, keys: List(String)) -> Result(Value, Error(e)) /// Gets a private value that is not exposed to the Lua runtime. /// @@ -811,13 +850,13 @@ pub fn get_private( state lua: Lua, key key: String, using decoder: decode.Decoder(a), -) -> Result(a, LuaError(e)) { +) -> Result(a, Error(e)) { use value <- result.try(do_get_private(lua, key)) decode.run(value, decoder) |> result.map_error(UnexpectedResultType) } @external(erlang, "glua_ffi", "get_private") -fn do_get_private(lua: Lua, key: String) -> Result(dynamic.Dynamic, LuaError(e)) +fn do_get_private(lua: Lua, key: String) -> Result(dynamic.Dynamic, Error(e)) /// Sets a value in the Lua environment. /// @@ -834,26 +873,24 @@ fn do_get_private(lua: Lua, key: String) -> Result(dynamic.Dynamic, LuaError(e)) /// keys: ["my_number"], /// value: glua.int(10) /// )) -/// use ref <- glua.get(keys: ["my_number"]) /// -/// glua.dereference(ref:, using: decode.int) +/// glua.get(keys: ["my_number"]) +/// |> glua.returning(using: decode.int) /// }) -/// // -> Ok(#(_state, 10)) +/// // -> Ok(10) /// ``` /// /// ```gleam /// let emails = ["jhondoe@example.com", "lucy@example.com"] -/// let assert Ok(#(_state, results)) = glua.run(glua.new(), { +/// let assert Ok(results) = glua.run(glua.new(), { /// use encoded <- glua.then(glua.table( /// list.index_map(emails, fn(email, i) { #(glua.int(i + 1), glua.string(email)) }) /// )) /// use _ <- glua.then(glua.set(["info", "emails"], encoded)) /// -/// use ret <- glua.then(glua.eval(code: "return info.emails")) -/// use ref <- glua.try(list.first(ret)) -/// -/// glua.dereference(ref:, using: decode.dict(decode.int, decode.string)) -/// |> glua.map(dict.values) +/// glua.eval(code: "return info.emails")) +/// |> glua.try(list.first) +/// |> glua.returning(using: glua.table_list_decoder(decode.string)) /// }) /// /// assert results == emails @@ -888,7 +925,7 @@ pub fn set(keys keys: List(String), value val: Value) -> Action(Nil, e) { /// ```gleam /// assert glua.new() /// |> glua.set("secret_value", "private_value") -/// |> glua.get("secret_value") +/// |> glua.get("secret_value", decode.string) /// == Ok("secret_value") /// ``` pub fn set_private(state lua: Lua, key key: String, value value: a) -> Lua { @@ -919,14 +956,14 @@ pub fn set_api( /// let my_scripts_paths = ["app/scripts/lua/?.lua"] /// glua.run(glua.new(), { /// use _ <- glua.then(glua.set_lua_paths(paths: my_scripts_paths)) -/// use ret <- glua.then(glua.eval( -/// code: "local my_math = require 'my_script'; return my_math.square(3)" -/// )) -/// use ref <- glua.try(list.first(ret)) /// -/// glua.dereference(ref:, using: decode.int) +/// glua.eval( +/// code: "local my_math = require 'my_script'; return my_math.square(3)" +/// ) +/// |> glua.try(list.first) +/// |> glua.returning(decode.int) /// }) -/// // -> Ok(#(_state, 9)) +/// // -> Ok(9) /// ``` pub fn set_lua_paths(paths paths: List(String)) -> Action(Nil, e) { let paths = string.join(paths, with: ";") |> string @@ -934,7 +971,7 @@ pub fn set_lua_paths(paths paths: List(String)) -> Action(Nil, e) { } @external(erlang, "glua_ffi", "set_table_keys") -fn do_set(lua: Lua, keys: List(String), val: a) -> Result(Lua, LuaError(e)) +fn do_set(lua: Lua, keys: List(String), val: a) -> Result(Lua, Error(e)) @external(erlang, "luerl", "put_private") fn do_set_private(key: String, value: a, lua: Lua) -> Lua @@ -944,7 +981,7 @@ fn do_set_private(key: String, value: a, lua: Lua) -> Lua /// ## Examples /// /// ```gleam -/// let lua = glua.set_private(glua.new(), "my_value", "will_be_removed" +/// let lua = glua.set_private(glua.new(), "my_value", "will_be_removed") /// assert glua.get(lua, "my_value", decode.string) == Ok("will_be_removed") /// /// assert glua.delete_private(lua, "my_value") @@ -966,7 +1003,7 @@ pub fn load(code code: String) -> Action(Chunk, e) { } @external(erlang, "glua_ffi", "load") -fn do_load(lua: Lua, code: String) -> Result(#(Lua, Chunk), LuaError(e)) +fn do_load(lua: Lua, code: String) -> Result(#(Lua, Chunk), Error(e)) /// Parses a Lua source file and returns it as a compiled chunk. /// @@ -976,30 +1013,27 @@ pub fn load_file(path path: String) -> Action(Chunk, e) { } @external(erlang, "glua_ffi", "load_file") -fn do_load_file(lua: Lua, path: String) -> Result(#(Lua, Chunk), LuaError(e)) +fn do_load_file(lua: Lua, path: String) -> Result(#(Lua, Chunk), Error(e)) /// Evaluates a string of Lua code. /// /// ## Examples /// /// ```gleam -/// glua.run(glua.new(), { -/// use ret <- glua.then(glua.eval(code: "return 1 + 2")) -/// use ref <- glua.try(list.first(ret)) -/// -/// glua.dereference(ref:, using: decode.int) -/// }) -/// // -> Ok(#(_state, 3)) +/// glua.eval(code: "return 1 + 2") +/// |> glua.returning_multi(using: decode.int) +/// |> glua.run(glua.new(), _) +/// // -> Ok([3]) /// ``` /// /// ```gleam -/// let assert Ok(#(state, [ref1, ref2])) = glua.run(glua.new(), glua.eval( +/// let assert Ok(#(state, [ref1, ref2])) = glua.exec(glua.new(), glua.eval( /// code: "return 'hello, world!', 10", /// )) /// -/// let assert Ok(#(_state, "hello world")) = +/// let assert Ok("hello world") = /// glua.run(state, glua.dereference(ref: ref1, using: decode.string)) -/// let assert Ok(#(_state, 10)) = +/// let assert Ok(10) = /// glua.run(state, glua.dereference(ref: ref2, using: decode.int)) /// ``` /// @@ -1019,23 +1053,18 @@ pub fn eval(code code: String) -> Action(List(Value), e) { } @external(erlang, "glua_ffi", "eval") -fn do_eval(lua: Lua, code: String) -> Result(#(Lua, List(Value)), LuaError(e)) +fn do_eval(lua: Lua, code: String) -> Result(#(Lua, List(Value)), Error(e)) /// Evaluates a compiled chunk of Lua code. /// /// ## Examples /// /// ```gleam -/// glua.run(glua.new(), { -/// use chunk <- glua.then(glua.load( -/// code: "return 'hello, world!'" -/// )) -/// -/// use ret <- glua.then(glua.eval_chunk(chunk:)) -/// use ref <- glua.try(list.first(ret)) -/// -/// glua.dereference(ref:, using: decode.string) -/// // -> Ok(#(_state, "hello, world!")) +/// glua.load(code: "return 'hello, world!'") +/// |> glua.then(glua.eval_chunk) +/// |> glua.returning_multi(using: decode.string) +/// |> glua.run(glua.new(), _) +/// // -> Ok(["hello, world!"]) /// ``` pub fn eval_chunk(chunk chunk: Chunk) -> Action(List(Value), e) { Action(do_eval_chunk(_, chunk)) @@ -1045,22 +1074,19 @@ pub fn eval_chunk(chunk chunk: Chunk) -> Action(List(Value), e) { fn do_eval_chunk( lua: Lua, chunk: Chunk, -) -> Result(#(Lua, List(Value)), LuaError(e)) +) -> Result(#(Lua, List(Value)), Error(e)) /// Evaluates a Lua source file. /// /// ## Examples /// /// ```gleam -/// glua.run(glua.new(), { -/// use ret <- glua.then(glua.eval_file( -/// path: "path/to/hello.lua", -/// )) -/// use ref <- glua.try(list.first(ret)) -/// -/// glua.dereference(ref:, using: decode.string) -/// }) -/// // -> Ok(#(_state, "hello, world!")) +/// glua.eval_file( +/// path: "path/to/hello.lua", +/// ) +/// |> glua.returning_multi(using: decode.string) +/// |> glua.run(glua.new(), _) +/// // -> Ok(["hello, world!"]) /// ``` /// /// ```gleam @@ -1074,10 +1100,7 @@ pub fn eval_file(path path: String) -> Action(List(Value), e) { } @external(erlang, "glua_ffi", "eval_file") -fn do_eval_file( - lua: Lua, - path: String, -) -> Result(#(Lua, List(Value)), LuaError(e)) +fn do_eval_file(lua: Lua, path: String) -> Result(#(Lua, List(Value)), Error(e)) /// Calls a Lua function by reference. /// @@ -1085,18 +1108,17 @@ fn do_eval_file( /// /// ```gleam /// glua.run(glua.new(), { -/// use ret <- glua.then(glua.eval(code: "return math.sqrt")) -/// use fun <- glua.try(list.first(ret)) +/// use fun <- glua.then( +/// glua.eval(code: "return math.sqrt") |> glua.try(list.first) +/// ) /// -/// use ret <- glua.then(glua.call_function( +/// glua.call_function( /// ref: fun, /// args: [glua.int(81)], -/// )) -/// use ref <- glua.try(list.first(ret)) -/// -/// glua.dereference(ref:, using: decode.float) +/// ) +/// |> glua.returning_multi(using: decode.float) /// }) -/// // -> Ok(#(_state, 9.0)) +/// // -> Ok([9.0]) /// ``` /// /// ```gleam @@ -1112,18 +1134,15 @@ fn do_eval_file( /// " /// /// glua.run(glua.new(), { -/// use ret <- glua.then(glua.eval(code:)) -/// use ref <- glua.try(list.first(ret)) +/// use fun <- glua.then(glua.eval(code:) |> glua.try(list.first)) /// -/// use ret <- glua.then(glua.call_function( +/// glua.call_function( /// ref: fun, /// args: [glua.int(10)], -/// )) -/// use ref <- glua.try(list.first(ret)) -/// -/// glua.dereference(ref:, using: decode.int) +/// ) +/// |> glua.returning_multi(using: decode.int) /// }) -/// // -> Ok(#(_state, 55)) +/// // -> Ok([55]) /// ``` pub fn call_function( ref fun: Value, @@ -1137,7 +1156,7 @@ fn do_call_function( lua: Lua, fun: Value, args: List(Value), -) -> Result(#(Lua, List(Value)), LuaError(e)) +) -> Result(#(Lua, List(Value)), Error(e)) /// Gets a reference to the function at `keys`, then inmediatly calls it with the provided `args`. /// @@ -1146,16 +1165,13 @@ fn do_call_function( /// ## Examples /// /// ```gleam -/// glua.run(glua.new(), { -/// use ret <- glua.then(glua.call_function_by_name( -/// keys: ["string", "upper"], -/// args: [glua.string("hello from Gleam!")] -/// )) -/// use ref <- glua.try(list.first(ret)) -/// -/// glua.dereference(ref:, using: decode.string) -/// }) -/// // -> Ok(#(_state, "HELLO FROM GLEAM!")) +/// glua.call_function_by_name( +/// keys: ["string", "upper"], +/// args: [glua.string("hello from Gleam!")] +/// )) +/// |> glua.returning_multi(using: decode.string) +/// |> glua.run(glua.new(), _) +/// // -> Ok("HELLO FROM GLEAM!") /// ``` pub fn call_function_by_name( keys keys: List(String), @@ -1181,9 +1197,9 @@ pub fn call_function_by_name( /// use mt <- glua.then(glua.table([#(glua.string("__index"), glua.function(fun))])) /// use _ <- glua.then(glua.call_function(lib.set_metatable(), [tbl, mt])) /// -/// glua.index(tbl, "a_key") +/// glua.index(tbl, "a_key") |> glua.returning(decode.string) /// }) -/// // -> Ok(#(_state, ["fixed value"])) +/// // -> Ok("fixed value") /// ``` pub fn index(table ref: Value, key key: String) -> Action(Value, e) { Action(do_index(_, ref, key)) @@ -1194,7 +1210,7 @@ fn do_index( state: Lua, ref: Value, key: String, -) -> Result(#(Lua, Value), LuaError(e)) +) -> Result(#(Lua, Value), Error(e)) /// Sets `value` under `key` of the provided table. /// @@ -1233,4 +1249,4 @@ fn do_new_index( ref: Value, key: String, val: Value, -) -> Result(#(Lua, Nil), LuaError(e)) +) -> Result(#(Lua, Nil), Error(e)) diff --git a/src/glua/lib/require.gleam b/src/glua/lib/require.gleam deleted file mode 100644 index 8b13789..0000000 --- a/src/glua/lib/require.gleam +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/glua_ffi.erl b/src/glua_ffi.erl index 77cbe48..b8cabe9 100644 --- a/src/glua_ffi.erl +++ b/src/glua_ffi.erl @@ -3,17 +3,33 @@ -import(ttdict, [fold/3]). -include_lib("luerl/include/luerl.hrl"). --export([get_stacktrace/1, dereference/2, coerce/1, coerce_nil/0, wrap_fun/1, decode_fun/1, sandbox_fun/1, get_table_key/3, - get_table_keys/2, get_private/2, set_table_key/4, set_table_keys/3, load/2, load_file/2, eval/2, eval_file/2, - eval_chunk/2, call_function/3]). - +-export([ + get_stacktrace/1, + dereference/2, + coerce/1, + coerce_nil/0, + wrap_fun/1, + decode_fun/1, + sandbox_fun/1, + get_table_key/3, + get_table_keys/2, + get_private/2, + set_table_key/4, + set_table_keys/3, + load/2, + load_file/2, + eval/2, + eval_file/2, + eval_chunk/2, + call_function/3 +]). %% helper to convert luerl return values to a format %% that is more suitable for use in Gleam code to_gleam(Value) -> case Value of - {ok, Result, LuaState} -> - {ok, {LuaState, Result}}; + {ok, Result, St0} -> + {ok, {St0, Result}}; {ok, _} = Result -> Result; {lua_error, _, _} = Error -> @@ -29,25 +45,32 @@ to_gleam(Value) -> dereference(St, LT) -> dereference(LT, St, []). -dereference(nil, _, _) -> nil; -dereference(false, _, _) -> false; -dereference(true, _, _) -> true; +dereference(nil, _, _) -> + nil; +dereference(false, _, _) -> + false; +dereference(true, _, _) -> + true; dereference(B, _, _) when is_binary(B) -> B; -dereference(N, _, _) when is_number(N) -> N; %Integers and floats -dereference(#tref{}=T, St, In) -> +%Integers and floats +dereference(N, _, _) when is_number(N) -> N; +dereference(#tref{} = T, St, In) -> dereference_table(T, St, In); -dereference(#usdref{}=U, St, _In) -> - {#userdata{d=Data},_} = luerl_heap:get_userdata(U, St), +dereference(#usdref{} = U, St, _In) -> + {#userdata{d = Data}, _} = luerl_heap:get_userdata(U, St), Data; -dereference(#funref{}=Fun, _St, _In) -> +dereference(#funref{} = Fun, _St, _In) -> dereference_fun(fun(Args, State) -> luerl_emul:functioncall(Fun, Args, State) end); -dereference(#erl_func{code=Fun}, _St, _In) -> - Fun; %Just the bare fun -dereference(#erl_mfa{m=M, f=F}, _St, _In) -> +dereference(#erl_func{code = Fun}, _St, _In) -> + %Just the bare fun + Fun; +dereference(#erl_mfa{m = M, f = F}, _St, _In) -> dereference_fun(fun(Args, State) -> M:F(nil, Args, State) end); -dereference(Lua, _, _) -> error({badarg,Lua}). %Shouldn't have anything else +%Shouldn't have anything else +dereference(Lua, _, _) -> + error({badarg, Lua}). -dereference_table(#tref{i=N}=T, St, In0) -> +dereference_table(#tref{i = N} = T, St, In0) -> case lists:member(N, In0) of true -> % Been here before @@ -59,8 +82,8 @@ dereference_table(#tref{i=N}=T, St, In0) -> #table{a = Arr, d = Dict} -> Fun = fun(K, V, Acc) -> Acc#{ - dereference(K, St, In1) - => dereference(V, St, In1) + dereference(K, St, In1) => + dereference(V, St, In1) } end, M0 = ttdict:fold(Fun, #{}, Dict), @@ -86,69 +109,73 @@ map_error({error, Errors, _}) -> {lua_compile_failure, lists:map(fun map_compile_error/1, Errors)}; map_error({lua_error, {illegal_index, Value, Index}, State}) -> FormattedIndex = unicode:characters_to_binary(Index), - FormattedValue = unicode:characters_to_binary(io_lib:format("~p",[luerl:decode(Value, State)])), - {lua_runtime_exception, {illegal_index, FormattedIndex, FormattedValue}, State}; + FormattedValue = unicode:characters_to_binary( + io_lib:format("~p", [luerl:decode(Value, State)]) + ), + {lua_runtime_exception, {illegal_index, FormattedIndex, FormattedValue}, State}; map_error({lua_error, {error_call, Args}, State}) -> case Args of [Msg, Level] when is_binary(Msg) andalso is_integer(Level) -> {lua_runtime_exception, {error_call, Msg, {some, Level}}, State}; [Msg] when is_binary(Msg) -> {lua_runtime_exception, {error_call, Msg, none}, State}; - % error() was called with incorrect arguments _ -> {unknown_error, {error_call, Args}} end; map_error({lua_error, {undefined_function, Value}, State}) -> {lua_runtime_exception, - {undefined_function, unicode:characters_to_binary(io_lib:format("~p",[Value]))}, State}; + {undefined_function, unicode:characters_to_binary(io_lib:format("~p", [Value]))}, State}; map_error({lua_error, {undefined_method, Obj, Value}, State}) -> {lua_runtime_exception, - {undefined_method, unicode:characters_to_binary(io_lib:format("~p", [Obj])), Value}, State}; + {undefined_method, unicode:characters_to_binary(io_lib:format("~p", [Obj])), Value}, State}; map_error({lua_error, {badarith, Operator, Args}, State}) -> FormattedOperator = unicode:characters_to_binary(atom_to_list(Operator)), FormattedArgs = - lists:map(fun(V) -> - unicode:characters_to_binary( - io_lib:format("~p", [V])) - end, - Args), + lists:map( + fun(V) -> + unicode:characters_to_binary( + io_lib:format("~p", [V]) + ) + end, + Args + ), {lua_runtime_exception, {bad_arith, FormattedOperator, FormattedArgs}, State}; map_error({lua_error, {assert_error, Msg} = Error, State}) -> case Msg of M when is_binary(M) -> {lua_runtime_exception, Error, State}; - % assert() was called with incorrect arguments _ -> {unknown_error, Error} end; map_error({lua_error, {badarg, F, Args}, State}) -> - {lua_runtime_exception, {badarg, atom_to_binary(F), Args}, State}; + {lua_runtime_exception, {badarg, atom_to_binary(F), Args}, State}; map_error({lua_error, {glua_action_error, Err}, _}) -> - Err; + Err; map_error({lua_error, _, State}) -> {lua_runtime_exception, unknown_exception, State}; map_error(Error) -> - {unknown_error, Error}. + {unknown_error, Error}. map_compile_error({Line, Type, {user, Messages}}) -> map_compile_error({Line, Type, Messages}); map_compile_error({Line, Type, {illegal, Token}}) -> - map_compile_error({Line, Type, io_lib:format("~p ~p",["Illegal token",Token])}); + map_compile_error({Line, Type, io_lib:format("~p ~p", ["Illegal token", Token])}); map_compile_error({Line, Type, Messages}) -> - Kind = case Type of - luerl_parse -> parse; - luerl_scan -> tokenize - end, + Kind = + case Type of + luerl_parse -> parse; + luerl_scan -> tokenize + end, {lua_compile_error, Line, Kind, unicode:characters_to_binary(Messages)}. - get_stacktrace(State) -> case luerl:get_stacktrace(State) of [] -> <<"">>; - Stacktrace -> format_stacktrace(State, Stacktrace) + Stacktrace -> + format_stacktrace(State, Stacktrace) end. %% turns a Lua stacktrace into a string suitable for pretty-printing @@ -209,7 +236,7 @@ format_stacktrace(State, [_ | Rest] = Stacktrace) -> %% borrowed from: https://github.com/tv-labs/lua format_args(Args) -> - ["(", lists:join(", ", lists:map(fun luerl_lib:format_value/1, Args)), ")"]. + ["(", lists:join(", ", lists:map(fun luerl_lib:format_value/1, Args)), ")"]. coerce(X) -> X. @@ -218,36 +245,35 @@ coerce_nil() -> nil. wrap_fun(Fun) -> - {erl_func, fun(Args, State) -> - {action, F} = Fun(Args), - case F(State) of - {ok, {NewState, Ret}} -> {Ret, NewState}; - {error, Err} -> - {error, map_error(lua_error({glua_action_error, Err}, State))} - end + {erl_func, fun(Args, St0) -> + {action, F} = Fun(Args), + case F(St0) of + {ok, {St1, Ret}} -> {Ret, St1}; + {error, Err} -> {error, map_error(lua_error({glua_action_error, Err}, St0))} + end end}. sandbox_fun(Msg) -> - {erl_func, fun(_, State) -> - {error, map_error(lua_error({error_call, [Msg]}, State))} + {erl_func, fun(_, St0) -> + {error, map_error(lua_error({error_call, [Msg]}, St0))} end}. decode_fun(Fun) -> case Fun of {luafun, F} -> {ok, F}; - _ -> {error, fun(_) -> {action, fun(State) -> {ok, {State, nil}} end} end} + _ -> {error, fun(_) -> {action, fun(St0) -> {ok, {St0, nil}} end} end} end. -get_table_key(Lua, Tref, Key) -> - try luerl_emul:get_table_key(Tref, Key, Lua) of - {nil, _St} -> {error, {key_not_found, [Key]}}; - {Value, St} -> {ok, {St, Value}} +get_table_key(St0, Tref, Key) -> + try luerl_emul:get_table_key(Tref, Key, St0) of + {nil, _St1} -> {error, {key_not_found, [Key]}}; + {Value, St1} -> {ok, {St1, Value}} catch error:{lua_error, _, _} = Err -> {error, map_error(Err)} end. -get_table_keys(Lua, Keys) -> - case luerl:get_table_keys(Keys, Lua) of +get_table_keys(St0, Keys) -> + case luerl:get_table_keys(Keys, St0) of {ok, nil, _} -> {error, {key_not_found, Keys}}; {ok, Value, _} -> @@ -256,45 +282,55 @@ get_table_keys(Lua, Keys) -> to_gleam(Other) end. -set_table_key(Lua, Tref, Key, Value) -> +set_table_key(St0, Tref, Key, Value) -> try - St = luerl_emul:set_table_key(Tref, Key, Value, Lua), - {ok, {St, nil}} + St1 = luerl_emul:set_table_key(Tref, Key, Value, St0), + {ok, {St1, nil}} catch error:{lua_error, _, _} = Err -> {error, map_error(Err)} end. -set_table_keys(Lua, Keys, Value) -> - to_gleam(luerl:set_table_keys(Keys, Value, Lua)). +set_table_keys(St0, Keys, Value) -> + to_gleam(luerl:set_table_keys(Keys, Value, St0)). -load(Lua, Code) -> - to_gleam(luerl:load( - unicode:characters_to_list(Code), Lua)). +load(St0, Code) -> + to_gleam( + luerl:load( + unicode:characters_to_list(Code), St0 + ) + ). -load_file(Lua, Path) -> - case luerl:loadfile(unicode:characters_to_list(Path), Lua) of - {error, [{none, file, enoent} | _], _} -> - {error, {file_not_found, Path}}; - Other -> to_gleam(Other) +load_file(St0, Path) -> + case luerl:loadfile(unicode:characters_to_list(Path), St0) of + {error, [{none, file, enoent} | _], _} -> + {error, {file_not_found, Path}}; + Other -> + to_gleam(Other) end. -eval(Lua, Code) -> - to_gleam(luerl:do( - unicode:characters_to_list(Code), Lua)). +eval(St0, Code) -> + to_gleam( + luerl:do( + unicode:characters_to_list(Code), St0 + ) + ). -eval_chunk(Lua, Chunk) -> - to_gleam(luerl:call_chunk(Chunk, Lua)). +eval_chunk(St0, Chunk) -> + to_gleam(luerl:call_chunk(Chunk, St0)). -eval_file(Lua, Path) -> - to_gleam(luerl:dofile( - unicode:characters_to_list(Path), Lua)). +eval_file(St0, Path) -> + to_gleam( + luerl:dofile( + unicode:characters_to_list(Path), St0 + ) + ). -call_function(Lua, Fun, Args) -> - to_gleam(luerl:call(Fun, Args, Lua)). +call_function(St0, Fun, Args) -> + to_gleam(luerl:call(Fun, Args, St0)). -get_private(Lua, Key) -> +get_private(St0, Key) -> try - {ok, luerl:get_private(Key, Lua)} + {ok, luerl:get_private(Key, St0)} catch error:{badkey, _} -> {error, {key_not_found, [Key]}} diff --git a/test/glua_test.gleam b/test/glua_test.gleam index 13e1c5c..d4f7d80 100644 --- a/test/glua_test.gleam +++ b/test/glua_test.gleam @@ -5,7 +5,6 @@ import gleam/int import gleam/list import gleam/option import gleam/pair -import gleam/result import gleeunit import glua import glua/lib @@ -31,12 +30,12 @@ pub fn get_table_test() { let action = { use Nil <- glua.then(glua.set(["cool_numbers"], cool_numbers)) - use ret <- glua.then(glua.call_function_by_name(["cool_numbers"], [])) - let assert [number] = ret - use table <- glua.then(glua.dereference( - ref: number, - using: decode.dict(decode.string, decode.int), - )) + use table <- glua.then( + glua.call_function_by_name(["cool_numbers"], []) + |> glua.try(list.first) + |> glua.returning(decode.dict(decode.string, decode.int)), + ) + assert table == dict.from_list(my_table) glua.success(Nil) } @@ -112,9 +111,9 @@ pub fn new_sandboxed_test() { use _ <- glua.then(glua.set_lua_paths(paths: ["./test/lua/?.lua"])) let code = "local s = require 'example'; return s" - use ref <- glua.then(glua.eval(code)) - use ref <- glua.try(list.first(ref)) - use result <- glua.map(glua.dereference(ref:, using: decode.string)) + use result <- glua.then( + glua.eval(code) |> glua.try(list.first) |> glua.returning(decode.string), + ) assert result == "LUA IS AN EMBEDDABLE LANGUAGE" glua.success(Nil) @@ -124,19 +123,18 @@ pub fn new_sandboxed_test() { pub fn guard_test() { let action = { - use ret <- glua.then( - glua.call_function_by_name(keys: ["math", "sqrt"], args: [glua.float(9.0)]), + use n <- glua.then( + glua.call_function_by_name(keys: ["math", "sqrt"], args: [glua.float(9.0)]) + |> glua.try(list.first) + |> glua.returning(decode.float), ) - use ref <- glua.try(list.first(ret)) - use n <- glua.then(glua.dereference(ref:, using: decode.float)) use <- glua.guard(when: n <. 0.0, return: Nil) glua.success("the root square of 9.0 is " <> float.to_string(n)) } - assert glua.run(glua.new(), action) |> result.map(pair.second) - == Ok("the root square of 9.0 is 3.0") + assert glua.run(glua.new(), action) == Ok("the root square of 9.0 is 3.0") let action = { use ret <- glua.then(glua.eval(code: "local a = 1")) @@ -155,19 +153,18 @@ pub fn guard_test() { pub fn map_test() { let action = glua.get(keys: ["math", "pi"]) - |> glua.then(glua.dereference(_, using: decode.float)) + |> glua.returning(decode.float) |> glua.map(float.truncate) - assert glua.run(glua.new(), action) |> result.map(pair.second) == Ok(3) + assert glua.run(glua.new(), action) == Ok(3) let action = { - use ret <- glua.then(glua.eval("return 3 * true")) - use ref <- glua.try(list.first(ret)) - - glua.map(glua.dereference(ref:, using: decode.int), fn(n) { - "the result is " <> int.to_string(n) - }) + glua.eval("return 3 * true") + |> glua.try(list.first) + |> glua.returning(decode.int) + |> glua.map(fn(n) { "the result is " <> int.to_string(n) }) } + let assert Error(glua.LuaRuntimeException(exception, _state)) = glua.run(glua.new(), action) @@ -191,9 +188,9 @@ pub fn encoding_and_decoding_nested_tables_test() { ) use Nil <- glua.then(glua.set(keys:, value: tb3)) - use ref <- glua.then(glua.get(keys:)) - - use result <- glua.then(glua.dereference(ref:, using: nested_table_decoder)) + use result <- glua.then( + glua.get(keys:) |> glua.returning(nested_table_decoder), + ) assert result == dict.from_list([ @@ -224,9 +221,11 @@ pub fn userdata_test() { } use Nil <- glua.then(glua.set(["my_userdata"], userdata)) - use ref <- glua.then(glua.eval("return my_userdata")) - use ref <- glua.try(list.first(ref)) - use result <- glua.then(glua.dereference(ref:, using: userdata_decoder)) + use result <- glua.then( + glua.eval("return my_userdata") + |> glua.try(list.first) + |> glua.returning(userdata_decoder), + ) assert result == Userdata("my-userdata", 1) @@ -234,7 +233,7 @@ pub fn userdata_test() { use Nil <- glua.then(glua.set(["my_other_userdata"], userdata)) glua.success(Nil) } - let assert Ok(#(lua, Nil)) = glua.run(glua.new(), action) + let assert Ok(#(lua, Nil)) = glua.exec(glua.new(), action) let assert Error(glua.LuaRuntimeException(glua.IllegalIndex(index, _), _)) = glua.run(lua, glua.eval("return my_other_userdata.foo")) @@ -244,16 +243,16 @@ pub fn userdata_test() { pub fn get_test() { let action = { - use ref <- glua.then(glua.get(keys: ["math", "pi"])) - use pi <- glua.then(glua.dereference(ref:, using: decode.float)) + use pi <- glua.then( + glua.get(keys: ["math", "pi"]) |> glua.returning(decode.float), + ) // TODO: Replace with glua/lib/math.pi assert pi >. 3.14 && pi <. 3.15 let keys = ["my_table", "my_value"] use Nil <- glua.then(glua.set(keys:, value: glua.bool(True))) - use ref <- glua.then(glua.get(keys:)) - use ret <- glua.then(glua.dereference(ref:, using: decode.bool)) + use ret <- glua.then(glua.get(keys:) |> glua.returning(decode.bool)) assert ret == True glua.success(Nil) @@ -267,8 +266,9 @@ pub fn get_test() { return 'ignored' " use _ <- glua.then(glua.eval(code)) - use ref <- glua.then(glua.get(keys: ["my_value"])) - use ret <- glua.then(glua.dereference(ref:, using: decode.int)) + use ret <- glua.then( + glua.get(keys: ["my_value"]) |> glua.returning(decode.int), + ) assert ret == 10 glua.success(Nil) @@ -297,8 +297,9 @@ pub fn set_test() { let encoded = glua.string("custom version") use Nil <- glua.then(glua.set(keys: ["_VERSION"], value: encoded)) - use ref <- glua.then(glua.get(keys: ["_VERSION"])) - use result <- glua.then(glua.dereference(ref:, using: decode.string)) + use result <- glua.then( + glua.get(keys: ["_VERSION"]) |> glua.returning(decode.string), + ) assert result == "custom version" @@ -313,11 +314,10 @@ pub fn set_test() { )) use Nil <- glua.then(glua.set(keys, encoded)) - use ref <- glua.then(glua.get(keys)) - use check <- glua.then(glua.dereference( - ref:, - using: decode.dict(decode.int, decode.int), - )) + use check <- glua.then( + glua.get(keys) + |> glua.returning(decode.dict(decode.int, decode.int)), + ) assert check == dict.from_list([#(1, 4), #(2, 16), #(3, 49), #(4, 144)]) let count_odd = fn(args: List(glua.Value)) { @@ -345,12 +345,11 @@ pub fn set_test() { ), ) - use refs <- glua.then( - glua.call_function_by_name(keys: ["count_odd"], args: [arg]), + use result <- glua.then( + glua.call_function_by_name(keys: ["count_odd"], args: [arg]) + |> glua.try(list.first) + |> glua.returning(decode.int), ) - use ref <- glua.try(list.first(refs)) - - use result <- glua.then(glua.dereference(ref:, using: decode.int)) assert result == 5 @@ -379,19 +378,21 @@ pub fn set_test() { use Nil <- glua.then(glua.set(keys: ["my_functions"], value: tbl)) - use refs <- glua.then( + use result <- glua.then( glua.call_function_by_name(keys: ["my_functions", "is_even"], args: [ glua.int(4), - ]), + ]) + |> glua.try(list.first) + |> glua.returning(decode.bool), ) - use ref <- glua.try(list.first(refs)) - use result <- glua.then(glua.dereference(ref:, using: decode.bool)) assert result == True - use refs <- glua.then(glua.eval("return my_functions.is_odd(4)")) - use ref <- glua.try(list.first(refs)) - use result <- glua.then(glua.dereference(ref:, using: decode.bool)) + use result <- glua.then( + glua.eval("return my_functions.is_odd(4)") + |> glua.try(list.first) + |> glua.returning(decode.bool), + ) assert result == False glua.success(Nil) @@ -405,9 +406,9 @@ pub fn set_lua_paths_test() { let code = "local s = require 'example'; return s" - use refs <- glua.then(glua.eval(code)) - use ref <- glua.try(list.first(refs)) - use result <- glua.then(glua.dereference(ref:, using: decode.string)) + use result <- glua.then( + glua.eval(code) |> glua.try(list.first) |> glua.returning(decode.string), + ) assert result == "LUA IS AN EMBEDDABLE LANGUAGE" glua.success(Nil) @@ -440,9 +441,11 @@ pub fn delete_private_test() { pub fn load_test() { let action = { use chunk <- glua.then(glua.load(code: "return 5 * 5")) - use refs <- glua.then(glua.eval_chunk(chunk)) - use ref <- glua.try(list.first(refs)) - use result <- glua.then(glua.dereference(ref:, using: decode.int)) + use result <- glua.then( + glua.eval_chunk(chunk) + |> glua.try(list.first) + |> glua.returning(decode.int), + ) assert result == 25 glua.success(Nil) @@ -453,9 +456,11 @@ pub fn load_test() { pub fn eval_load_file_test() { let action = { use chunk <- glua.then(glua.load_file("./test/lua/example.lua")) - use refs <- glua.then(glua.eval_chunk(chunk)) - use ref <- glua.try(list.first(refs)) - use result <- glua.then(glua.dereference(ref:, using: decode.string)) + use result <- glua.then( + glua.eval_chunk(chunk) + |> glua.try(list.first) + |> glua.returning(decode.string), + ) assert result == "LUA IS AN EMBEDDABLE LANGUAGE" use _ <- glua.then(glua.load_file("non_existent_file")) @@ -468,15 +473,16 @@ pub fn eval_load_file_test() { pub fn eval_test() { let actions = { - use refs <- glua.then(glua.eval("return 'hello, ' .. 'world!'")) - use ref <- glua.try(list.first(refs)) - use result <- glua.then(glua.dereference(ref:, using: decode.string)) + use result <- glua.then( + glua.eval("return 'hello, ' .. 'world!'") + |> glua.try(list.first) + |> glua.returning(decode.string), + ) assert result == "hello, world!" - use refs <- glua.then(glua.eval("return 2 + 2, 3 - 1")) use return <- glua.then( - glua.fold(refs, glua.dereference(ref: _, using: decode.int)), + glua.eval("return 2 + 2, 3 - 1") |> glua.returning_multi(decode.int), ) assert return == [4, 2] glua.success(Nil) @@ -500,9 +506,11 @@ pub fn eval_returns_proper_errors_test() { ]) let action = { - use refs <- glua.then(glua.eval("return 'Hello from Lua!'")) - use ref <- glua.try(list.first(refs)) - use _ <- glua.then(glua.dereference(ref:, using: decode.int)) + use _ <- glua.then( + glua.eval("return 'Hello from Lua!'") + |> glua.try(list.first) + |> glua.returning(decode.int), + ) panic as "unreachable" } assert glua.run(lua, action) @@ -570,37 +578,46 @@ pub fn eval_returns_proper_errors_test() { pub fn eval_file_test() { let action = { - use refs <- glua.then(glua.eval_file("./test/lua/example.lua")) - use ref <- glua.try(list.first(refs)) - use result <- glua.then(glua.dereference(ref:, using: decode.string)) + use result <- glua.then( + glua.eval_file("./test/lua/example.lua") + |> glua.try(list.first) + |> glua.returning(decode.string), + ) assert result == "LUA IS AN EMBEDDABLE LANGUAGE" glua.success(Nil) } - glua.run(glua.new(), action) + let assert Ok(_) = glua.run(glua.new(), action) } pub fn call_function_test() { let action = { - use return <- glua.then(glua.eval("return string.reverse")) - use fun <- glua.try(list.first(return)) + use fun <- glua.then( + glua.eval("return string.reverse") |> glua.try(list.first), + ) let encoded = glua.string("auL") - use refs <- glua.then(glua.call_function(fun, [encoded])) - use ref <- glua.try(list.first(refs)) - use result <- glua.then(glua.dereference(ref:, using: decode.string)) + use result <- glua.then( + glua.call_function(fun, [encoded]) + |> glua.try(list.first) + |> glua.returning(decode.string), + ) assert result == "Lua" - use return <- glua.then(glua.eval("return function(a, b) return a .. b end")) - use fun <- glua.try(list.first(return)) + use fun <- glua.then( + glua.eval("return function(a, b) return a .. b end") + |> glua.try(list.first), + ) let args = list.map(["Lua in ", "Gleam"], glua.string) - use refs <- glua.then(glua.call_function(fun, args)) - use ref <- glua.try(list.first(refs)) - use result <- glua.then(glua.dereference(ref:, using: decode.string)) + use result <- glua.then( + glua.call_function(fun, args) + |> glua.try(list.first) + |> glua.returning(decode.string), + ) assert result == "Lua in Gleam" glua.success(Nil) @@ -610,13 +627,15 @@ pub fn call_function_test() { pub fn call_function_returns_proper_errors_test() { let action = { - use refs <- glua.then(glua.eval("return string.upper")) - use ref <- glua.try(list.first(refs)) - use refs <- glua.then( - glua.call_function(ref, [glua.string("Hello from Gleam!")]), + use fun <- glua.then( + glua.eval("return string.upper") + |> glua.try(list.first), + ) + use _ <- glua.then( + glua.call_function(fun, [glua.string("Hello from Gleam!")]) + |> glua.try(list.first) + |> glua.returning(decode.int), ) - use ref <- glua.try(list.first(refs)) - use _ <- glua.then(glua.dereference(ref:, using: decode.int)) panic as "unreachable" } @@ -626,8 +645,10 @@ pub fn call_function_returns_proper_errors_test() { ) let action = { - use refs <- glua.then(glua.eval("return 1")) - use ref <- glua.try(list.first(refs)) + use ref <- glua.then( + glua.eval("return 1") + |> glua.try(list.first), + ) use _ <- glua.then(glua.call_function(ref, [])) panic as "unreachable" } @@ -676,33 +697,28 @@ pub fn call_function_returns_proper_errors_test() { pub fn call_function_by_name_test() { let action = { let args = list.map([20, 10], glua.int) - use refs <- glua.then(glua.call_function_by_name( - keys: ["math", "max"], - args:, - )) - use ref <- glua.try(list.first(refs)) - use result <- glua.then(glua.dereference(ref:, using: decode.int)) + use result <- glua.then( + glua.call_function_by_name(keys: ["math", "max"], args:) + |> glua.try(list.first) + |> glua.returning(decode.int), + ) assert result == 20 - use refs <- glua.then(glua.call_function_by_name( - keys: ["math", "min"], - args:, - )) - use ref <- glua.try(list.first(refs)) - use result <- glua.then(glua.dereference(ref:, using: decode.int)) + use result <- glua.then( + glua.call_function_by_name(keys: ["math", "min"], args:) + |> glua.try(list.first) + |> glua.returning(decode.int), + ) assert result == 10 let arg = glua.float(10.2) - use refs <- glua.then( - glua.call_function_by_name(keys: ["math", "type"], args: [arg]), + use result <- glua.then( + glua.call_function_by_name(keys: ["math", "type"], args: [arg]) + |> glua.try(list.first) + |> glua.returning(decode.optional(decode.string)), ) - use ref <- glua.try(list.first(refs)) - use result <- glua.then(glua.dereference( - ref:, - using: decode.optional(decode.string), - )) assert result == option.Some("float") @@ -715,14 +731,14 @@ pub fn nested_function_references_test() { let action = { let code = "return function() return math.sqrt end" - use refs <- glua.then(glua.eval(code)) - use ref <- glua.try(list.first(refs)) - use refs <- glua.then(glua.call_function(ref, [])) - use ref <- glua.try(list.first(refs)) + use fun <- glua.then(glua.eval(code) |> glua.try(list.first)) + use fun <- glua.then(glua.call_function(fun, []) |> glua.try(list.first)) - use refs <- glua.then(glua.call_function(ref, [glua.int(400)])) - use ref <- glua.try(list.first(refs)) - use result <- glua.then(glua.dereference(ref:, using: decode.float)) + use result <- glua.then( + glua.call_function(fun, [glua.int(400)]) + |> glua.try(list.first) + |> glua.returning(decode.float), + ) assert result == 20.0 glua.success(Nil) } @@ -753,8 +769,7 @@ pub fn format_error_test() { == "Lua source file \"non_existent_file\" not found" let action = { - use refs <- glua.then(glua.eval("return 1 + 1")) - use ref <- glua.try(list.first(refs)) + use ref <- glua.then(glua.eval("return 1 + 1") |> glua.try(list.first)) use _ <- glua.then(glua.dereference(ref:, using: decode.string)) panic as "unreachable" } @@ -766,17 +781,18 @@ pub fn format_error_test() { } pub fn decode_function_test() { - let lua_sqrt = { - use ref <- glua.then(glua.get(keys: ["math", "sqrt"])) - glua.dereference(ref:, using: glua.function_decoder()) - } + let lua_sqrt = + glua.get(keys: ["math", "sqrt"]) + |> glua.returning(glua.function_decoder()) let assert Ok(_) = glua.run(glua.new(), { use fun <- glua.then(lua_sqrt) - use ret <- glua.then(fun([glua.int(9)])) - use ref <- glua.try(list.first(ret)) - use result <- glua.map(glua.dereference(ref:, using: decode.float)) + use result <- glua.map( + fun([glua.int(9)]) + |> glua.try(list.first) + |> glua.returning(decode.float), + ) assert result == 3.0 Nil @@ -802,11 +818,10 @@ pub fn decode_function_test() { return fold " - let lua_fold = { - use ret <- glua.then(glua.eval(code:)) - use ref <- glua.try(list.first(ret)) - glua.dereference(ref:, using: glua.function_decoder()) - } + let lua_fold = + glua.eval(code:) + |> glua.try(list.first) + |> glua.returning(glua.function_decoder()) let assert Ok(_) = glua.run(glua.new(), { @@ -825,9 +840,11 @@ pub fn decode_function_test() { |> glua.function use fun <- glua.then(lua_fold) - use ret <- glua.then(fun([tbl, glua.int(1), callback])) - use ref <- glua.try(list.first(ret)) - use result <- glua.map(glua.dereference(ref:, using: decode.int)) + use result <- glua.map( + fun([tbl, glua.int(1), callback]) + |> glua.try(list.first) + |> glua.returning(decode.int), + ) assert result == 729 Nil }) @@ -843,10 +860,9 @@ pub fn decode_function_test() { }) assert exn == glua.UndefinedFunction("nil") - let lua_table_unpack = { - use ref <- glua.then(glua.get(keys: ["table", "unpack"])) - glua.dereference(ref:, using: glua.function_decoder()) - } + let lua_table_unpack = + glua.get(keys: ["table", "unpack"]) + |> glua.returning(glua.function_decoder()) let assert Ok(_) = glua.run(glua.new(), { @@ -857,17 +873,17 @@ pub fn decode_function_test() { |> glua.table, ) - use ret <- glua.then(fun([tbl, glua.int(4), glua.int(8)])) use result <- glua.then( - glua.fold(ret, glua.dereference(_, using: decode.string)), + fun([tbl, glua.int(4), glua.int(8)]) + |> glua.returning_multi(decode.string), ) assert result == ["d", "e", "f", "g", "h"] - use ret <- glua.then(fun([tbl, glua.int(8)])) use result <- glua.then( - glua.fold(ret, glua.dereference(_, using: decode.string)), + fun([tbl, glua.int(8)]) |> glua.returning_multi(decode.string), ) + assert result == ["h", "i", "j"] glua.success(Nil) }) @@ -882,8 +898,7 @@ pub fn decode_function_test() { let assert Error(glua.LuaRuntimeException(exn, _)) = glua.run(glua.new(), { - use ret <- glua.then(glua.eval(code:)) - use ref <- glua.try(list.first(ret)) + use ref <- glua.then(glua.eval(code:) |> glua.try(list.first)) use fun <- glua.then(glua.dereference( ref:, using: glua.function_decoder(), @@ -896,12 +911,11 @@ pub fn decode_function_test() { let code = "return function() return 3 * true end" let assert Error(glua.LuaRuntimeException(exn, _)) = glua.run(glua.new(), { - use ret <- glua.then(glua.eval(code:)) - use ref <- glua.try(list.first(ret)) - use fun <- glua.then(glua.dereference( - ref:, - using: glua.function_decoder(), - )) + use fun <- glua.then( + glua.eval(code:) + |> glua.try(list.first) + |> glua.returning(glua.function_decoder()), + ) fun([]) }) @@ -910,7 +924,7 @@ pub fn decode_function_test() { pub fn index_test() { let assert Ok(#(state, tbl)) = - glua.run(glua.new(), { + glua.exec(glua.new(), { glua.table([#(glua.string("a_key"), glua.string("a value"))]) }) @@ -918,7 +932,6 @@ pub fn index_test() { state, glua.index(tbl, "a_key") |> glua.returning(decode.string), ) - |> result.map(pair.second) == Ok("a value") assert glua.run(state, { @@ -930,7 +943,7 @@ pub fn index_test() { == Error(glua.KeyNotFound(["other_key"])) let assert Ok(#(state, tbl)) = - glua.run(glua.new(), { + glua.exec(glua.new(), { use tbl <- glua.then(glua.table([])) use mt <- glua.then( glua.table([ @@ -959,33 +972,28 @@ pub fn index_test() { }) assert glua.run(state, glua.index(tbl, "1") |> glua.returning(decode.string)) - |> result.map(pair.second) == Ok("integer") assert glua.run(state, glua.index(tbl, "a") |> glua.returning(decode.string)) - |> result.map(pair.second) == Ok("other") assert glua.run(state, glua.index(tbl, "2") |> glua.returning(decode.int)) - |> result.map(pair.second) == Error( glua.UnexpectedResultType([decode.DecodeError("Int", "String", [])]), ) } pub fn new_index_test() { - let assert Ok(#(state, tbl)) = glua.run(glua.new(), { glua.table([]) }) + let assert Ok(#(state, tbl)) = glua.exec(glua.new(), { glua.table([]) }) assert glua.run(state, { use _ <- glua.then(glua.new_index(tbl, "a_key", glua.int(1))) - use ref <- glua.then(glua.index(tbl, "a_key")) - glua.dereference(ref, decode.int) + glua.index(tbl, "a_key") |> glua.returning(decode.int) }) - |> result.map(pair.second) == Ok(1) let assert Ok(#(state, tbl)) = - glua.run(glua.new(), { + glua.exec(glua.new(), { use tbl <- glua.then(glua.table([])) use mt <- glua.then( glua.table([ @@ -1011,22 +1019,18 @@ pub fn new_index_test() { assert glua.run(state, { use _ <- glua.then(glua.new_index(tbl, "a_key", glua.int(6))) - use ref <- glua.then(glua.index(tbl, "a_key")) - glua.dereference(ref, decode.int) + glua.index(tbl, "a_key") |> glua.returning(decode.int) }) - |> result.map(pair.second) == Ok(36) assert glua.run(state, { use _ <- glua.then(glua.new_index(tbl, "other_key", glua.int(7))) - use ref <- glua.then(glua.index(tbl, "other_key")) - glua.dereference(ref, decode.int) + glua.index(tbl, "other_key") |> glua.returning(decode.int) }) - |> result.map(pair.second) == Ok(49) let assert Ok(#(state, tbl)) = - glua.run(glua.new(), { + glua.exec(glua.new(), { use tbl <- glua.then(glua.table([])) use mt <- glua.then( glua.table([ diff --git a/test/lib_test.gleam b/test/lib_test.gleam index 861b03d..9755132 100644 --- a/test/lib_test.gleam +++ b/test/lib_test.gleam @@ -12,7 +12,7 @@ import glua/lib/utf8 /// Checks to see if a value matches a value at the given path. fn check(val: glua.Value, path: List(String)) { - let assert Ok(#(_, found)) = glua.run(glua.new(), glua.get(path)) + let assert Ok(found) = glua.run(glua.new(), glua.get(path)) assert val == found }