Repository: tv-labs/lua
Pull request #10 (2024-03-14 01:34:02)

Better error messages for undefined functions when loading scripts


Patch:
diff --git a/lib/lua.ex b/lib/lua.ex
index ea10575..575ba20 100644
--- a/lib/lua.ex
+++ b/lib/lua.ex
@@ -208,6 +208,9 @@ defmodule Lua do
       {:ok, [], state} ->
         %__MODULE__{lua | state: state}
 
+      {:lua_error, _error, _lua} = error ->
+        raise Lua.CompilerException, error
+
       :error ->
         raise "Cannot load lua file, #{inspect(path <> ".lua")} does not exist"
     end
diff --git a/lib/lua/compiler_exception.ex b/lib/lua/compiler_exception.ex
index 209ab0b..75fb8be 100644
--- a/lib/lua/compiler_exception.ex
+++ b/lib/lua/compiler_exception.ex
@@ -1,7 +1,21 @@
 defmodule Lua.CompilerException do
   defexception [:message]
 
-  def exception(data) do
+  alias Lua.Util
+
+  def exception({:lua_error, error, state}) do
+    stacktrace = Luerl.New.get_stacktrace(state)
+
+    message = """
+    Failed to compile Lua script: #{Util.format_error(error)}
+
+    #{Util.format_stacktrace(stacktrace, state)}
+    """
+
+    %__MODULE__{message: message}
+  end
+
+  def exception(data) when is_list(data) do
     message =
       data
       |> Keyword.fetch!(:reason)
diff --git a/lib/lua/runtime_exception.ex b/lib/lua/runtime_exception.ex
index ad477b7..015719b 100644
--- a/lib/lua/runtime_exception.ex
+++ b/lib/lua/runtime_exception.ex
@@ -5,35 +5,7 @@ defmodule Lua.RuntimeException do
 
   @impl true
   def exception({:lua_error, error, state}) do
-    message =
-      case error do
-        {:error_call, message} ->
-          Util.format_function("error", message)
-
-        {:undefined_function, nil} ->
-          "undefined function"
-
-        {:undefined_function, "sandboxed"} ->
-          "sandboxed function"
-
-        {:undefined_function, ref} ->
-          "undefined function #{inspect(ref)}"
-
-        {:undefined_method, nil, name} ->
-          "undefined method #{inspect(name)}"
-
-        {:illegal_index, _, name} ->
-          "invalid index #{inspect(name)}"
-
-        {:badarith, operator, values} ->
-          expression = values |> Enum.map(&to_string/1) |> Enum.join(" #{operator} ")
-
-          "bad arithmetic #{expression}"
-
-        error ->
-          "unknown error #{inspect(error)}"
-      end
-
+    message = Util.format_error(error)
     stacktrace = Luerl.New.get_stacktrace(state)
 
     %__MODULE__{
diff --git a/lib/lua/util.ex b/lib/lua/util.ex
index f093aa8..15132f4 100644
--- a/lib/lua/util.ex
+++ b/lib/lua/util.ex
@@ -35,6 +35,36 @@ defmodule Lua.Util do
     |> Enum.sort(:asc)
   end
 
+  def format_error(error) do
+    case error do
+      {:error_call, message} ->
+        format_function("error", message)
+
+      {:undefined_function, nil} ->
+        "undefined function"
+
+      {:undefined_function, "sandboxed"} ->
+        "sandboxed function"
+
+      {:undefined_function, ref} ->
+        "undefined function #{inspect(ref)}"
+
+      {:undefined_method, nil, name} ->
+        "undefined method #{inspect(name)}"
+
+      {:illegal_index, _, name} ->
+        "invalid index #{inspect(name)}"
+
+      {:badarith, operator, values} ->
+        expression = values |> Enum.map(&to_string/1) |> Enum.join(" #{operator} ")
+
+        "bad arithmetic #{expression}"
+
+      error ->
+        "unknown error #{inspect(error)}"
+    end
+  end
+
   @doc """
   Pretty prints a stack trace
   """


Test patch:
diff --git a/test/fixtures/syntax_error.lua b/test/fixtures/syntax_error.lua
new file mode 100644
index 0000000..da88366
--- /dev/null
+++ b/test/fixtures/syntax_error.lua
@@ -0,0 +1,1 @@
+shisjg;alskdgjl''z'
diff --git a/test/fixtures/undefined_function.lua b/test/fixtures/undefined_function.lua
new file mode 100644
index 0000000..871c67c
--- /dev/null
+++ b/test/fixtures/undefined_function.lua
@@ -0,0 +1,1 @@
+bogus()
diff --git a/test/lua_test.exs b/test/lua_test.exs
index b854632..a179fdc 100644
--- a/test/lua_test.exs
+++ b/test/lua_test.exs
@@ -1,5 +1,5 @@
 defmodule LuaTest do
-  use ExUnit.Case
+  use ExUnit.Case, async: true
 
   alias Lua
 
@@ -24,7 +24,7 @@ defmodule LuaTest do
 
   describe "load_lua_file/2" do
     test "loads the lua file into the state" do
-      path = Path.join(["test", "fixtures", "test_api"])
+      path = test_file("test_api")
 
       assert lua = Lua.load_lua_file!(Lua.new(), path)
 
@@ -34,6 +34,29 @@ defmodule LuaTest do
                """)
     end
 
+    @tag :skip
+    test "loading files with syntax errors returns an error" do
+      path = test_file("syntax_error")
+
+      assert_raise Lua.CompilerException, fn ->
+        Lua.load_lua_file!(Lua.new(), path)
+      end
+    end
+
+    test "loading files with undefined functions returns an error" do
+      path = test_file("undefined_function")
+
+      error = """
+      Failed to compile Lua script: undefined function
+
+      script line 1: <unknown function>()
+      """
+
+      assert_raise Lua.CompilerException, error, fn ->
+        Lua.load_lua_file!(Lua.new(), path)
+      end
+    end
+
     test "non-existent files are not loaded" do
       assert_raise RuntimeError, "Cannot load lua file, \"bananas.lua\" does not exist", fn ->
         Lua.load_lua_file!(Lua.new(), "bananas")
@@ -468,4 +491,8 @@ defmodule LuaTest do
       end
     end
   end
+
+  defp test_file(name) do
+    Path.join(["test", "fixtures", name])
+  end
 end