cat-bookmarker/deps/jason/lib/codegen.ex

139 lines
3.4 KiB
Elixir
Raw Normal View History

2024-03-10 18:52:04 +00:00
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(val), unquote(rest)::bits>> ->
unquote(action)
end ++ jump_table_to_clauses(tail, empty)
end
defp jump_table_to_clauses([{val, {byte, rest, action}} | tail], empty) do
quote do
<<unquote(byte), unquote(rest)::bits>> 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 <<(<<byte>> <- 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