defmodule Jason.Codegen do @moduledoc false alias Jason.{Encode, EncodeError} def jump_table(ranges, default) do ranges |> ranges_to_orddict() |> :array.from_orddict(default) |> :array.to_orddict() end def jump_table(ranges, default, max) do ranges |> ranges_to_orddict() |> :array.from_orddict(default) |> resize(max) |> :array.to_orddict() end defmacro bytecase(var, do: clauses) do {ranges, default, literals} = clauses_to_ranges(clauses, []) jump_table = jump_table(ranges, default) quote do case unquote(var) do unquote(jump_table_to_clauses(jump_table, literals)) end end end defmacro bytecase(var, max, do: clauses) do {ranges, default, empty} = clauses_to_ranges(clauses, []) jump_table = jump_table(ranges, default, max) quote do case unquote(var) do unquote(jump_table_to_clauses(jump_table, empty)) end end end def build_kv_iodata(kv, encode_args) do elements = kv |> Enum.map(&encode_pair(&1, encode_args)) |> Enum.intersperse(",") collapse_static(List.flatten(["{", elements] ++ '}')) end defp clauses_to_ranges([{:->, _, [[{:in, _, [byte, range]}, rest], action]} | tail], acc) do clauses_to_ranges(tail, [{range, {byte, rest, action}} | acc]) end defp clauses_to_ranges([{:->, _, [[default, rest], action]} | tail], acc) do {Enum.reverse(acc), {default, rest, action}, literal_clauses(tail)} end defp literal_clauses(clauses) do Enum.map(clauses, fn {:->, _, [[literal], action]} -> {literal, action} end) end defp jump_table_to_clauses([{val, {{:_, _, _}, rest, action}} | tail], empty) do quote do <> -> unquote(action) end ++ jump_table_to_clauses(tail, empty) end defp jump_table_to_clauses([{val, {byte, rest, action}} | tail], empty) do quote do <> when unquote(byte) === unquote(val) -> unquote(action) end ++ jump_table_to_clauses(tail, empty) end defp jump_table_to_clauses([], literals) do Enum.flat_map(literals, fn {pattern, action} -> quote do unquote(pattern) -> unquote(action) end end) end defp resize(array, size), do: :array.resize(size, array) defp ranges_to_orddict(ranges) do ranges |> Enum.flat_map(fn {int, value} when is_integer(int) -> [{int, value}] {enum, value} -> Enum.map(enum, &{&1, value}) end) |> :orddict.from_list() end defp encode_pair({key, value}, encode_args) do key = IO.iodata_to_binary(Encode.key(key, &escape_key/3)) key = "\"" <> key <> "\":" [key, quote(do: Encode.value(unquote(value), unquote_splicing(encode_args)))] end defp escape_key(binary, _original, _skip) do check_safe_key!(binary) binary end defp check_safe_key!(binary) do for <<(<> <- binary)>> do if byte > 0x7F or byte < 0x1F or byte in '"\\/' do raise EncodeError, "invalid byte #{inspect(byte, base: :hex)} in literal key: #{inspect(binary)}" end end :ok end defp collapse_static([bin1, bin2 | rest]) when is_binary(bin1) and is_binary(bin2) do collapse_static([bin1 <> bin2 | rest]) end defp collapse_static([other | rest]) do [other | collapse_static(rest)] end defp collapse_static([]) do [] end end