defmodule WaParser do @moduledoc """ Documentation for `WaParser`. """ alias WaParser.Types.Atomic def stream(data) do Stream.resource( fn -> {data, &parse/1} end, fn {"", _} = acc -> {:halt, acc} {data, parser} -> case parser.(data) do {:ok, {result, rest, next}} when is_list(result) -> {result, {rest, next}} {:ok, {result, rest, next}} -> {[result], {rest, next}} end end, fn _ -> nil end ) end def parse(<<0x00, 0x61, 0x73, 0x6D, rest::binary>>) do {:ok, {{:magic, <<0x00, 0x61, 0x73, 0x6D>>}, rest, &parse_version/1}} end defp parse_version(<<0x01, 0x00, 0x00, 0x00, rest::binary>>) do {:ok, {{:version, <<0x01, 0x00, 0x00, 0x00>>}, rest, &parse_section/1}} end defp parse_section(<<id, rest::binary>>) do type = get_section_type(id) {:ok, {[{:section_id, id}, {:section_type, type}], rest, do_parse_section(type)}} end defp get_section_type(0), do: :custom defp get_section_type(1), do: :type defp get_section_type(3), do: :function defp get_section_type(4), do: :table defp get_section_type(5), do: :memory defp get_section_type(6), do: :global defp get_section_type(7), do: :export defp get_section_type(10), do: :code defp do_parse_section(type) do fn binary -> do_parse_section_len(type, binary) end end defp do_parse_section_len(type, binary) do {length, rest} = Atomic.u32(binary) {:ok, {{:section_length, length}, rest, parse_section_body(type, length)}} end defp parse_section_body(type, length) do fn binary -> do_parse_section_body(type, length, binary) end end defp do_parse_section_body(type, len, binary) do <<section::binary-size(len), rest::binary>> = binary parsed_body = section_body(type, section) {func_type, ""} = parsed_body {:ok, {{:section_body, func_type}, rest, &parse_section/1}} end defp section_body(:type, binary) do vec(&functype/1).(binary) end defp section_body(:function, binary) do vec(&typeidx/1).(binary) end defp section_body(:table, binary) do vec(&tabletype/1).(binary) end defp section_body(:memory, binary) do vec(&memtype/1).(binary) end defp section_body(:global, binary) do vec(&global/1).(binary) end defp section_body(:export, binary) do vec(&export/1).(binary) end defp section_body(:code, binary) do vec(&code/1).(binary) end defp section_body(:custom, binary) do {n, rest} = name(binary) {%{name: n, content: rest}, ""} end defp code(binary) do {code_size, rest} = Atomic.u32(binary) {code, rest} = func(rest) {%{size: code_size, code: code}, rest} end defp func(binary) do {locals, rest} = vec(&locals/1).(binary) {e, rest} = expr(rest) {%{locals: locals, expr: e}, rest} end defp locals(binary) do {num, rest} = Atomic.u32(binary) {type, rest} = valtype(rest) {%{num: num, type: type}, rest} end defp export(binary) do {name, rest} = name(binary) {export_desc, rest} = exportdesc(rest) {{name, export_desc}, rest} end defp name(binary) do {nm, rest} = vec(&byte/1).(binary) {List.to_string(nm), rest} end defp byte(<<b, rest::binary>>) do {b, rest} end defp exportdesc(<<0x00, rest::binary>>) do {fun, rest} = funcidx(rest) {{:func, fun}, rest} end defp exportdesc(<<0x02, rest::binary>>) do {mem, rest} = memidx(rest) {{:mem, mem}, rest} end defp exportdesc(<<0x03, rest::binary>>) do {glob, rest} = globalidx(rest) {{:global, glob}, rest} end defp global(binary) do {gl, rest} = globaltype(binary) {ex, rest} = expr(rest) {{gl, ex}, rest} end defp globaltype(binary) do {vt, rest} = valtype(binary) {m, rest} = mut(rest) {{vt, m}, rest} end defp mut(<<0x00, rest::binary>>) do {:const, rest} end defp mut(<<0x01, rest::binary>>) do {:var, rest} end defp expr(binary) do expr(binary, []) end defp expr(<<0x03, rest::binary>>, acc) do {bt, rest} = blocktype(rest) {exprs, rest} = expr(rest) expr(rest, [ {:loop, %{blocktype: bt, instr: exprs}} | acc ]) end defp expr(<<0x04, rest::binary>>, acc) do {bt, rest} = blocktype(rest) {exprs, rest} = expr(rest) expr(rest, [ {:if, %{blocktype: bt, instr: exprs}} | acc ]) end defp expr(<<0x0B, rest::binary>>, acc) do {acc |> Enum.reverse(), rest} end defp expr(<<0x1B, rest::binary>>, acc) do expr(rest, [:select | acc]) end defp expr(<<0x0D, rest::binary>>, acc) do {l, rest} = labelidx(rest) expr(rest, [{:br_if, l} | acc]) end defp expr(<<0x20, rest::binary>>, acc) do {idx, rest} = localidx(rest) expr(rest, [{:"local.get", idx} | acc]) end defp expr(<<0x21, rest::binary>>, acc) do {idx, rest} = localidx(rest) expr(rest, [{:"local.set", idx} | acc]) end defp expr(<<0x22, rest::binary>>, acc) do {idx, rest} = localidx(rest) expr(rest, [{:"local.tee", idx} | acc]) end defp expr(<<0x41, rest::binary>>, acc) do {val, rest} = Atomic.i32(rest) expr(rest, [{:"i32.const", val} | acc]) end @plain_numeric 0x45..0xC4 defp expr(<<opcode, rest::binary>>, acc) when opcode in @plain_numeric do expr(rest, [plain_numeric_instr(opcode) | acc]) end defp plain_numeric_instr(0x46), do: :"i32.eq" defp plain_numeric_instr(0x47), do: :"i32.ne" defp plain_numeric_instr(0x48), do: :"i32.lt_s" defp plain_numeric_instr(0x4A), do: :"i32.gt_s" defp plain_numeric_instr(0x4C), do: :"i32.le_s" defp plain_numeric_instr(0x4E), do: :"i32.ge_s" defp plain_numeric_instr(0x6A), do: :"i32.add" defp plain_numeric_instr(0x6B), do: :"i32.sub" defp plain_numeric_instr(0x6C), do: :"i32.mul" defp plain_numeric_instr(0x6D), do: :"i32.div_s" defp blocktype(<<0x40, rest::binary>>), do: {:empty, rest} defp memtype(binary) do limits(binary) end defp tabletype(binary) do {et, rest} = reftype(binary) {lim, rest} = limits(rest) {{et, lim}, rest} end defp reftype(<<0x70, rest::binary>>) do {:funcref, rest} end defp limits(<<0x00, rest::binary>>) do {min, rest} = Atomic.u32(rest) {{min, nil}, rest} end defp limits(<<0x01, rest::binary>>) do {min, rest} = Atomic.u32(rest) {max, rest} = Atomic.u32(rest) {{min, max}, rest} end defp vec(type) do fn binary -> {length, rest} = Atomic.u32(binary) do_vec(type, length, rest) end end defp do_vec(type, length, binary) do do_vec(type, length, binary, []) end defp do_vec(_type, 0, rest, acc) do {acc |> Enum.reverse(), rest} end defp do_vec(type, length, binary, acc) do {v, rest} = type.(binary) do_vec(type, length - 1, rest, [v | acc]) end defp functype(<<0x60, rest::binary>>) do {param_type, rest} = resulttype(rest) {result_type, rest} = resulttype(rest) {{param_type, result_type}, rest} end defp resulttype(binary) do vec(&valtype/1).(binary) end defp valtype(<<0x7F, rest::binary>>), do: {:i32, rest} defp funcidx(binary), do: Atomic.u32(binary) defp globalidx(binary), do: Atomic.u32(binary) defp labelidx(binary), do: Atomic.u32(binary) defp localidx(binary), do: Atomic.u32(binary) defp memidx(binary), do: Atomic.u32(binary) defp typeidx(binary), do: Atomic.u32(binary) end