Skip to content

Commit

Permalink
Add erlang-module support
Browse files Browse the repository at this point in the history
The code is deemed a module definition if the first line
start with "-module(".

In this case the entire code-block is interpreted as
erlang-module and if there are no errors the module is compiled
and loaded.
  • Loading branch information
fnchooft committed Jul 20, 2023
1 parent 122cb67 commit 9296c48
Showing 1 changed file with 79 additions and 0 deletions.
79 changes: 79 additions & 0 deletions lib/livebook/runtime/evaluator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,85 @@ defmodule Livebook.Runtime.Evaluator do
end

defp eval(:erlang, code, binding, env) do
try do
is_module = String.starts_with?(code, "-module(")

case is_module do
true -> eval_module(:erlang, code, binding, env)
false -> eval_statements(:erlang, code, binding, env)
end
catch
kind, error ->
stacktrace = prune_stacktrace(:erl_eval, __STACKTRACE__)
{{:error, kind, error, stacktrace}, []}
end
end

# Simple Erlang Module - helper functions
# ------------------------------------------------------------------------
# In order to handle the expression in their forms - separate per {:dot,_}
defp not_dot({:dot, _}) do
false
end

defp not_dot(_) do
true
end

# A list of scanned token - must be seperated per dot, in order to feed them
# into the :erl_parse.parse_form function.
defp tokens_to_forms(tokens) do
tokens_to_forms(tokens, [])
end

defp tokens_to_forms([], acc) do
:lists.reverse(acc)
end

defp tokens_to_forms(tokens, acc) do
form = :lists.takewhile(&not_dot/1, tokens)
[dot | rest] = :lists.dropwhile(&not_dot/1, tokens)
tokens_to_forms(rest, [{form ++ [dot]}] ++ acc)
end

# Create module - tokens from string
# Based on: https://stackoverflow.com/questions/2160660/how-to-compile-erlang-code-loaded-into-a-string
# The function will first assume that code starting with -module( is a erlang module definition

# Step 1: Scan the code
# Step 2: Convert to forms
# Step 3: Extract module name
# Step 4: Compile and load
defp eval_module(:erlang, code, binding, env) do
try do
{:ok, tokens, _} = :erl_scan.string(String.to_charlist(code), {1, 1}, [:text])

form_statements = tokens_to_forms(tokens)

forms =
Enum.map(
form_statements,
fn {form_statement} ->
{:ok, form} = :erl_parse.parse_form(form_statement)
form
end
)

# First statement - form = module definition
{:attribute, _, :module, module_name} = hd(forms)

# Compile the forms from the code-block
{:ok, _, binary_module} = :compile.forms(forms)
:code.load_binary(module_name, ~c"nofile", binary_module)
{{:ok, ~c"erlang module successfully compiled", binding, env}, []}
catch
kind, error ->
stacktrace = prune_stacktrace(:erl_eval, __STACKTRACE__)
{{:error, kind, error, stacktrace}, []}
end
end

defp eval_statements(:erlang, code, binding, env) do
try do
erl_binding =
Enum.reduce(binding, %{}, fn {name, value}, erl_binding ->
Expand Down

0 comments on commit 9296c48

Please sign in to comment.