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