133 lines
4.1 KiB
Elixir
133 lines
4.1 KiB
Elixir
defmodule BeamJs do
|
|
@moduledoc """
|
|
Documentation for `BeamJs`.
|
|
"""
|
|
require Logger
|
|
|
|
def module_code(module) do
|
|
module
|
|
|> BeamFile.byte_code()
|
|
|> ok(fn {_, _, _, _, _, sections} ->
|
|
{:ok, Enum.filter(sections, fn
|
|
{:function, _fun_name, _arity, _, _code} ->
|
|
true
|
|
_other ->
|
|
false
|
|
end)}
|
|
end)
|
|
|> ok(fn functions ->
|
|
Enum.map(functions, fn {:function, name, arity, _, code} ->
|
|
{"#{name}/#{arity}", transform_function_code(code)}
|
|
end)
|
|
|> Map.new()
|
|
end)
|
|
end
|
|
|
|
def transform_function_code(ops) do
|
|
Enum.reduce(ops, [], fn
|
|
{:label, number}, [{line_no, head_ops} | tail] ->
|
|
[{number, []} | [{line_no, Enum.reverse(head_ops)} | tail]]
|
|
|
|
{:label, number}, acc ->
|
|
[{number, []} | acc]
|
|
|
|
{:line, _line_number}, [] ->
|
|
[]
|
|
|
|
other_op, [{number, head} | tail] ->
|
|
[{number, [other_op|head]} | tail]
|
|
end)
|
|
|> Enum.reverse()
|
|
end
|
|
|
|
def encode_fun(label_sections) do
|
|
"{\n" <> Enum.map_join(label_sections, ",\n", fn {label, ops} ->
|
|
body = Enum.map_join(ops, "", &encode_op/1)
|
|
" #{label}: (p) => {\n#{body}}"
|
|
end) <> "}"
|
|
end
|
|
|
|
def encode_op(tuple) when is_tuple(tuple) do
|
|
op_name = elem(tuple, 0)
|
|
args =
|
|
try do
|
|
encode_args(tuple)
|
|
rescue
|
|
e ->
|
|
Logger.error("Error encoding op: #{inspect(tuple)}")
|
|
reraise e, __STACKTRACE__
|
|
end
|
|
|
|
" BeamJs.Ops.#{op_name}(p, " <> Enum.join(args, ", ") <> ");\n"
|
|
end
|
|
|
|
def encode_op(atom) when is_atom(atom), do: " BeamJs.Ops.#{atom}(p);\n"
|
|
|
|
def encode_args({:allocate, frame_size, live}), do: [number(frame_size), number(live)]
|
|
def encode_args({:line, number}), do: [number(number)]
|
|
def encode_args({:func_info, {:atom, m}, {:atom, f}, arity}), do: [quoted_string(m), quoted_string(f), number(arity)]
|
|
def encode_args({:select_val, source, {:f, fail_label}, comparison}), do: [encode_source(source), number(fail_label), encode_value(comparison)]
|
|
def encode_args({:move, source, {register_bank, number}}) when register_bank in [:x, :y], do: [encode_source(source), quoted_string(register_bank), number(number)]
|
|
def encode_args({:call, _live, {_m, f, a}}), do: [quoted_string(f), number(a)]
|
|
def encode_args({:call_ext_only, _live, {:extfunc, m, f, a}}), do: [quoted_string(m), quoted_string(f), number(a)]
|
|
def encode_args({:call_only, _live, {_m, f, a}}), do: [quoted_string(f), number(a)]
|
|
def encode_args({:deallocate, live}), do: [number(live)]
|
|
def encode_args({:gc_bif, bif, {:f, fail_to}, _unknown, args, {register_bank, number}}) when register_bank in [:x, :y] do
|
|
[
|
|
quoted_string(bif),
|
|
number(fail_to),
|
|
"[" <> (args |> Enum.map(&encode_source/1) |> Enum.join(", ")) <> "]",
|
|
register_bank,
|
|
number
|
|
]
|
|
end
|
|
|
|
def encode_source({:tr, source, _}), do: encode_source(source)
|
|
def encode_source({:x, number}), do: "p.registers.x[#{number}]"
|
|
def encode_source({:y, number}), do: "p.registers.y[#{number}]"
|
|
def encode_source({:integer, number}), do: number(number)
|
|
def encode_source(value), do: encode_value(value)
|
|
|
|
|
|
def encode_value({:literal, value}), do: encode_value(value)
|
|
|
|
def encode_value({type, value}) when type in [:atom, :x, :y, :list] do
|
|
~s|BeamJs.boxed("#{type}", #{encode_value(value)})|
|
|
end
|
|
|
|
def encode_value(tuple) when is_tuple(tuple) do
|
|
~s|BeamJs.boxed("tuple", #{encode_value(Tuple.to_list(tuple))})|
|
|
end
|
|
|
|
def encode_value(value) when is_binary(value) do
|
|
if String.valid?(value) do
|
|
quoted_string(value)
|
|
else
|
|
# Likely a raw bytes array
|
|
base64 = value |> Base.encode64() |> quoted_string()
|
|
~s|atob(#{base64})|
|
|
end
|
|
end
|
|
|
|
def encode_value(value) when is_list(value) do
|
|
~s|[#{Enum.map_join(value, ", ", &encode_value/1)}]|
|
|
end
|
|
|
|
def encode_value(value) when is_atom(value) do
|
|
quoted_string(value)
|
|
end
|
|
|
|
def encode_value(value) when is_number(value) do
|
|
"#{value}"
|
|
end
|
|
|
|
defp quoted_string(v), do: ~s|"#{v}"|
|
|
defp number(n), do: "#{n}"
|
|
|
|
def fact(0), do: 1
|
|
def fact(1), do: 1
|
|
def fact(n), do: fact(n - 1) + fact(n - 2)
|
|
|
|
defp ok({:ok, value}, fun), do: fun.(value)
|
|
defp ok(other, _fun), do: other
|
|
end
|