Current section
2 Versions
Jump to
Current section
2 Versions
Compare versions
15
files changed
+469
additions
-103
deletions
| @@ -1,6 +1,6 @@ | |
| 1 1 | {<<"links">>,[{<<"GitHub">>,<<"https://github.com/iggi42/luex">>}]}. |
| 2 2 | {<<"name">>,<<"luex">>}. |
| 3 | - {<<"version">>,<<"0.0.1">>}. |
| 3 | + {<<"version">>,<<"0.0.2">>}. |
| 4 4 | {<<"description">>,<<"an elixir interface to the great luerl">>}. |
| 5 5 | {<<"elixir">>,<<"~> 1.17">>}. |
| 6 6 | {<<"app">>,<<"luex">>}. |
| @@ -8,9 +8,12 @@ | |
| 8 8 | {<<"files">>, |
| 9 9 | [<<"lib">>,<<"lib/luex.ex">>,<<"lib/luex">>,<<"lib/luex/functions.ex">>, |
| 10 10 | <<"lib/luex/records.ex">>,<<"lib/luex/lua_error.ex">>, |
| 11 | - <<"lib/luex/table.ex">>,<<"lib/luex/utils.ex">>, |
| 12 | - <<"lib/luex/ext_module.ex">>,<<"lib/luex/userdata.ex">>,<<"mix.exs">>, |
| 13 | - <<"README.md">>,<<"LICENSE">>]}. |
| 11 | + <<"lib/luex/table.ex">>,<<"lib/luex/call_result.ex">>,<<"lib/luex/async">>, |
| 12 | + <<"lib/luex/async/server.ex">>,<<"lib/luex/utils.ex">>, |
| 13 | + <<"lib/luex/ext_module.ex">>,<<"lib/luex/table">>, |
| 14 | + <<"lib/luex/table/array.ex">>,<<"lib/luex/table/not_an_array.ex">>, |
| 15 | + <<"lib/luex/table/recursive_exception.ex">>,<<"lib/luex/async.ex">>, |
| 16 | + <<"lib/luex/userdata.ex">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>]}. |
| 14 17 | {<<"requirements">>, |
| 15 18 | [[{<<"name">>,<<"luerl">>}, |
| 16 19 | {<<"app">>,<<"luerl">>}, |
| @@ -9,28 +9,16 @@ defmodule Luex do | |
| 9 9 | require Luex.LuaError |
| 10 10 | require Luex.Records |
| 11 11 | |
| 12 | + alias Luex.CallResult |
| 13 | + |
| 14 | + @typedoc """ |
| 15 | + represents a lua call, that returned a value and potentially changed the vm state |
| 16 | + """ |
| 17 | + @type lua_call(result_type) :: %Luex.CallResult{vm: Luex.vm(), return: result_type} # {result_type, vm()} |
| 18 | + |
| 12 19 | @type vm() :: Luex.Records.luerl() |
| 13 20 | defguard is_vm(val) when Luex.Records.is_luerl(val) |
| 14 21 | |
| 15 | - @typedoc """ |
| 16 | - A keypath describes a list of keys, to navigate nested tables. |
| 17 | - |
| 18 | - For example `package.path` is a keypath with the elixir representation of `["package", "path"]`. |
| 19 | - """ |
| 20 | - @type keypath() :: [String.t()] |
| 21 | - |
| 22 | - @typedoc """ |
| 23 | - This type can representation any lua type. |
| 24 | - """ |
| 25 | - @type lua_value() :: |
| 26 | - lua_nil() |
| 27 | - | lua_bool() |
| 28 | - | lua_string() |
| 29 | - | lua_number() |
| 30 | - | lua_table() |
| 31 | - | lua_userdata() |
| 32 | - | lua_fun() |
| 33 | - |
| 34 22 | @typedoc """ |
| 35 23 | literal type representation of the lua `nil` value via atom `:nil` |
| 36 24 | """ |
| @@ -59,7 +47,7 @@ defmodule Luex do | |
| 59 47 | references a table in a lua virtual machine. |
| 60 48 | See `Luex.Table` for more information on how to handle this value. |
| 61 49 | """ |
| 62 | - @type lua_table() :: Luex.Table.t() |
| 50 | + @type lua_table() :: Luex.Records.tref() |
| 63 51 | defguard is_lua_table(v) when Luex.Records.is_tref(v) |
| 64 52 | |
| 65 53 | @typedoc """ |
| @@ -78,9 +66,22 @@ defmodule Luex do | |
| 78 66 | when Luex.Records.is_erl_mfa(v) or Luex.Records.is_erl_func(v) or |
| 79 67 | Luex.Records.is_lua_func(v) |
| 80 68 | |
| 69 | + # TODO write docs |
| 81 70 | @type lua_chunk() :: Luex.Records.erl_func() | Luex.Records.funref() |
| 82 71 | defguard is_lua_chunk(v) when Luex.Records.is_erl_func(v) or Luex.Records.is_funref(v) |
| 83 72 | |
| 73 | + @typedoc """ |
| 74 | + This type can representation any lua type. |
| 75 | + """ |
| 76 | + @type lua_value() :: |
| 77 | + lua_nil() |
| 78 | + | lua_bool() |
| 79 | + | lua_string() |
| 80 | + | lua_number() |
| 81 | + | lua_table() |
| 82 | + | lua_userdata() |
| 83 | + | lua_fun() |
| 84 | + |
| 84 85 | @doc """ |
| 85 86 | `is_lua_value` checks looks like any representation of a value in a lua vm |
| 86 87 | """ |
| @@ -89,6 +90,13 @@ defmodule Luex do | |
| 89 90 | is_lua_string(value) or is_lua_number(value) or is_lua_table(value) or |
| 90 91 | is_lua_userdata(value) or is_lua_fun(value) or is_lua_chunk(value) |
| 91 92 | |
| 93 | + @typedoc """ |
| 94 | + A keypath describes a list of keys, to navigate nested tables. |
| 95 | + |
| 96 | + For example `package.path` is a keypath with the elixir representation of `["package", "path"]`. |
| 97 | + """ |
| 98 | + @type keypath() :: [lua_value()] |
| 99 | + |
| 92 100 | @typedoc """ |
| 93 101 | input type for encode/2 |
| 94 102 | """ |
| @@ -102,6 +110,9 @@ defmodule Luex do | |
| 102 110 | | {:userdata, any()} |
| 103 111 | |
| 104 112 | @doc """ |
| 113 | + Import an elixir value into a luerl vm. |
| 114 | + This is function recursively loads lists, keyword lists and maps. |
| 115 | + |
| 105 116 | This is how elixir values are encoded into lua values, by luerl. |
| 106 117 | ```mermaid |
| 107 118 | graph LR; |
| @@ -127,37 +138,47 @@ defmodule Luex do | |
| 127 138 | ``` |
| 128 139 | Tuples are only allowed as keyword lists items and to indicate userdata. |
| 129 140 | """ |
| 130 | - @deprecated "luex will move away from luerl style encoding of values" |
| 141 | + # maybe don't deprecate, but rename with emphasis on recursive loading, and only for _input_ into lua, not output (bc rec tables in lua) |
| 142 | + # @deprecated "luex will move away from luerl style encoding of values" |
| 131 143 | # TODO move the mermaid diagram to somewhere useful () |
| 132 | - @spec encode(vm(), encoding_input()) :: {lua_value(), vm()} |
| 133 | - defdelegate encode(vm, encode_me), to: Luerl |
| 144 | + @spec encode(vm(), encoding_input()) :: lua_call(lua_value()) |
| 145 | + def encode(vm, encoding_input) do |
| 146 | + vm |
| 147 | + |> Luerl.encode(encoding_input) |
| 148 | + |> CallResult.from_luerl() |
| 149 | + end |
| 134 150 | |
| 135 151 | @type input_value() :: |
| 136 152 | nil |
| 137 153 | | boolean() |
| 138 154 | | number() |
| 139 155 | | String.t() |
| 140 | - | Luex.Table.input() |
| 156 | + | Luex.Table.data() |
| 141 157 | | Luex.Functions.input() |
| 142 158 | | {:userdata, any()} |
| 143 159 | |
| 144 160 | @doc """ |
| 145 161 | an attempt at doing Luerl.encode/2 better. |
| 146 162 | |
| 147 | - not sure if having such a function at all is a good idea |
| 148 163 | """ |
| 149 | - @spec load_value(vm(), input_value()) :: {lua_value(), vm()} |
| 164 | + @spec load_value(vm(), input_value()) :: lua_call(lua_value()) |
| 150 165 | # literals |
| 151 | - def load_value(vm, nil) when is_vm(vm), do: {nil, vm} |
| 152 | - def load_value(vm, true) when is_vm(vm), do: {true, vm} |
| 153 | - def load_value(vm, false) when is_vm(vm), do: {false, vm} |
| 154 | - def load_value(vm, n) when is_vm(vm) and is_number(n), do: {n, vm} |
| 155 | - def load_value(vm, s) when is_vm(vm) and is_binary(s), do: {s, vm} |
| 166 | + def load_value(vm, nil) when is_vm(vm), do: %CallResult{return: nil, vm: vm} |
| 167 | + def load_value(vm, true) when is_vm(vm), do: %CallResult{return: true, vm: vm} |
| 168 | + def load_value(vm, false) when is_vm(vm), do: %CallResult{return: false, vm: vm} |
| 169 | + def load_value(vm, n) when is_vm(vm) and is_number(n), do: %CallResult{return: n, vm: vm} |
| 170 | + def load_value(vm, s) when is_vm(vm) and is_binary(s), do: %CallResult{return: s, vm: vm} |
| 156 171 | # referenced values |
| 157 172 | def load_value(vm, m) when is_vm(vm) and is_map(m), do: Luex.Table.new(vm, m) |
| 158 173 | def load_value(vm, {:userdata, payload}) when is_vm(vm), do: Luex.Userdata.new(vm, payload) |
| 159 174 | def load_value(vm, f) when is_vm(vm) and is_function(f, 2), do: Luex.Functions.new(vm, f) |
| 160 175 | |
| 176 | + @doc """ |
| 177 | + |
| 178 | + """ |
| 179 | + @spec set_value(lua_call(lua_value()), keypath()) :: vm() |
| 180 | + def set_value(%Luex.CallResult{vm: vm, return: r }, keypath), do: set_value(vm, keypath, r) |
| 181 | + |
| 161 182 | @spec set_value(vm(), keypath(), lua_value()) :: vm() |
| 162 183 | def set_value(vm, keypath, value) when is_vm(vm) and is_list(keypath) and is_lua_value(value) do |
| 163 184 | Luex.LuaError.wrap do |
| @@ -171,13 +192,15 @@ defmodule Luex do | |
| 171 192 | |> Luerl.set_table1(keypath, value) |
| 172 193 | end |
| 173 194 | |
| 174 | - @spec get_value(vm(), keypath()) :: {lua_value(), vm()} |
| 195 | + @spec get_value(vm() | lua_call(any()), keypath()) :: lua_call(lua_value()) |
| 196 | + def get_value(%Luex.CallResult{vm: vm}, keypath), do: get_value(vm, keypath) |
| 175 197 | def get_value(vm, keypath) when is_vm(vm) and is_list(keypath) do |
| 176 198 | Luex.LuaError.wrap do |
| 177 | - get_value1(vm, keypath) |
| 199 | + get_value1(vm, keypath) |> CallResult.from_luerl() |
| 178 200 | end |
| 179 201 | end |
| 180 202 | |
| 203 | + # offer private get_value version without wrapping to prevent unnecessary multiple wrapping |
| 181 204 | defp get_value1(vm, keypath), do: Luerl.get_table1(vm, keypath) |
| 182 205 | |
| 183 206 | @doc """ |
| @@ -186,22 +209,45 @@ defmodule Luex do | |
| 186 209 | @spec init() :: vm() |
| 187 210 | defdelegate init, to: Luerl |
| 188 211 | |
| 212 | + @typedoc """ |
| 213 | + options |
| 214 | + - `:ext_searcher`: list of modules that implement Luex.ExtModule, used to extend the lua require function. |
| 215 | + """ |
| 216 | + @type vm_config() :: {:lua_ext_searcher, [module()]} |
| 217 | + |
| 218 | + @spec configure(vm(), [vm_config()]) :: vm() |
| 219 | + def configure(vm, []), do: vm |
| 220 | + |
| 221 | + def configure(vm, [{:ext_searcher, mods} | args]) do |
| 222 | + whitelist = Enum.map(mods, fn m -> {m.target(), m} end) |> Map.new() |
| 223 | + |
| 224 | + raw_searcher = fn [query], vm_s when is_lua_string(query) -> |
| 225 | + case Map.get(whitelist, query, :not_found) do |
| 226 | + :not_found -> |
| 227 | + {["no luex module registered for: \"#{query}\""], vm_s} |
| 228 | + |
| 229 | + ext_module when is_atom(ext_module) -> |
| 230 | + raw_loader = Luex.ExtModule.build_loader(ext_module) |
| 231 | + %CallResult{return: f, vm: vm_s} = Luex.Functions.new(vm_s, raw_loader) |
| 232 | + {[f], vm_s} |
| 233 | + end |
| 234 | + end |
| 235 | + |
| 236 | + %CallResult{return: searchers, vm: vm} = Luex.get_value(vm, ["package", "searchers"]) |
| 237 | + %CallResult{return: epath_searcher, vm: vm} = Luex.Functions.new(vm, raw_searcher) |
| 238 | + |
| 239 | + vm |
| 240 | + |> Luex.Table.Array.append(searchers, epath_searcher) |
| 241 | + |> configure(args) |
| 242 | + end |
| 243 | + |
| 189 244 | @doc """ |
| 190 245 | run a string as lua code in the given vm. |
| 191 | - |
| 192 | - # Example |
| 193 | - |
| 194 | - |
| 195 | - ```elixir |
| 196 | - iex> vm0 = Luex.init() |
| 197 | - iex> {[5], _vm1} = Luex.do_inline(vm0, "return 3+2") |
| 198 | - ``` |
| 199 | - |
| 200 246 | """ |
| 201 | - @spec do_inline(vm(), String.t()) :: {[lua_value()], vm()} |
| 247 | + @spec do_inline(vm(), String.t()) :: lua_call([lua_value()]) |
| 202 248 | def do_inline(vm, program) do |
| 203 249 | Luex.LuaError.wrap do |
| 204 | - Luerl.do(vm, program) |
| 250 | + Luerl.do(vm, program) |> CallResult.from_luerl() |
| 205 251 | end |
| 206 252 | end |
| 207 253 | |
| @@ -212,21 +258,22 @@ defmodule Luex do | |
| 212 258 | |
| 213 259 | ```elixir |
| 214 260 | iex> vm0 = Luex.init() |
| 215 | - iex> {[5], _vm1} = Luex.do_file(vm0, "./test/return_5.lua") |
| 261 | + iex> %Luex.CallResult{return: [5]} = Luex.do_file(vm0, "./test/return_5.lua") |
| 216 262 | ``` |
| 217 263 | |
| 218 264 | """ |
| 219 | - @spec do_file(vm(), Path.t()) :: {[lua_value()], vm()} |
| 265 | + @spec do_file(vm(), Path.t()) :: lua_call([lua_value()]) |
| 220 266 | def do_file(vm, path) do |
| 221 267 | Luex.LuaError.wrap do |
| 222 | - Luerl.dofile(vm, to_charlist(path)) |
| 268 | + Luerl.dofile(vm, to_charlist(path)) |> CallResult.from_luerl() |
| 223 269 | end |
| 224 270 | end |
| 225 271 | |
| 226 | - @spec do_chunk(vm(), lua_chunk(), [lua_value()]) :: {[lua_value()], vm()} |
| 272 | + # TODO write docs |
| 273 | + @spec do_chunk(vm(), lua_chunk(), [lua_value()]) :: lua_call([lua_value()]) |
| 227 274 | def do_chunk(vm, chunk, args \\ []) do |
| 228 275 | Luex.LuaError.wrap do |
| 229 | - Luerl.call(vm, chunk, args) |
| 276 | + Luerl.call(vm, chunk, args) |> CallResult.from_luerl() |
| 230 277 | end |
| 231 278 | end |
| @@ -0,0 +1,18 @@ | |
| 1 | + defmodule Luex.Async do |
| 2 | + @typedoc """ |
| 3 | + # config options |
| 4 | + - `:boot_args` : map function of lua vm to configure said vm on server start |
| 5 | + """ |
| 6 | + @type boot_args() :: [config: (Luex.vm() -> Luex.vm())] |
| 7 | + |
| 8 | + @type t() :: pid() |
| 9 | + |
| 10 | + @spec start_link(boot_args(), GenServer.options()) :: GenServer.on_start() |
| 11 | + def start_link(args, options \\ []) do |
| 12 | + GenServer.start_link(__MODULE__.Server, args, options) |
| 13 | + end |
| 14 | + |
| 15 | + @spec do_inline(t(), String.t(), timeout()) :: {:ok, [Luex.lua_value()]} |
| 16 | + def do_inline(vm, lua, timeout \\ 5000), do: GenServer.call(vm, {:do_inline, lua}, timeout) |
| 17 | + |
| 18 | + end |
| @@ -0,0 +1,28 @@ | |
| 1 | + defmodule Luex.Async.Server do |
| 2 | + @moduledoc false |
| 3 | + use GenServer |
| 4 | + |
| 5 | + defmodule __MODULE__.State do |
| 6 | + defstruct [:vm] |
| 7 | + end |
| 8 | + |
| 9 | + alias Luex.CallResult |
| 10 | + alias __MODULE__.State, as: S |
| 11 | + |
| 12 | + # configure |
| 13 | + def init(args) do |
| 14 | + do_conf = args[:setup] || (& &1) |
| 15 | + |
| 16 | + Luex.init() |
| 17 | + |> then(do_conf) |
| 18 | + |> then(&{:ok, %S{vm: &1}}) |
| 19 | + end |
| 20 | + |
| 21 | + def handle_call({:do_inline, inline_lua}, _from, state) do |
| 22 | + %CallResult{return: results, vm: vm} = Luex.do_inline(state.vm, inline_lua) |
| 23 | + response = {:ok, results} |
| 24 | + state = %S{ state | vm: vm } |
| 25 | + {:reply, response, state} |
| 26 | + end |
| 27 | + |
| 28 | + end |
| @@ -0,0 +1,16 @@ | |
| 1 | + defmodule Luex.CallResult do |
| 2 | + @moduledoc false |
| 3 | + # IDEA STAGE: replace the lua(call) type |
| 4 | + # implement enumerable against this |
| 5 | + defstruct [:vm, :return] |
| 6 | + |
| 7 | + |
| 8 | + def from_luerl({result, vm}), do: %__MODULE__{vm: vm, return: result} |
| 9 | + |
| 10 | + |
| 11 | + def then_vm(%__MODULE__{vm: vm}, fun), do: fun.(vm) |
| 12 | + |
| 13 | + # def then_result(%__MODULE__{result: r}, fun), do: fun.(r) |
| 14 | + # is a bad idea, because users might throw away a vm state and use an old instead |
| 15 | + |
| 16 | + end |
Loading more files…