zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

encode.ex (21380B)


      1 defmodule Jason.EncodeError do
      2   defexception [:message]
      3 
      4   @type t :: %__MODULE__{message: String.t}
      5 
      6   def new({:duplicate_key, key}) do
      7     %__MODULE__{message: "duplicate key: #{key}"}
      8   end
      9   def new({:invalid_byte, byte, original}) do
     10     %__MODULE__{message: "invalid byte #{inspect byte, base: :hex} in #{inspect original}"}
     11   end
     12 end
     13 
     14 defmodule Jason.Encode do
     15   @moduledoc """
     16   Utilities for encoding elixir values to JSON.
     17   """
     18 
     19   import Bitwise
     20 
     21   alias Jason.{Codegen, EncodeError, Encoder, Fragment, OrderedObject}
     22 
     23   @typep escape :: (String.t, String.t, integer -> iodata)
     24   @typep encode_map :: (map, escape, encode_map -> iodata)
     25   @opaque opts :: {escape, encode_map}
     26 
     27   @dialyzer :no_improper_lists
     28 
     29   # @compile :native
     30 
     31   @doc false
     32   @spec encode(any, map) :: {:ok, iodata} | {:error, EncodeError.t | Exception.t}
     33   def encode(value, opts) do
     34     escape = escape_function(opts)
     35     encode_map = encode_map_function(opts)
     36     try do
     37       {:ok, value(value, escape, encode_map)}
     38     catch
     39       :throw, %EncodeError{} = e ->
     40         {:error, e}
     41       :error, %Protocol.UndefinedError{protocol: Jason.Encoder} = e ->
     42         {:error, e}
     43     end
     44   end
     45 
     46   defp encode_map_function(%{maps: maps}) do
     47     case maps do
     48       :naive -> &map_naive/3
     49       :strict -> &map_strict/3
     50     end
     51   end
     52 
     53   defp escape_function(%{escape: escape}) do
     54     case escape do
     55       :json -> &escape_json/3
     56       :html_safe -> &escape_html/3
     57       :unicode_safe -> &escape_unicode/3
     58       :javascript_safe -> &escape_javascript/3
     59       # Keep for compatibility with Poison
     60       :javascript -> &escape_javascript/3
     61       :unicode -> &escape_unicode/3
     62     end
     63   end
     64 
     65   @doc """
     66   Equivalent to calling the `Jason.Encoder.encode/2` protocol function.
     67 
     68   Slightly more efficient for built-in types because of the internal dispatching.
     69   """
     70   @spec value(term, opts) :: iodata
     71   def value(value, {escape, encode_map}) do
     72     value(value, escape, encode_map)
     73   end
     74 
     75   @doc false
     76   # We use this directly in the helpers and deriving for extra speed
     77   def value(value, escape, _encode_map) when is_atom(value) do
     78     encode_atom(value, escape)
     79   end
     80 
     81   def value(value, escape, _encode_map) when is_binary(value) do
     82     encode_string(value, escape)
     83   end
     84 
     85   def value(value, _escape, _encode_map) when is_integer(value) do
     86     integer(value)
     87   end
     88 
     89   def value(value, _escape, _encode_map) when is_float(value) do
     90     float(value)
     91   end
     92 
     93   def value(value, escape, encode_map) when is_list(value) do
     94     list(value, escape, encode_map)
     95   end
     96 
     97   def value(%{__struct__: module} = value, escape, encode_map) do
     98     struct(value, escape, encode_map, module)
     99   end
    100 
    101   def value(value, escape, encode_map) when is_map(value) do
    102     case Map.to_list(value) do
    103       [] -> "{}"
    104       keyword -> encode_map.(keyword, escape, encode_map)
    105     end
    106   end
    107 
    108   def value(value, escape, encode_map) do
    109     Encoder.encode(value, {escape, encode_map})
    110   end
    111 
    112   @compile {:inline, integer: 1, float: 1}
    113 
    114   @spec atom(atom, opts) :: iodata
    115   def atom(atom, {escape, _encode_map}) do
    116     encode_atom(atom, escape)
    117   end
    118 
    119   defp encode_atom(nil, _escape), do: "null"
    120   defp encode_atom(true, _escape), do: "true"
    121   defp encode_atom(false, _escape), do: "false"
    122   defp encode_atom(atom, escape),
    123     do: encode_string(Atom.to_string(atom), escape)
    124 
    125   @spec integer(integer) :: iodata
    126   def integer(integer) do
    127     Integer.to_string(integer)
    128   end
    129 
    130   has_short_format = try do
    131     :erlang.float_to_binary(1.0, [:short])
    132   catch
    133     _, _ -> false
    134   else
    135     _ -> true
    136   end
    137 
    138   @spec float(float) :: iodata
    139   if has_short_format do
    140     def float(float) do
    141       :erlang.float_to_binary(float, [:short])
    142     end
    143   else
    144     def float(float) do
    145       :io_lib_format.fwrite_g(float)
    146    end
    147   end
    148 
    149   @spec list(list, opts) :: iodata
    150   def list(list, {escape, encode_map}) do
    151     list(list, escape, encode_map)
    152   end
    153 
    154   defp list([], _escape, _encode_map) do
    155     "[]"
    156   end
    157 
    158   defp list([head | tail], escape, encode_map) do
    159     [?[, value(head, escape, encode_map)
    160      | list_loop(tail, escape, encode_map)]
    161   end
    162 
    163   defp list_loop([], _escape, _encode_map) do
    164     ']'
    165   end
    166 
    167   defp list_loop([head | tail], escape, encode_map) do
    168     [?,, value(head, escape, encode_map)
    169      | list_loop(tail, escape, encode_map)]
    170   end
    171 
    172   @spec keyword(keyword, opts) :: iodata
    173   def keyword(list, _) when list == [], do: "{}"
    174   def keyword(list, {escape, encode_map}) when is_list(list) do
    175     encode_map.(list, escape, encode_map)
    176   end
    177 
    178   @spec map(map, opts) :: iodata
    179   def map(value, {escape, encode_map}) do
    180     case Map.to_list(value) do
    181       [] -> "{}"
    182       keyword -> encode_map.(keyword, escape, encode_map)
    183     end
    184   end
    185 
    186   defp map_naive([{key, value} | tail], escape, encode_map) do
    187     ["{\"", key(key, escape), "\":",
    188     value(value, escape, encode_map)
    189     | map_naive_loop(tail, escape, encode_map)]
    190   end
    191 
    192   defp map_naive_loop([], _escape, _encode_map) do
    193     '}'
    194   end
    195 
    196   defp map_naive_loop([{key, value} | tail], escape, encode_map) do
    197     [",\"", key(key, escape), "\":",
    198      value(value, escape, encode_map)
    199      | map_naive_loop(tail, escape, encode_map)]
    200   end
    201 
    202   defp map_strict([{key, value} | tail], escape, encode_map) do
    203     key = IO.iodata_to_binary(key(key, escape))
    204     visited = %{key => []}
    205     ["{\"", key, "\":",
    206      value(value, escape, encode_map)
    207      | map_strict_loop(tail, escape, encode_map, visited)]
    208   end
    209 
    210   defp map_strict_loop([], _encode_map, _escape, _visited) do
    211     '}'
    212   end
    213 
    214   defp map_strict_loop([{key, value} | tail], escape, encode_map, visited) do
    215     key = IO.iodata_to_binary(key(key, escape))
    216     case visited do
    217       %{^key => _} ->
    218         error({:duplicate_key, key})
    219       _ ->
    220         visited = Map.put(visited, key, [])
    221         [",\"", key, "\":",
    222          value(value, escape, encode_map)
    223          | map_strict_loop(tail, escape, encode_map, visited)]
    224     end
    225   end
    226 
    227   @spec struct(struct, opts) :: iodata
    228   def struct(%module{} = value, {escape, encode_map}) do
    229     struct(value, escape, encode_map, module)
    230   end
    231 
    232   # TODO: benchmark the effect of inlining the to_iso8601 functions
    233   for module <- [Date, Time, NaiveDateTime, DateTime] do
    234     defp struct(value, _escape, _encode_map, unquote(module)) do
    235       [?", unquote(module).to_iso8601(value), ?"]
    236     end
    237   end
    238 
    239   defp struct(value, _escape, _encode_map, Decimal) do
    240     # silence the xref warning
    241     decimal = Decimal
    242     [?", decimal.to_string(value, :normal), ?"]
    243   end
    244 
    245   defp struct(value, escape, encode_map, Fragment) do
    246     %{encode: encode} = value
    247     encode.({escape, encode_map})
    248   end
    249 
    250   defp struct(value, escape, encode_map, OrderedObject) do
    251     case value do
    252       %{values: []} -> "{}"
    253       %{values: values} -> encode_map.(values, escape, encode_map)
    254     end
    255   end
    256 
    257   defp struct(value, escape, encode_map, _module) do
    258     Encoder.encode(value, {escape, encode_map})
    259   end
    260 
    261   @doc false
    262   # This is used in the helpers and deriving implementation
    263   def key(string, escape) when is_binary(string) do
    264     escape.(string, string, 0)
    265   end
    266   def key(atom, escape) when is_atom(atom) do
    267     string = Atom.to_string(atom)
    268     escape.(string, string, 0)
    269   end
    270   def key(other, escape) do
    271     string = String.Chars.to_string(other)
    272     escape.(string, string, 0)
    273   end
    274 
    275   @spec string(String.t, opts) :: iodata
    276   def string(string, {escape, _encode_map}) do
    277     encode_string(string, escape)
    278   end
    279 
    280   defp encode_string(string, escape) do
    281     [?", escape.(string, string, 0), ?"]
    282   end
    283 
    284   slash_escapes = Enum.zip('\b\t\n\f\r\"\\', 'btnfr"\\')
    285   surogate_escapes = Enum.zip([0x2028, 0x2029], ["\\u2028", "\\u2029"])
    286   ranges = [{0x00..0x1F, :unicode} | slash_escapes]
    287   html_ranges = [{0x00..0x1F, :unicode}, {?<, :unicode}, {?/, ?/} | slash_escapes]
    288   escape_jt = Codegen.jump_table(html_ranges, :error)
    289 
    290   Enum.each(escape_jt, fn
    291     {byte, :unicode} ->
    292       sequence = List.to_string(:io_lib.format("\\u~4.16.0B", [byte]))
    293       defp escape(unquote(byte)), do: unquote(sequence)
    294     {byte, char} when is_integer(char) ->
    295       defp escape(unquote(byte)), do: unquote(<<?\\, char>>)
    296     {byte, :error} ->
    297       defp escape(unquote(byte)), do: throw(:error)
    298   end)
    299 
    300   ## regular JSON escape
    301 
    302   json_jt = Codegen.jump_table(ranges, :chunk, 0x7F + 1)
    303 
    304   defp escape_json(data, original, skip) do
    305     escape_json(data, [], original, skip)
    306   end
    307 
    308   Enum.map(json_jt, fn
    309     {byte, :chunk} ->
    310       defp escape_json(<<byte, rest::bits>>, acc, original, skip)
    311            when byte === unquote(byte) do
    312         escape_json_chunk(rest, acc, original, skip, 1)
    313       end
    314     {byte, _escape} ->
    315       defp escape_json(<<byte, rest::bits>>, acc, original, skip)
    316            when byte === unquote(byte) do
    317         acc = [acc | escape(byte)]
    318         escape_json(rest, acc, original, skip + 1)
    319       end
    320   end)
    321   defp escape_json(<<char::utf8, rest::bits>>, acc, original, skip)
    322        when char <= 0x7FF do
    323     escape_json_chunk(rest, acc, original, skip, 2)
    324   end
    325   defp escape_json(<<char::utf8, rest::bits>>, acc, original, skip)
    326        when char <= 0xFFFF do
    327     escape_json_chunk(rest, acc, original, skip, 3)
    328   end
    329   defp escape_json(<<_char::utf8, rest::bits>>, acc, original, skip) do
    330     escape_json_chunk(rest, acc, original, skip, 4)
    331   end
    332   defp escape_json(<<>>, acc, _original, _skip) do
    333     acc
    334   end
    335   defp escape_json(<<byte, _rest::bits>>, _acc, original, _skip) do
    336     error({:invalid_byte, byte, original})
    337   end
    338 
    339   Enum.map(json_jt, fn
    340     {byte, :chunk} ->
    341       defp escape_json_chunk(<<byte, rest::bits>>, acc, original, skip, len)
    342            when byte === unquote(byte) do
    343         escape_json_chunk(rest, acc, original, skip, len + 1)
    344       end
    345     {byte, _escape} ->
    346       defp escape_json_chunk(<<byte, rest::bits>>, acc, original, skip, len)
    347            when byte === unquote(byte) do
    348         part = binary_part(original, skip, len)
    349         acc = [acc, part | escape(byte)]
    350         escape_json(rest, acc, original, skip + len + 1)
    351       end
    352   end)
    353   defp escape_json_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len)
    354        when char <= 0x7FF do
    355     escape_json_chunk(rest, acc, original, skip, len + 2)
    356   end
    357   defp escape_json_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len)
    358        when char <= 0xFFFF do
    359     escape_json_chunk(rest, acc, original, skip, len + 3)
    360   end
    361   defp escape_json_chunk(<<_char::utf8, rest::bits>>, acc, original, skip, len) do
    362     escape_json_chunk(rest, acc, original, skip, len + 4)
    363   end
    364   defp escape_json_chunk(<<>>, acc, original, skip, len) do
    365     part = binary_part(original, skip, len)
    366     [acc | part]
    367   end
    368   defp escape_json_chunk(<<byte, _rest::bits>>, _acc, original, _skip, _len) do
    369     error({:invalid_byte, byte, original})
    370   end
    371 
    372   ## javascript safe JSON escape
    373 
    374   defp escape_javascript(data, original, skip) do
    375     escape_javascript(data, [], original, skip)
    376   end
    377 
    378   Enum.map(json_jt, fn
    379     {byte, :chunk} ->
    380       defp escape_javascript(<<byte, rest::bits>>, acc, original, skip)
    381             when byte === unquote(byte) do
    382         escape_javascript_chunk(rest, acc, original, skip, 1)
    383       end
    384     {byte, _escape} ->
    385       defp escape_javascript(<<byte, rest::bits>>, acc, original, skip)
    386             when byte === unquote(byte) do
    387         acc = [acc | escape(byte)]
    388         escape_javascript(rest, acc, original, skip + 1)
    389       end
    390   end)
    391   defp escape_javascript(<<char::utf8, rest::bits>>, acc, original, skip)
    392         when char <= 0x7FF do
    393     escape_javascript_chunk(rest, acc, original, skip, 2)
    394   end
    395   Enum.map(surogate_escapes, fn {byte, escape} ->
    396     defp escape_javascript(<<unquote(byte)::utf8, rest::bits>>, acc, original, skip) do
    397       acc = [acc | unquote(escape)]
    398       escape_javascript(rest, acc, original, skip + 3)
    399     end
    400   end)
    401   defp escape_javascript(<<char::utf8, rest::bits>>, acc, original, skip)
    402         when char <= 0xFFFF do
    403     escape_javascript_chunk(rest, acc, original, skip, 3)
    404   end
    405   defp escape_javascript(<<_char::utf8, rest::bits>>, acc, original, skip) do
    406     escape_javascript_chunk(rest, acc, original, skip, 4)
    407   end
    408   defp escape_javascript(<<>>, acc, _original, _skip) do
    409     acc
    410   end
    411   defp escape_javascript(<<byte, _rest::bits>>, _acc, original, _skip) do
    412     error({:invalid_byte, byte, original})
    413   end
    414 
    415   Enum.map(json_jt, fn
    416     {byte, :chunk} ->
    417       defp escape_javascript_chunk(<<byte, rest::bits>>, acc, original, skip, len)
    418             when byte === unquote(byte) do
    419         escape_javascript_chunk(rest, acc, original, skip, len + 1)
    420       end
    421     {byte, _escape} ->
    422       defp escape_javascript_chunk(<<byte, rest::bits>>, acc, original, skip, len)
    423             when byte === unquote(byte) do
    424         part = binary_part(original, skip, len)
    425         acc = [acc, part | escape(byte)]
    426         escape_javascript(rest, acc, original, skip + len + 1)
    427       end
    428   end)
    429   defp escape_javascript_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len)
    430         when char <= 0x7FF do
    431     escape_javascript_chunk(rest, acc, original, skip, len + 2)
    432   end
    433   Enum.map(surogate_escapes, fn {byte, escape} ->
    434     defp escape_javascript_chunk(<<unquote(byte)::utf8, rest::bits>>, acc, original, skip, len) do
    435       part = binary_part(original, skip, len)
    436       acc = [acc, part | unquote(escape)]
    437       escape_javascript(rest, acc, original, skip + len + 3)
    438     end
    439   end)
    440   defp escape_javascript_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len)
    441         when char <= 0xFFFF do
    442     escape_javascript_chunk(rest, acc, original, skip, len + 3)
    443   end
    444   defp escape_javascript_chunk(<<_char::utf8, rest::bits>>, acc, original, skip, len) do
    445     escape_javascript_chunk(rest, acc, original, skip, len + 4)
    446   end
    447   defp escape_javascript_chunk(<<>>, acc, original, skip, len) do
    448     part = binary_part(original, skip, len)
    449     [acc | part]
    450   end
    451   defp escape_javascript_chunk(<<byte, _rest::bits>>, _acc, original, _skip, _len) do
    452     error({:invalid_byte, byte, original})
    453   end
    454 
    455   ## HTML safe JSON escape
    456 
    457   html_jt = Codegen.jump_table(html_ranges, :chunk, 0x7F + 1)
    458 
    459   defp escape_html(data, original, skip) do
    460     escape_html(data, [], original, skip)
    461   end
    462 
    463   Enum.map(html_jt, fn
    464     {byte, :chunk} ->
    465       defp escape_html(<<byte, rest::bits>>, acc, original, skip)
    466             when byte === unquote(byte) do
    467         escape_html_chunk(rest, acc, original, skip, 1)
    468       end
    469     {byte, _escape} ->
    470       defp escape_html(<<byte, rest::bits>>, acc, original, skip)
    471             when byte === unquote(byte) do
    472         acc = [acc | escape(byte)]
    473         escape_html(rest, acc, original, skip + 1)
    474       end
    475   end)
    476   defp escape_html(<<char::utf8, rest::bits>>, acc, original, skip)
    477         when char <= 0x7FF do
    478     escape_html_chunk(rest, acc, original, skip, 2)
    479   end
    480   Enum.map(surogate_escapes, fn {byte, escape} ->
    481     defp escape_html(<<unquote(byte)::utf8, rest::bits>>, acc, original, skip) do
    482       acc = [acc | unquote(escape)]
    483       escape_html(rest, acc, original, skip + 3)
    484     end
    485   end)
    486   defp escape_html(<<char::utf8, rest::bits>>, acc, original, skip)
    487         when char <= 0xFFFF do
    488     escape_html_chunk(rest, acc, original, skip, 3)
    489   end
    490   defp escape_html(<<_char::utf8, rest::bits>>, acc, original, skip) do
    491     escape_html_chunk(rest, acc, original, skip, 4)
    492   end
    493   defp escape_html(<<>>, acc, _original, _skip) do
    494     acc
    495   end
    496   defp escape_html(<<byte, _rest::bits>>, _acc, original, _skip) do
    497     error({:invalid_byte, byte, original})
    498   end
    499 
    500   Enum.map(html_jt, fn
    501     {byte, :chunk} ->
    502       defp escape_html_chunk(<<byte, rest::bits>>, acc, original, skip, len)
    503             when byte === unquote(byte) do
    504         escape_html_chunk(rest, acc, original, skip, len + 1)
    505       end
    506     {byte, _escape} ->
    507       defp escape_html_chunk(<<byte, rest::bits>>, acc, original, skip, len)
    508             when byte === unquote(byte) do
    509         part = binary_part(original, skip, len)
    510         acc = [acc, part | escape(byte)]
    511         escape_html(rest, acc, original, skip + len + 1)
    512       end
    513   end)
    514   defp escape_html_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len)
    515         when char <= 0x7FF do
    516     escape_html_chunk(rest, acc, original, skip, len + 2)
    517   end
    518   Enum.map(surogate_escapes, fn {byte, escape} ->
    519     defp escape_html_chunk(<<unquote(byte)::utf8, rest::bits>>, acc, original, skip, len) do
    520       part = binary_part(original, skip, len)
    521       acc = [acc, part | unquote(escape)]
    522       escape_html(rest, acc, original, skip + len + 3)
    523     end
    524   end)
    525   defp escape_html_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len)
    526         when char <= 0xFFFF do
    527     escape_html_chunk(rest, acc, original, skip, len + 3)
    528   end
    529   defp escape_html_chunk(<<_char::utf8, rest::bits>>, acc, original, skip, len) do
    530     escape_html_chunk(rest, acc, original, skip, len + 4)
    531   end
    532   defp escape_html_chunk(<<>>, acc, original, skip, len) do
    533     part = binary_part(original, skip, len)
    534     [acc | part]
    535   end
    536   defp escape_html_chunk(<<byte, _rest::bits>>, _acc, original, _skip, _len) do
    537     error({:invalid_byte, byte, original})
    538   end
    539 
    540   ## unicode escape
    541 
    542   defp escape_unicode(data, original, skip) do
    543     escape_unicode(data, [], original, skip)
    544   end
    545 
    546   Enum.map(json_jt, fn
    547     {byte, :chunk} ->
    548       defp escape_unicode(<<byte, rest::bits>>, acc, original, skip)
    549            when byte === unquote(byte) do
    550         escape_unicode_chunk(rest, acc, original, skip, 1)
    551       end
    552     {byte, _escape} ->
    553       defp escape_unicode(<<byte, rest::bits>>, acc, original, skip)
    554            when byte === unquote(byte) do
    555         acc = [acc | escape(byte)]
    556         escape_unicode(rest, acc, original, skip + 1)
    557       end
    558   end)
    559   defp escape_unicode(<<char::utf8, rest::bits>>, acc, original, skip)
    560        when char <= 0xFF do
    561     acc = [acc, "\\u00" | Integer.to_string(char, 16)]
    562     escape_unicode(rest, acc, original, skip + 2)
    563   end
    564   defp escape_unicode(<<char::utf8, rest::bits>>, acc, original, skip)
    565         when char <= 0x7FF do
    566     acc = [acc, "\\u0" | Integer.to_string(char, 16)]
    567     escape_unicode(rest, acc, original, skip + 2)
    568   end
    569   defp escape_unicode(<<char::utf8, rest::bits>>, acc, original, skip)
    570         when char <= 0xFFF do
    571     acc = [acc, "\\u0" | Integer.to_string(char, 16)]
    572     escape_unicode(rest, acc, original, skip + 3)
    573   end
    574   defp escape_unicode(<<char::utf8, rest::bits>>, acc, original, skip)
    575         when char <= 0xFFFF do
    576     acc = [acc, "\\u" | Integer.to_string(char, 16)]
    577     escape_unicode(rest, acc, original, skip + 3)
    578   end
    579   defp escape_unicode(<<char::utf8, rest::bits>>, acc, original, skip) do
    580     char = char - 0x10000
    581     acc =
    582       [
    583         acc,
    584         "\\uD", Integer.to_string(0x800 ||| (char >>> 10), 16),
    585         "\\uD" | Integer.to_string(0xC00 ||| (char &&& 0x3FF), 16)
    586       ]
    587     escape_unicode(rest, acc, original, skip + 4)
    588   end
    589   defp escape_unicode(<<>>, acc, _original, _skip) do
    590     acc
    591   end
    592   defp escape_unicode(<<byte, _rest::bits>>, _acc, original, _skip) do
    593     error({:invalid_byte, byte, original})
    594   end
    595 
    596   Enum.map(json_jt, fn
    597     {byte, :chunk} ->
    598       defp escape_unicode_chunk(<<byte, rest::bits>>, acc, original, skip, len)
    599             when byte === unquote(byte) do
    600         escape_unicode_chunk(rest, acc, original, skip, len + 1)
    601       end
    602     {byte, _escape} ->
    603       defp escape_unicode_chunk(<<byte, rest::bits>>, acc, original, skip, len)
    604             when byte === unquote(byte) do
    605         part = binary_part(original, skip, len)
    606         acc = [acc, part | escape(byte)]
    607         escape_unicode(rest, acc, original, skip + len + 1)
    608       end
    609   end)
    610   defp escape_unicode_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len)
    611        when char <= 0xFF do
    612     part = binary_part(original, skip, len)
    613     acc = [acc, part, "\\u00" | Integer.to_string(char, 16)]
    614     escape_unicode(rest, acc, original, skip + len + 2)
    615   end
    616   defp escape_unicode_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len)
    617         when char <= 0x7FF do
    618     part = binary_part(original, skip, len)
    619     acc = [acc, part, "\\u0" | Integer.to_string(char, 16)]
    620     escape_unicode(rest, acc, original, skip + len + 2)
    621   end
    622   defp escape_unicode_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len)
    623         when char <= 0xFFF do
    624     part = binary_part(original, skip, len)
    625     acc = [acc, part, "\\u0" | Integer.to_string(char, 16)]
    626     escape_unicode(rest, acc, original, skip + len + 3)
    627   end
    628   defp escape_unicode_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len)
    629         when char <= 0xFFFF do
    630     part = binary_part(original, skip, len)
    631     acc = [acc, part, "\\u" | Integer.to_string(char, 16)]
    632     escape_unicode(rest, acc, original, skip + len + 3)
    633   end
    634   defp escape_unicode_chunk(<<char::utf8, rest::bits>>, acc, original, skip, len) do
    635     char = char - 0x10000
    636     part = binary_part(original, skip, len)
    637     acc =
    638       [
    639         acc, part,
    640         "\\uD", Integer.to_string(0x800 ||| (char >>> 10), 16),
    641         "\\uD" | Integer.to_string(0xC00 ||| (char &&& 0x3FF), 16)
    642       ]
    643     escape_unicode(rest, acc, original, skip + len + 4)
    644   end
    645   defp escape_unicode_chunk(<<>>, acc, original, skip, len) do
    646     part = binary_part(original, skip, len)
    647     [acc | part]
    648   end
    649   defp escape_unicode_chunk(<<byte, _rest::bits>>, _acc, original, _skip, _len) do
    650     error({:invalid_byte, byte, original})
    651   end
    652 
    653   @compile {:inline, error: 1}
    654   defp error(error) do
    655     throw EncodeError.new(error)
    656   end
    657 end