beam_js/lib/beam_js.ex

134 lines
4.1 KiB
Elixir
Raw Normal View History

2026-03-07 01:45:35 +00:00
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