Skip to content

Commit

Permalink
feat: Simple runtime and execution (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
doomspork committed Dec 21, 2023
1 parent 9f5a819 commit 1051f5e
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 22 deletions.
20 changes: 10 additions & 10 deletions README.md
@@ -1,11 +1,16 @@
# Luau
# Lūʻau

**TODO: Add description**
Welcome to the Lua Lūʻau!

## Installation
## What's in a name?

In ancient Hawaii, there were strict rules about who could eat together and what they could eat.
However, in 1819, King Kamehameha II abolished the religious laws and broke the taboos by eating with women marking the end of the kapu (taboo) system.
From this symbolic act were born the lūʻau parties.

Taking inspiration from King Kamehameha II's symbolic act of ending the kapu system we'll explore the power of Lua to break down taboos and unleash new possibilities.

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `luau` to your list of dependencies in `mix.exs`:
## Installation

```elixir
def deps do
Expand All @@ -14,8 +19,3 @@ def deps do
]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/luau>.

84 changes: 84 additions & 0 deletions lib/library.ex
@@ -0,0 +1,84 @@
defmodule Luau.Library do
@moduledoc """
Defines a library that will extend the Lua runtime.
## Example usage
defmodule MyLibrary do
use Luau.Library, scope: "my_library"
deflua hello(name) do
"Hello, \#{name}!"
end
end
In our Lua code, we can now call `my_library.hello("Robert")` and get back `"Hello, Robert!"`.
"""
@type t :: __MODULE__

@callback install(tuple()) :: tuple()
@callback table() :: [tuple()]
@callback scope() :: String.t()

defmacro __using__(opts) do
scope = Keyword.fetch!(opts, :scope)

quote do
require Record
Record.defrecord(:erl_mfa, Record.extract(:erl_mfa, from_lib: "luerl/include/luerl.hrl"))

Module.register_attribute(__MODULE__, :lua_functions, accumulate: true, persist: true)

import Luau.Library, only: [deflua: 2]

@before_compile unquote(__MODULE__)

@behaviour Luau.Library

@impl Luau.Library
def install(luerl_state) do
:luerl_heap.alloc_table(table(), luerl_state)
end

@impl Luau.Library
def scope, do: unquote(scope)
end
end

defmacro __before_compile__(_) do
quote do
@impl Luau.Library
def table do
functions =
:functions
|> __MODULE__.__info__()
|> Enum.group_by(&elem(&1, 0), &elem(&1, 1))

Enum.flat_map(@lua_functions, fn {func, wrapped_func} ->
functions
|> Map.get(func, [])
|> Enum.map(&{to_string(func), erl_mfa(m: __MODULE__, f: wrapped_func, a: &1)})
end)
end
end
end

defmacro deflua(call, do: body) do
{func, _line, _args} = call

# credo:disable-for-next-line
wrapped_func = String.to_atom("lua_wrapped_#{func}")

quote do
@lua_functions {unquote(func), unquote(wrapped_func)}
def unquote(call) do
unquote(body)
end

def unquote(wrapped_func)(_arity, args, state) do
res = apply(__MODULE__, unquote(func), args)
{:ok, state}
end
end
end
end
19 changes: 10 additions & 9 deletions lib/luau.ex
@@ -1,18 +1,19 @@
defmodule Luau do
@moduledoc """
Documentation for `Luau`.
Let's get this party started!
"""

@doc """
Hello world.
alias Luau.Runtime

## Examples
@spec initialize(Keyword.t()) :: Runtime.t()
def initialize(args) do
Runtime.initialize(args)
end

iex> Luau.hello()
:world
@spec execute(Runtime.t(), String.t()) :: {any(), Runtime.t()}
def execute(runtime, lua) do
{res, new_state} = :luerl.do(lua, runtime.state)

"""
def hello do
:world
{res, %{runtime | state: new_state}}
end
end
50 changes: 50 additions & 0 deletions lib/runtime.ex
@@ -0,0 +1,50 @@
defmodule Luau.Runtime do
@type t :: %__MODULE__{
id: nil | String.t(),
libraries: [Luau.Library.t()],
state: tuple(),
variables: %{String.t() => any()}
}

defstruct id: nil, libraries: [], state: nil, variables: %{}

@spec initialize(Keyword.t()) :: Luau.Runtime.t()
def initialize(opts \\ []) do
libraries = Keyword.get(opts, :libraries, [])
variables = Keyword.get(opts, :variables, %{})
runtime = %Luau.Runtime{id: Nanoid.generate(), state: :luerl.init()}

runtime
|> add_libraries(libraries)
|> set_variables(variables)
end

@spec set_variable(Luau.Runtime.t(), String.t(), any()) :: Luau.Runtime.t()
def set_variable(runtime, key, value) do
new_state = :luerl.set_table([key], value, runtime.state)

%{runtime | state: new_state, variables: Map.put(runtime.variables, key, value)}
end

@spec add_library(Luau.Runtime.t(), Luau.Library.t()) :: Luau.Runtime.t()
def add_library(runtime, library) do
new_state = :luerl.load_module([library.scope()], library, runtime.state)

%{runtime | state: new_state, libraries: [library | runtime.libraries]}
end

@spec run(Luau.Runtime.t(), String.t()) :: {any(), Luau.Runtime.t()}
def run(runtime, lua) do
{res, new_state} = :luerl.do(lua, runtime.state)

{res, %{runtime | state: new_state}}
end

defp add_libraries(runtime, libraries) do
Enum.reduce(libraries, runtime, &add_library(&2, &1))
end

defp set_variables(runtime, variables) do
Enum.reduce(variables, runtime, fn {key, value}, runtime -> set_variable(runtime, key, value) end)
end
end
1 change: 1 addition & 0 deletions mix.exs
Expand Up @@ -22,6 +22,7 @@ defmodule Luau.MixProject do
defp deps do
[
{:luerl, github: "rvirding/luerl", branch: "develop"},
{:nanoid, "~> 2.1.0"},

# Dev & Test dependencies
{:credo, "~> 1.6", only: [:dev, :test], runtime: false},
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Expand Up @@ -11,5 +11,6 @@
"makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"},
"nanoid": {:hex, :nanoid, "2.1.0", "d192a5bf1d774258bc49762b480fca0e3128178fa6d35a464af2a738526607fd", [:mix], [], "hexpm", "ebc7a342d02d213534a7f93a091d569b9fea7f26fcd3a638dc655060fc1f76ac"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
}
Empty file added test/library_test.exs
Empty file.
25 changes: 22 additions & 3 deletions test/luau_test.exs
@@ -1,8 +1,27 @@
defmodule LuauTest do
use ExUnit.Case
doctest Luau

test "greets the world" do
assert Luau.hello() == :world
defmodule TestLibrary do
@moduledoc """
A test library for Luau.
"""
use Luau.Library, scope: "test"

deflua hello(name) do
"Hello, #{name}!"
end
end

describe "initialize/1" do
test "returns a runtime" do
assert %Luau.Runtime{} = Luau.initialize([])
end
end

describe "execute/2" do
test "evaluates Lua" do
# runtime = Luau.initialize(libraries: [TestLibrary], variables: %{name: "Robert"})
# assert {"Hello, Robert!", %Luau.Runtime{}} = Luau.execute(runtime, ~s/test.hello(name)/)
end
end
end
61 changes: 61 additions & 0 deletions test/runtime_test.exs
@@ -0,0 +1,61 @@
defmodule Luau.RuntimeTest do
use ExUnit.Case

alias Luau.Runtime

defmodule BarLibrary do
use Luau.Library, scope: "bar"
end

defmodule FooLibrary do
use Luau.Library, scope: "foo"
end

describe "initialize/0" do
test "returns a runtime containing the Lua state and unique id" do
assert %Runtime{id: id, state: state} = Runtime.initialize()
assert is_binary(id)
refute is_nil(state)
end
end

describe "initialize/1" do
test "accepts `:libraries` as an option" do
assert %Runtime{libraries: [BarLibrary]} = Runtime.initialize(libraries: [BarLibrary])
end

test "accepts `:variables` as an option" do
assert %Runtime{} = Runtime.initialize(variables: %{name: "Robert"})
end
end

describe "set_variable/3" do
setup do
runtime = Runtime.initialize()

{:ok, runtime: runtime}
end

test "sets a variable", %{runtime: runtime} do
name = "name"
value = "Robert"

assert %Runtime{variables: %{^name => ^value}} = Runtime.set_variable(runtime, name, value)
end
end

describe "add_library/2" do
setup do
runtime = Runtime.initialize()

{:ok, runtime: runtime}
end

test "adds a library to the lua runtime", %{runtime: runtime} do
assert %Runtime{libraries: [FooLibrary, BarLibrary]} =
runtime
|> Runtime.add_library(BarLibrary)
|> Runtime.add_library(FooLibrary)
end
end
end

0 comments on commit 1051f5e

Please sign in to comment.