dependabot-hex 0.107.13 → 0.107.14

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