dependabot-hex 0.107.13 → 0.107.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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