Skip to content

Commit

Permalink
Add Lua.parse_chunk/1 for runtime parsing of chunks (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
davydog187 authored Sep 4, 2024
1 parent ec21718 commit d5b9e05
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 48 deletions.
2 changes: 1 addition & 1 deletion guides/working-with-lua.livemd
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

```elixir
Mix.install([
{:lua, "~> 0.0.16"}
{:lua, "~> 0.0.17"}
])
```

Expand Down
54 changes: 47 additions & 7 deletions lib/lua.ex
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ defmodule Lua do
`eval!/2` can also evaluate chunks by passing instead of a script. As a
peformance optimization, it is recommended to call `load_chunk/2` if you
performance optimization, it is recommended to call `load_chunk!/2` if you
will be executing a chunk many times, but it is not necessary.
iex> {[4], _} = Lua.eval!(~LUA[return 2 + 2]c)
Expand Down Expand Up @@ -273,7 +273,7 @@ defmodule Lua do
end

def eval!(%__MODULE__{} = lua, %Lua.Chunk{} = chunk) do
{chunk, lua} = load_chunk(lua, chunk)
{chunk, lua} = load_chunk!(lua, chunk)

case :luerl_new.call_chunk(chunk.ref, lua.state) do
{:ok, result, new_state} ->
Expand All @@ -299,18 +299,58 @@ defmodule Lua do
end

@doc """
Similar to `:luerl_new.load/3`, it takes a `t:Lua.Chunk.t`, and loads
it into state so that it can be evaluated via `eval!/2`
Parses a chunk of Lua code into a `t:Lua.Chunk.t/0`, which then can
be loaded via `load_chunk!/2` or run via `eval!`.
This function is particularly useful for checking Lua code for syntax
erorrs and warnings at runtime. If you would like to just load a chunk,
use `load_chunk!/1` instead.
iex> {:ok, %Lua.Chunk{}} = Lua.parse_chunk("local foo = 1")
Errors found during parsing will be returned as a list of formatted strings
iex> Lua.parse_chunk("local foo =;")
{:error, ["Line 1: syntax error before: ';'"]}
iex> {%Lua.Chunk{}, %Lua{}} = Lua.load_chunk(Lua.new(), ~LUA[return 2 + 2]c)
"""
def load_chunk(%__MODULE__{state: state} = lua, %Lua.Chunk{ref: nil} = chunk) do
def parse_chunk(code) do
case :luerl_comp.string(code, [:return]) do
{:ok, chunk} ->
{:ok, %Lua.Chunk{instructions: chunk}}

{:error, errors, _warnings} ->
{:error, Enum.map(errors, &Util.format_error/1)}
end
end

@doc """
Loads string or `t:Lua.Chunk.t/0` into state so that it can be
evaluated via `eval!/2`
Strings can be loaded as chunks, which are parsed and loaded
iex> {%Lua.Chunk{}, %Lua{}} = Lua.load_chunk!(Lua.new(), "return 2 + 2")
Or a pre-compiled chunk can be loaded as well. Loaded chunks will be marked as loaded,
otherwise they will be re-loaded everytime `eval!/2` is called
iex> {%Lua.Chunk{}, %Lua{}} = Lua.load_chunk!(Lua.new(), ~LUA[return 2 + 2]c)
"""
def load_chunk!(%__MODULE__{} = lua, code) when is_binary(code) do
case parse_chunk(code) do
{:ok, chunk} -> load_chunk!(lua, chunk)
{:error, errors} -> raise Lua.CompilerException, formatted: errors
end
end

def load_chunk!(%__MODULE__{state: state} = lua, %Lua.Chunk{ref: nil} = chunk) do
{ref, state} = :luerl_emul.load_chunk(chunk.instructions, state)

{%Lua.Chunk{chunk | ref: ref}, %__MODULE__{lua | state: state}}
end

def load_chunk(%__MODULE__{} = lua, %Lua.Chunk{} = chunk) do
def load_chunk!(%__MODULE__{} = lua, %Lua.Chunk{} = chunk) do
{chunk, lua}
end

Expand Down
47 changes: 21 additions & 26 deletions lib/lua/compiler_exception.ex
Original file line number Diff line number Diff line change
@@ -1,46 +1,41 @@
defmodule Lua.CompilerException do
defexception [:message]
defexception [:errors, :state]

alias Lua.Util

def exception(errors) when is_list(errors) do
for error <- errors do
Util.format_error(error)
end
def exception(formatted: errors) when is_list(errors) do
%__MODULE__{errors: errors}
end

errors = Enum.map_join(errors, "\n", &Util.format_error/1)
def exception(errors) when is_list(errors) do
%__MODULE__{errors: Enum.map(errors, &Util.format_error/1)}
end

message = """
Failed to compile Lua!
def exception({:lua_error, error, state}) do
%__MODULE__{errors: [Util.format_error(error)], state: state}
end

#{errors}
def exception({_line, _type, _failure} = error) do
%__MODULE__{errors: [Util.format_error(error)]}
end

def message(%__MODULE__{state: nil, errors: errors}) do
"""
Failed to compile Lua!
%__MODULE__{message: message}
#{Enum.join(errors, "\n")}
"""
end

def exception({:lua_error, error, state}) do
def message(%__MODULE__{errors: errors, state: state}) do
stacktrace = Luerl.New.get_stacktrace(state)

message = """
Failed to compile Lua script!
#{Util.format_error(error)}
#{Util.format_stacktrace(stacktrace, state)}
"""
Failed to compile Lua!
%__MODULE__{message: message}
end

def exception({_line, _type, _failure} = error) do
message = """
Failed to compile Lua script!
#{Enum.join(errors, "\n")}
#{Util.format_error(error)}
#{Util.format_stacktrace(stacktrace, state)}
"""

%__MODULE__{message: message}
end
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Lua.MixProject do
use Mix.Project

@url "https://github.com/tv-labs/lua"
@version "0.0.16"
@version "0.0.17"

def project do
[
Expand Down
41 changes: 28 additions & 13 deletions test/lua_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ defmodule LuaTest do
Failed to compile Lua!
Failed to tokenize: illegal token on line 1: \"hi)
"""

assert_raise Lua.CompilerException, message, fn ->
Expand All @@ -55,7 +54,6 @@ defmodule LuaTest do
Failed to compile Lua!
Line 3: syntax error before: '&'
"""

assert_raise Lua.CompilerException, message, fn ->
Expand Down Expand Up @@ -101,7 +99,7 @@ defmodule LuaTest do
path = test_file("illegal_token")

error = """
Failed to compile Lua script!
Failed to compile Lua!
Failed to tokenize: illegal token on line 1: '
Expand All @@ -127,7 +125,7 @@ defmodule LuaTest do
path = test_file("syntax_error")

error = """
Failed to compile Lua script!
Failed to compile Lua!
Line 1: syntax error before: ','
"""
Expand All @@ -142,7 +140,7 @@ defmodule LuaTest do

error =
"""
Failed to compile Lua script!
Failed to compile Lua!
undefined function nil
Expand Down Expand Up @@ -197,7 +195,7 @@ defmodule LuaTest do
test "parsing errors raise" do
lua = Lua.new()

assert_raise Lua.CompilerException, ~r/Failed to compile Lua script/, fn ->
assert_raise Lua.CompilerException, ~r/Failed to compile Lua/, fn ->
Lua.eval!(lua, """
local map = {a="1", b="2"}
Expand All @@ -220,20 +218,37 @@ defmodule LuaTest do
end
end

describe "load_chunk/2" do
describe "load_chunk!/2" do
test "loads a chunk into state" do
assert %Lua.Chunk{ref: nil} = chunk = ~LUA[print("hello")]c
assert {%Lua.Chunk{} = chunk, %Lua{}} = Lua.load_chunk(Lua.new(), chunk)
assert {%Lua.Chunk{} = chunk, %Lua{}} = Lua.load_chunk!(Lua.new(), chunk)
assert chunk.ref
end

test "can load strings as well" do
assert {%Lua.Chunk{} = chunk, %Lua{}} = Lua.load_chunk!(Lua.new(), ~S[print("hello")])
assert chunk.ref
end

test "invalid strings raise Lua.CompilerException" do
message = """
Failed to compile Lua!
Line 1: syntax error before: ';'
"""

assert_raise Lua.CompilerException, message, fn ->
Lua.load_chunk!(Lua.new(), "local foo = ;")
end
end

test "chunks can be loaded multiple times" do
lua = Lua.new()
chunk = ~LUA[print("hello")]c

assert {chunk, lua} = Lua.load_chunk(lua, chunk)
assert {chunk, lua} = Lua.load_chunk(lua, chunk)
assert {_chunk, _lua} = Lua.load_chunk(lua, chunk)
assert {chunk, lua} = Lua.load_chunk!(lua, chunk)
assert {chunk, lua} = Lua.load_chunk!(lua, chunk)
assert {_chunk, _lua} = Lua.load_chunk!(lua, chunk)
end
end

Expand Down Expand Up @@ -351,7 +366,7 @@ defmodule LuaTest do
lua = Lua.new()

error = """
Failed to compile Lua script!
Failed to compile Lua!
Failed to tokenize: illegal token on line 1: ")
Expand All @@ -364,7 +379,7 @@ defmodule LuaTest do
end

error = """
Failed to compile Lua script!
Failed to compile Lua!
Failed to tokenize: illegal token on line 1: "yuup)
Expand Down

0 comments on commit d5b9e05

Please sign in to comment.