diff --git a/lib/lua/vm/stdlib.ex b/lib/lua/vm/stdlib.ex index 79c7b64..6c5e423 100644 --- a/lib/lua/vm/stdlib.ex +++ b/lib/lua/vm/stdlib.ex @@ -38,6 +38,7 @@ defmodule Lua.VM.Stdlib do |> State.register_function("setmetatable", &lua_setmetatable/2) |> State.register_function("getmetatable", &lua_getmetatable/2) |> State.register_function("select", &lua_select/2) + |> State.register_function("load", &lua_load/2) |> State.register_function("require", &lua_require/2) |> install_package_table() |> Lua.VM.Stdlib.String.install() @@ -332,6 +333,45 @@ defmodule Lua.VM.Stdlib do raise ArgumentError.value_expected("select", 1) end + # load(chunk, chunkname, mode, env) — loads a Lua chunk + # chunk can be a string + # Returns compiled function or (nil, error message) + defp lua_load([chunk | _rest], state) when is_binary(chunk) do + case Lua.Parser.parse(chunk) do + {:ok, ast} -> + case Lua.Compiler.compile(ast) do + {:ok, prototype} -> + # Create a closure from the compiled prototype + closure = {:lua_closure, prototype, %{}} + {[closure], state} + + {:error, reason} -> + error_msg = format_compile_error(reason) + {[nil, error_msg], state} + end + + {:error, reason} -> + error_msg = format_parse_error(reason) + {[nil, error_msg], state} + end + end + + defp lua_load([non_string | _], state) when not is_binary(non_string) do + # For now, only support string chunks + # TODO: Support function chunks and reader functions + {[nil, "load only supports string chunks currently"], state} + end + + defp lua_load([], _state) do + raise ArgumentError.value_expected("load", 1) + end + + defp format_parse_error(error) when is_binary(error), do: error + defp format_parse_error(error), do: inspect(error) + + defp format_compile_error(error) when is_binary(error), do: error + defp format_compile_error(error), do: inspect(error) + # setmetatable(table, metatable) — sets the metatable for a table defp lua_setmetatable([{:tref, _} = tref, metatable], state) do # Validate metatable is nil or a table diff --git a/test/lua_test.exs b/test/lua_test.exs index e15569e..3c05606 100644 --- a/test/lua_test.exs +++ b/test/lua_test.exs @@ -1557,6 +1557,51 @@ defmodule LuaTest do end end + describe "load function" do + setup do + %{lua: Lua.new(sandboxed: [])} + end + + test "load compiles and returns a function", %{lua: lua} do + code = """ + f = load("return 1 + 2") + return f() + """ + + assert {[3], _} = Lua.eval!(lua, code) + end + + test "load with syntax error returns nil", %{lua: lua} do + # Note: Multi-assignment and table constructors don't capture multiple return values yet + # So we just test that load returns nil on error + code = """ + f = load("return 1 +") + return f == nil + """ + + assert {[true], _} = Lua.eval!(lua, code) + end + + test "loaded function can access upvalues", %{lua: lua} do + code = """ + x = 10 + f = load("return x + 5") + return f() + """ + + assert {[15], _} = Lua.eval!(lua, code) + end + + test "load can compile complex code", %{lua: lua} do + code = """ + f = load("function add(a, b) return a + b end; return add(3, 4)") + return f() + """ + + assert {[7], _} = Lua.eval!(lua, code) + end + end + defp test_file(name) do Path.join(["test", "fixtures", name]) end