Commit initially
This commit is contained in:
commit
dfb24274b0
8 changed files with 223 additions and 0 deletions
4
.formatter.exs
Normal file
4
.formatter.exs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
||||
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where third-party dependencies like ExDoc output generated docs.
|
||||
/doc/
|
||||
|
||||
# Temporary files, for example, from tests.
|
||||
/tmp/
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
beam_js-*.tar
|
||||
|
||||
21
README.md
Normal file
21
README.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# BeamJs
|
||||
|
||||
**TODO: Add description**
|
||||
|
||||
## Installation
|
||||
|
||||
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
||||
by adding `beam_js` to your list of dependencies in `mix.exs`:
|
||||
|
||||
```elixir
|
||||
def deps do
|
||||
[
|
||||
{:beam_js, "~> 0.1.0"}
|
||||
]
|
||||
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/beam_js>.
|
||||
|
||||
133
lib/beam_js.ex
Normal file
133
lib/beam_js.ex
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
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
|
||||
29
mix.exs
Normal file
29
mix.exs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
defmodule BeamJs.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
def project do
|
||||
[
|
||||
app: :beam_js,
|
||||
version: "0.1.0",
|
||||
elixir: "~> 1.19",
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps: deps()
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help compile.app" to learn about applications.
|
||||
def application do
|
||||
[
|
||||
extra_applications: [:logger]
|
||||
]
|
||||
end
|
||||
|
||||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||
{:beam_file, "~> 0.6.4"}
|
||||
]
|
||||
end
|
||||
end
|
||||
3
mix.lock
Normal file
3
mix.lock
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
%{
|
||||
"beam_file": {:hex, :beam_file, "0.6.4", "73660f76330e6b29be2b8ae3779b30cb67a7803cde88a21e3e74371130eed4d4", [:mix], [], "hexpm", "3f295dba08a68360903e86be4f183d7fb70f762ee37ee176438dde23ea494431"},
|
||||
}
|
||||
8
test/beam_js_test.exs
Normal file
8
test/beam_js_test.exs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
defmodule BeamJsTest do
|
||||
use ExUnit.Case
|
||||
doctest BeamJs
|
||||
|
||||
test "greets the world" do
|
||||
assert BeamJs.hello() == :world
|
||||
end
|
||||
end
|
||||
1
test/test_helper.exs
Normal file
1
test/test_helper.exs
Normal file
|
|
@ -0,0 +1 @@
|
|||
ExUnit.start()
|
||||
Loading…
Reference in a new issue