dependabot-hex 0.118.16 → 0.119.0.beta1

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