changed LICENSE
 
@@ -1,4 1,6 @@
1
- Copyright (C) 2021 Devin Alexander Torres <[email protected]>
1
BSD Zero Clause License
2
3
Copyright (C) 2024 Devin Alexander Torres <[email protected]>
2
4
3
5
Permission to use, copy, modify, and/or distribute this software for any
4
6
purpose with or without fee is hereby granted.
changed README.md
 
@@ -1,9 1,10 @@
1
1
# Poison
2
2
3
- [![Build Status](https://travis-ci.org/devinus/poison.svg?branch=master)](https://travis-ci.org/devinus/poison)
4
- [![Coverage Status](https://coveralls.io/repos/github/devinus/poison/badge.svg?branch=master)](https://coveralls.io/github/devinus/poison?branch=master)
5
- [![Hex.pm Version](https://img.shields.io/hexpm/v/poison.svg?style=flat-square)](https://hex.pm/packages/poison)
6
- [![Hex.pm Download Total](https://img.shields.io/hexpm/dt/poison.svg?style=flat-square)](https://hex.pm/packages/poison)
3
[![Build Status](https://img.shields.io/github/actions/workflow/status/devinus/poison/ci.yml)](https://github.com/devinus/poison/actions/workflows/ci.yml)
4
[![Coverage Status](https://img.shields.io/coverallsCoverage/github/devinus/poison)](https://coveralls.io/github/devinus/poison?branch=master))
5
[![Hex.pm Version](https://img.shields.io/hexpm/v/poison)](https://hex.pm/packages/poison)
6
[![Hex.pm Download Total](https://img.shields.io/hexpm/dt/poison)](https://hex.pm/packages/poison)
7
[![Hex.pm Dependents](https://img.shields.io/librariesio/dependents/hex/poison)](https://hex.pm/packages/poison)
7
8
8
9
Poison is a new JSON library for Elixir focusing on wicked-fast **speed**
9
10
without sacrificing **simplicity**, **completeness**, or **correctness**.
 
@@ -17,7 18,7 @@ several techniques that are [known to benefit BeamAsm][2] for JIT compilation,
17
18
Poison benchmarks sometimes puts Poison's performance close to `jiffy` and
18
19
usually faster than other Erlang/Elixir libraries.
19
20
20
- Poison fully conforms to [RFC 7159][4], [ECMA 404][5], and fully passes the
21
Poison fully conforms to [RFC 8259][4], [ECMA 404][5], and fully passes the
21
22
[JSONTestSuite][6].
22
23
23
24
## Installation
 
@@ -26,14 27,14 @@ First, add Poison to your `mix.exs` dependencies:
26
27
27
28
```elixir
28
29
def deps do
29
- [{:poison, "~> 5.0"}]
30
[{:poison, "~> 6.0"}]
30
31
end
31
32
```
32
33
33
34
Then, update your dependencies:
34
35
35
- ```sh-session
36
- $ mix deps.get
36
```sh
37
mix deps.get
37
38
```
38
39
39
40
## Usage
 
@@ -133,7 134,7 @@ ignored.
133
134
134
135
### Key Validation
135
136
136
- According to [RFC 7159][4] keys in a JSON object should be unique. This is
137
According to [RFC 8259][4] keys in a JSON object should be unique. This is
137
138
enforced and resolved in different ways in other libraries. In the Ruby JSON
138
139
library for example, the output generated from encoding a hash with a duplicate
139
140
key (say one is a string, the other an atom) will include both keys. When
 
@@ -154,27 155,27 @@ iex> Poison.encode!(%{:foo => "foo1", "foo" => "foo2"}, strict_keys: true)
154
155
155
156
## Benchmarking
156
157
157
- ```sh-session
158
- $ MIX_ENV=bench mix run bench/run.exs
158
```sh
159
MIX_ENV=bench mix run bench/run.exs
159
160
```
160
161
161
162
### Current Benchmarks
162
163
163
- As of 2020-06-25:
164
As of 2024-06-06:
164
165
165
- - Amazon EC2 c5.2xlarge instance running Ubuntu Server 20.04:
166
- https://gist.github.com/devinus/c82c2f6eaa22456e7ff0f5705466b1de
166
- Amazon EC2 c6i.2xlarge instance running Ubuntu Server 22.04:
167
<https://gist.github.com/devinus/afb351ae45194a6b93b6db9bf2d4c163>
167
168
168
169
## License
169
170
170
171
Poison is released under the [public-domain-equivalent][8] [0BSD][9] license.
171
172
172
- [1]: http://www.erlang.org/euc/07/papers/1700Gustafsson.pdf
173
[1]: https://erlang.org/euc/07/papers/1700Gustafsson.pdf
173
174
[2]: https://erlang.org/documentation/doc-12.0-rc1/erts-12.0/doc/html/BeamAsm.html
174
- [3]: http://jlouisramblings.blogspot.com/2013/07/problematic-traits-in-erlang.html
175
- [4]: https://tools.ietf.org/html/rfc7159
176
- [5]: http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
175
[3]: https://jlouisramblings.blogspot.com/2013/07/problematic-traits-in-erlang.html
176
[4]: https://datatracker.ietf.org/doc/html/rfc8259
177
[5]: https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf
177
178
[6]: https://github.com/nst/JSONTestSuite
178
- [7]: http://prog21.dadgum.com/70.html
179
[7]: https://prog21.dadgum.com/70.html
179
180
[8]: https://en.wikipedia.org/wiki/Public-domain-equivalent_license
180
181
[9]: https://opensource.org/licenses/0BSD
changed VERSION
 
@@ -1 1 @@
1
- 5.0.0
1
6.0.0
changed hex_metadata.config
 
@@ -1,18 1,18 @@
1
- {<<"app">>,<<"poison">>}.
2
- {<<"build_tools">>,[<<"mix">>]}.
3
- {<<"description">>,<<"An incredibly fast, pure Elixir JSON library">>}.
4
- {<<"elixir">>,<<"~> 1.11">>}.
5
- {<<"files">>,
6
- [<<"lib">>,<<"lib/poison.ex">>,<<"lib/poison">>,<<"lib/poison/encoder.ex">>,
7
- <<"lib/poison/decoder.ex">>,<<"lib/poison/parser.ex">>,<<"mix.exs">>,
8
- <<"README.md">>,<<"LICENSE">>,<<"VERSION">>]}.
9
- {<<"licenses">>,[<<"0BSD">>]}.
10
1
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/devinus/poison">>}]}.
11
2
{<<"name">>,<<"poison">>}.
3
{<<"version">>,<<"6.0.0">>}.
4
{<<"description">>,<<"An incredibly fast, pure Elixir JSON library">>}.
5
{<<"elixir">>,<<"~> 1.12">>}.
6
{<<"app">>,<<"poison">>}.
7
{<<"files">>,
8
[<<"lib">>,<<"lib/poison">>,<<"lib/poison/decoder.ex">>,
9
<<"lib/poison/encoder.ex">>,<<"lib/poison/parser.ex">>,<<"lib/poison.ex">>,
10
<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>,<<"VERSION">>]}.
11
{<<"licenses">>,[<<"0BSD">>]}.
12
12
{<<"requirements">>,
13
- [[{<<"app">>,<<"decimal">>},
14
- {<<"name">>,<<"decimal">>},
13
[[{<<"name">>,<<"decimal">>},
14
{<<"app">>,<<"decimal">>},
15
15
{<<"optional">>,true},
16
- {<<"repository">>,<<"hexpm">>},
17
- {<<"requirement">>,<<"~> 2.0">>}]]}.
18
- {<<"version">>,<<"5.0.0">>}.
16
{<<"requirement">>,<<"~> 2.1">>},
17
{<<"repository">>,<<"hexpm">>}]]}.
18
{<<"build_tools">>,[<<"mix">>]}.
changed lib/poison.ex
 
@@ -8,15 8,58 @@ defmodule Poison do
8
8
alias Poison.{EncodeError, Encoder}
9
9
alias Poison.{ParseError, Parser}
10
10
11
@doc """
12
Encode a value to JSON IO data.
13
14
iex> Poison.encode_to_iodata([1, 2, 3])
15
{:ok, [91, ["1", 44, "2", 44, "3"], 93]}
16
17
iex> Poison.encode_to_iodata({})
18
{:error, %Poison.EncodeError{message: nil, value: {}}}
19
"""
20
@spec encode_to_iodata(Encoder.t()) :: {:ok, iodata} | {:error, EncodeError.t()}
21
@spec encode_to_iodata(Encoder.t(), Encoder.options() | [Encoder.option()]) ::
22
{:ok, iodata} | {:error, EncodeError.t()}
23
def encode_to_iodata(value, options \\ %{}) do
24
{:ok, encode_to_iodata!(value, options)}
25
rescue
26
exception in [EncodeError] ->
27
{:error, exception}
28
end
29
30
@doc """
31
Encode a value to JSON IO data. Raises an exception on error.
32
33
iex> Poison.encode_to_iodata!([1, 2, 3])
34
[91, ["1", 44, "2", 44, "3"], 93]
35
36
iex> Poison.encode_to_iodata!({})
37
** (Poison.EncodeError) unable to encode value: {}
38
"""
39
@spec encode_to_iodata!(Encoder.t()) :: iodata
40
@spec encode_to_iodata!(Encoder.t(), Encoder.options() | [Encoder.option()]) :: iodata
41
def encode_to_iodata!(value, options \\ %{})
42
43
def encode_to_iodata!(value, options) when is_list(options) do
44
encode_to_iodata!(value, Map.new(options))
45
end
46
47
def encode_to_iodata!(value, options) do
48
Encoder.encode(value, options)
49
end
50
11
51
@doc """
12
52
Encode a value to JSON.
13
53
14
54
iex> Poison.encode([1, 2, 3])
15
55
{:ok, "[1,2,3]"}
56
57
iex> Poison.encode({})
58
{:error, %Poison.EncodeError{message: nil, value: {}}}
16
59
"""
17
- @spec encode(Encoder.t(), Encoder.options()) ::
18
- {:ok, iodata}
19
- | {:error, Exception.t()}
60
@spec encode(Encoder.t()) :: {:ok, iodata} | {:error, EncodeError.t()}
61
@spec encode(Encoder.t(), Encoder.options() | [Encoder.option()]) ::
62
{:ok, iodata} | {:error, EncodeError.t()}
20
63
def encode(value, options \\ %{}) do
21
64
{:ok, encode!(value, options)}
22
65
rescue
 
@@ -25,12 68,16 @@ defmodule Poison do
25
68
end
26
69
27
70
@doc """
28
- Encode a value to JSON, raises an exception on error.
71
Encode a value to JSON. Raises an exception on error.
29
72
30
73
iex> Poison.encode!([1, 2, 3])
31
74
"[1,2,3]"
75
76
iex> Poison.encode!({})
77
** (Poison.EncodeError) unable to encode value: {}
32
78
"""
33
- @spec encode!(Encoder.t(), Encoder.options()) :: iodata | no_return
79
@spec encode!(Encoder.t()) :: binary
80
@spec encode!(Encoder.t(), Encoder.options() | [Encoder.option()]) :: binary
34
81
def encode!(value, options \\ %{})
35
82
36
83
def encode!(value, options) when is_list(options) do
 
@@ -38,11 85,7 @@ defmodule Poison do
38
85
end
39
86
40
87
def encode!(value, options) do
41
- if options[:iodata] do
42
- Encoder.encode(value, options)
43
- else
44
- value |> Encoder.encode(options) |> IO.iodata_to_binary()
45
- end
88
value |> encode_to_iodata!(options) |> IO.iodata_to_binary()
46
89
end
47
90
48
91
@doc """
 
@@ -50,13 93,13 @@ defmodule Poison do
50
93
51
94
iex> Poison.decode("[1,2,3]")
52
95
{:ok, [1, 2, 3]}
96
97
iex> Poison.decode("[")
98
{:error, %Poison.ParseError{data: "[", skip: 1, value: nil}}
53
99
"""
54
- @spec decode(iodata) ::
55
- {:ok, Parser.t()}
56
- | {:error, Exception.t()}
57
- @spec decode(iodata, Decoder.options()) ::
58
- {:ok, Parser.t()}
59
- | {:error, Exception.t()}
100
@spec decode(iodata) :: {:ok, Parser.t()} | {:error, ParseError.t() | DecodeError.t()}
101
@spec decode(iodata, Decoder.options() | [Decoder.option()]) ::
102
{:ok, Decoder.t()} | {:error, ParseError.t() | DecodeError.t()}
60
103
def decode(iodata, options \\ %{}) do
61
104
{:ok, decode!(iodata, options)}
62
105
rescue
 
@@ -65,17 108,18 @@ defmodule Poison do
65
108
end
66
109
67
110
@doc """
68
- Decode JSON to a value, raises an exception on error.
111
Decode JSON to a value. Raises an exception on error.
69
112
70
113
iex> Poison.decode!("[1,2,3]")
71
114
[1, 2, 3]
72
- """
73
- @spec decode!(iodata) :: Parser.t() | no_return
74
- def decode!(value) do
75
- Parser.parse!(value, %{})
76
- end
77
115
78
- @spec decode!(iodata, Decoder.options()) :: Decoder.t() | no_return
116
iex> Poison.decode!("[")
117
** (Poison.ParseError) unexpected end of input at position 1
118
"""
119
@spec decode!(iodata) :: Parser.t()
120
@spec decode!(iodata, Decoder.options() | [Decoder.option()]) :: Decoder.t()
121
def decode!(value, options \\ %{})
122
79
123
def decode!(value, options) when is_list(options) do
80
124
decode!(value, Map.new(options))
81
125
end
 
@@ -84,7 128,6 @@ defmodule Poison do
84
128
value
85
129
|> Parser.parse!(options)
86
130
|> Decode.transform(options)
87
- |> Decoder.decode(options)
88
131
end
89
132
90
133
def decode!(value, options) do
changed lib/poison/decoder.ex
 
@@ -17,6 17,9 @@ defmodule Poison.Decode do
17
17
18
18
alias Poison.Decoder
19
19
20
@compile :inline
21
@compile :inline_list_funcs
22
20
23
def transform(value, options) when is_map(value) or is_list(value) do
21
24
case Map.get(options, :as) do
22
25
nil -> value
 
@@ -42,6 45,10 @@ defmodule Poison.Decode do
42
45
for v <- value, do: transform(v, keys, as, options)
43
46
end
44
47
48
defp transform(value, keys, as, options) when is_function(as, 1) do
49
transform(value, keys, as.(value), options)
50
end
51
45
52
defp transform(value, _keys, _as, _options) do
46
53
value
47
54
end
 
@@ -106,11 113,14 @@ end
106
113
defprotocol Poison.Decoder do
107
114
@fallback_to_any true
108
115
109
- @typep as :: map | struct | [as]
116
@type keys :: :atoms | :atoms!
117
@type decimal :: boolean
118
@type as :: map | struct | [as] | (t -> as)
110
119
120
@type option :: {:keys, keys} | {:decimal, decimal} | {:as, as}
111
121
@type options :: %{
112
- optional(:keys) => :atoms | :atoms!,
113
- optional(:decimal) => boolean,
122
optional(:keys) => keys,
123
optional(:decimal) => decimal,
114
124
optional(:as) => as
115
125
}
changed lib/poison/encoder.ex
 
@@ -80,11 80,18 @@ end
80
80
defprotocol Poison.Encoder do
81
81
@fallback_to_any true
82
82
83
- @typep escape :: :unicode | :javascript | :html_safe
84
- @typep pretty :: boolean
85
- @typep indent :: non_neg_integer
86
- @typep offset :: non_neg_integer
87
- @typep strict_keys :: boolean
83
@type escape :: :unicode | :javascript | :html_safe
84
@type pretty :: boolean
85
@type indent :: non_neg_integer
86
@type offset :: non_neg_integer
87
@type strict_keys :: boolean
88
89
@type option ::
90
{:escape, escape}
91
| {:pretty, pretty}
92
| {:indent, indent}
93
| {:offset, offset}
94
| {:strict_keys, strict_keys}
88
95
89
96
@type options :: %{
90
97
optional(:escape) => escape,
 
@@ -109,7 116,7 @@ defimpl Poison.Encoder, for: Atom do
109
116
end
110
117
111
118
defimpl Poison.Encoder, for: BitString do
112
- use Bitwise
119
import Bitwise
113
120
114
121
@compile :inline
115
122
@compile :inline_list_funcs
 
@@ -130,7 137,7 @@ defimpl Poison.Encoder, for: BitString do
130
137
end
131
138
end
132
139
133
- # http://en.wikipedia.org/wiki/Unicode_control_characters
140
# https://en.wikipedia.org/wiki/Unicode_control_characters
134
141
defp escape(<<char, rest::bits>>, mode) when char <= 0x1F or char == 0x7F do
135
142
[seq(char) | escape(rest, mode)]
136
143
end
 
@@ -147,8 154,8 @@ defimpl Poison.Encoder, for: BitString do
147
154
[seq(char) | escape(rest, :unicode)]
148
155
end
149
156
150
- # http://en.wikipedia.org/wiki/UTF-16#Example_UTF-16_encoding_procedure
151
- # http://unicodebook.readthedocs.org/unicode_encodings.html
157
# https://en.wikipedia.org/wiki/UTF-16#U D800_to_U DFFF_(surrogates)
158
# https://unicodebook.readthedocs.io/unicode_encodings.html
152
159
defp escape(<<char::utf8, rest::bits>>, :unicode) when char > 0xFFFF do
153
160
code = char - 0x10000
154
161
 
@@ -159,13 166,28 @@ defimpl Poison.Encoder, for: BitString do
159
166
]
160
167
end
161
168
162
- defp escape(<<char::utf8, rest::bits>>, mode)
163
- when mode in [:html_safe, :javascript] and char in [0x2028, 0x2029] do
164
- [seq(char) | escape(rest, mode)]
169
# https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
170
# https://rubydoc.info/docs/rails/ActiveSupport/JSON/Encoding.escape_html_entities_in_json=
171
defp escape(<<?&::utf8, rest::bits>>, :html_safe) do
172
["\\u0026" | escape(rest, :html_safe)]
165
173
end
166
174
167
- defp escape(<<?/::utf8, rest::bits>>, :html_safe) do
168
- ["\\/" | escape(rest, :html_safe)]
175
defp escape(<<?<::utf8, rest::bits>>, :html_safe) do
176
["\\u003C" | escape(rest, :html_safe)]
177
end
178
179
defp escape(<<?>::utf8, rest::bits>>, :html_safe) do
180
["\\u003E" | escape(rest, :html_safe)]
181
end
182
183
defp escape(<<"\u2028"::utf8, rest::bits>>, mode)
184
when mode in [:html_safe, :javascript] do
185
["\\u2028" | escape(rest, mode)]
186
end
187
188
defp escape(<<"\u2029"::utf8, rest::bits>>, mode)
189
when mode in [:html_safe, :javascript] do
190
["\\u2029" | escape(rest, mode)]
169
191
end
170
192
171
193
defp escape(string, mode) do
 
@@ -177,11 199,11 @@ defimpl Poison.Encoder, for: BitString do
177
199
@compile {:inline, chunk_size: 3}
178
200
179
201
defp chunk_size(<<char, _rest::bits>>, _mode, acc)
180
- when char <= 0x1F or char in '"\\' do
202
when char <= 0x1F or char in ~c("\\) do
181
203
acc
182
204
end
183
205
184
- defp chunk_size(<<?/, _rest::bits>>, :html_safe, acc) do
206
defp chunk_size(<<char, _rest::bits>>, :html_safe, acc) when char in ~c(&<>) do
185
207
acc
186
208
end
187
209
 
@@ -189,7 211,7 @@ defimpl Poison.Encoder, for: BitString do
189
211
chunk_size(rest, mode, acc 1)
190
212
end
191
213
192
- defp chunk_size(<<_::utf8, _rest::bits>>, :unicode, acc) do
214
defp chunk_size(<<_codepoint::utf8, _rest::bits>>, :unicode, acc) do
193
215
acc
194
216
end
195
217
 
@@ -199,7 221,7 @@ defimpl Poison.Encoder, for: BitString do
199
221
end
200
222
201
223
defp chunk_size(<<codepoint::utf8, rest::bits>>, mode, acc) do
202
- chunk_size(rest, mode, acc byte_size(<<codepoint::utf8>>))
224
chunk_size(rest, mode, acc codepoint_size(codepoint))
203
225
end
204
226
205
227
defp chunk_size(<<>>, _mode, acc), do: acc
 
@@ -218,6 240,16 @@ defimpl Poison.Encoder, for: BitString do
218
240
s -> ["\\u" | s]
219
241
end
220
242
end
243
244
@compile {:inline, codepoint_size: 1}
245
246
defp codepoint_size(codepoint) do
247
cond do
248
codepoint <= 0x7FF -> 2
249
codepoint <= 0xFFFF -> 3
250
true -> 4
251
end
252
end
221
253
end
222
254
223
255
defimpl Poison.Encoder, for: Integer do
 
@@ -240,7 272,7 @@ defimpl Poison.Encoder, for: Map do
240
272
@compile :inline
241
273
@compile :inline_list_funcs
242
274
243
- def encode(map, _) when map_size(map) < 1, do: "{}"
275
def encode(map, _options) when map_size(map) < 1, do: "{}"
244
276
245
277
def encode(map, options) do
246
278
map
 
@@ -347,7 379,7 @@ defimpl Poison.Encoder, for: List do
347
379
end
348
380
end
349
381
350
- defimpl Poison.Encoder, for: [Range, Stream, MapSet, HashSet] do
382
defimpl Poison.Encoder, for: [Range, Stream, MapSet, Date.Range] do
351
383
alias Poison.{Encoder, Pretty}
352
384
353
385
use Pretty
changed lib/poison/parser.ex
 
@@ -30,7 30,7 @@ defmodule Poison.ParseError do
30
30
<<>> ->
31
31
"unexpected end of input at position #{pos}"
32
32
33
- <<token::utf8, _::bits>> ->
33
<<token::utf8, _rest::bits>> ->
34
34
"unexpected token at position #{pos}: #{escape(token)}"
35
35
36
36
_rest ->
 
@@ -50,31 50,28 @@ end
50
50
51
51
defmodule Poison.Parser do
52
52
@moduledoc """
53
- An RFC 7159 and ECMA 404 conforming JSON parser.
53
An RFC 8259 and ECMA 404 conforming JSON parser.
54
54
55
- See: https://tools.ietf.org/html/rfc7159
56
- See: http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf
55
See: https://datatracker.ietf.org/doc/html/rfc8259
56
See: https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf
57
57
"""
58
58
59
59
@compile :inline
60
60
@compile :inline_list_funcs
61
- @compile {:inline_effort, 2500}
62
- @compile {:inline_size, 150}
63
- @compile {:inline_unroll, 3}
64
61
65
- use Bitwise
62
import Bitwise
66
63
67
64
alias Poison.{Decoder, ParseError}
68
65
69
- @typep value :: nil | true | false | map | list | float | integer | String.t()
66
@type scalar :: nil | true | false | float | integer | String.t()
70
67
71
68
if Code.ensure_loaded?(Decimal) do
72
- @type t :: value | Decimal.t()
69
@type t :: scalar | Decimal.t() | [t] | %{optional(String.t()) => t}
73
70
else
74
- @type t :: value
71
@type t :: scalar | [t] | %{optional(String.t()) => t}
75
72
end
76
73
77
- whitespace = '\s\t\n\r'
74
whitespace = ~c"\s\t\n\r"
78
75
digits = ?0..?9
79
76
80
77
defmacrop syntax_error(skip) do
 
@@ -83,14 80,14 @@ defmodule Poison.Parser do
83
80
end
84
81
end
85
82
86
- @spec parse!(iodata | binary, Decoder.options()) :: t | no_return
83
@spec parse!(iodata, Decoder.options()) :: t
87
84
def parse!(value, options \\ %{})
88
85
89
86
def parse!(data, options) when is_bitstring(data) do
90
87
[value | skip] =
91
88
value(data, data, :maps.get(:keys, options, nil), :maps.get(:decimal, options, nil), 0)
92
89
93
- <<_::binary-size(skip), rest::bits>> = data
90
<<_skip::binary-size(skip), rest::bits>> = data
94
91
skip_whitespace(rest, skip, value)
95
92
rescue
96
93
exception in ParseError ->
 
@@ -105,25 102,16 @@ defmodule Poison.Parser do
105
102
106
103
@compile {:inline, value: 5}
107
104
108
- defp value(<<?f, rest::bits>>, _data, _keys, _decimal, skip) do
109
- case rest do
110
- <<"alse", _rest::bits>> -> [false | skip 5]
111
- _other -> syntax_error(skip)
112
- end
105
defp value(<<"null", _rest::bits>>, _data, _keys, _decimal, skip) do
106
[nil | skip 4]
113
107
end
114
108
115
- defp value(<<?t, rest::bits>>, _data, _keys, _decimal, skip) do
116
- case rest do
117
- <<"rue", _rest::bits>> -> [true | skip 4]
118
- _other -> syntax_error(skip)
119
- end
109
defp value(<<"true", _rest::bits>>, _data, _keys, _decimal, skip) do
110
[true | skip 4]
120
111
end
121
112
122
- defp value(<<?n, rest::bits>>, _data, _keys, _decimal, skip) do
123
- case rest do
124
- <<"ull", _rest::bits>> -> [nil | skip 4]
125
- _other -> syntax_error(skip)
126
- end
113
defp value(<<"false", _rest::bits>>, _data, _keys, _decimal, skip) do
114
[false | skip 5]
127
115
end
128
116
129
117
defp value(<<?-, rest::bits>>, _data, _keys, decimal, skip) do
 
@@ -192,10 180,10 @@ defmodule Poison.Parser do
192
180
defp object_pairs(<<?", rest::bits>>, data, keys, decimal, skip, acc) do
193
181
start = skip 1
194
182
[name | skip] = string_continue(rest, data, start)
195
- <<_::binary-size(skip), rest::bits>> = data
183
<<_skip::binary-size(skip), rest::bits>> = data
196
184
197
185
[value | skip] = object_value(rest, data, keys, decimal, skip)
198
- <<_::binary-size(skip), rest::bits>> = data
186
<<_skip::binary-size(skip), rest::bits>> = data
199
187
200
188
object_pairs_continue(rest, data, keys, decimal, skip, [
201
189
{object_name(keys, start, name), value} | acc
 
@@ -256,8 244,8 @@ defmodule Poison.Parser do
256
244
257
245
@compile {:inline, array_values: 6}
258
246
259
- defp array_values(<<?], _rest::bits>>, _data, _keys, _decimal, skip, _acc) do
260
- [[] | skip 1]
247
defp array_values(<<?], _rest::bits>>, _data, _keys, _decimal, skip, acc) do
248
[acc | skip 1]
261
249
end
262
250
263
251
for char <- whitespace do
 
@@ -268,7 256,7 @@ defmodule Poison.Parser do
268
256
269
257
defp array_values(rest, data, keys, decimal, skip, acc) do
270
258
[value | skip] = value(rest, data, keys, decimal, skip)
271
- <<_::binary-size(skip), rest::bits>> = data
259
<<_skip::binary-size(skip), rest::bits>> = data
272
260
array_values_continue(rest, data, keys, decimal, skip, [value | acc])
273
261
end
274
262
 
@@ -276,7 264,7 @@ defmodule Poison.Parser do
276
264
277
265
defp array_values_continue(<<?,, rest::bits>>, data, keys, decimal, skip, acc) do
278
266
[value | skip] = value(rest, data, keys, decimal, skip 1)
279
- <<_::binary-size(skip), rest::bits>> = data
267
<<_skip::binary-size(skip), rest::bits>> = data
280
268
array_values_continue(rest, data, keys, decimal, skip, [value | acc])
281
269
end
282
270
 
@@ -352,7 340,7 @@ defmodule Poison.Parser do
352
340
353
341
@compile {:inline, number_exp: 6}
354
342
355
- for e <- 'eE' do
343
for e <- ~c(eE) do
356
344
defp number_exp(<<unquote(e), rest::bits>>, decimal, skip, sign, coef, exp) do
357
345
[value | skip] = number_exp_continue(rest, skip 1)
358
346
number_complete(decimal, skip, sign, coef, exp value)
 
@@ -424,7 412,7 @@ defmodule Poison.Parser do
424
412
425
413
# See: https://arxiv.org/pdf/2101.11408.pdf
426
414
defp number_complete(_decimal, skip, sign, coef, exp)
427
- when exp in -10..10 and coef <= unquote(max_sig) do
415
when exp in -22..22 and coef <= unquote(max_sig) do
428
416
if exp < 0 do
429
417
[coef / pow10(-exp) * sign | skip]
430
418
else
 
@@ -440,7 428,7 @@ defmodule Poison.Parser do
440
428
| skip
441
429
]
442
430
rescue
443
- ArithmeticError ->
431
ArgumentError ->
444
432
reraise ParseError, [skip: skip, value: "#{coef * sign}e#{exp}"], __STACKTRACE__
445
433
end
446
434
 
@@ -486,10 474,13 @@ defmodule Poison.Parser do
486
474
end
487
475
488
476
unicode ->
489
- [
490
- :unicode.characters_to_binary([acc | binary_part(data, skip, len)], :utf8)
491
- | skip len 1
492
- ]
477
case :unicode.characters_to_binary([acc | binary_part(data, skip, len)], :utf8) do
478
string when is_binary(string) ->
479
[string | skip len 1]
480
481
_other ->
482
syntax_error(skip len)
483
end
493
484
494
485
true ->
495
486
[IO.iodata_to_binary([acc | binary_part(data, skip, len)]) | skip len 1]
 
@@ -515,29 506,23 @@ defmodule Poison.Parser do
515
506
516
507
@compile {:inline, string_escape: 5}
517
508
518
- for {seq, char} <- Enum.zip(~C("\ntr/fb), ~c("\\\n\t\r/\f\b)) do
519
- defp string_escape(<<unquote(seq), rest::bits>>, data, skip, unicode, acc) do
520
- string_continue(rest, data, skip 1, unicode, 0, [acc | unquote(<<char>>)])
521
- end
509
defp string_escape(<<?u, rest::bits>>, data, skip, _unicode, acc) do
510
string_escape_unicode(rest, data, skip, acc)
522
511
end
523
512
524
- defp string_escape(
525
- <<?u, seq1::binary-size(4), rest::bits>>,
526
- data,
527
- skip,
528
- _unicode,
529
- acc
530
- ) do
531
- string_escape_unicode(rest, data, skip, acc, seq1)
513
for {seq, char} <- Enum.zip(~C("\ntr/fb), ~c("\\\n\t\r/\f\b)) do
514
defp string_escape(<<unquote(seq), rest::bits>>, data, skip, unicode, acc) do
515
string_continue(rest, data, skip 1, unicode, 0, [acc | [unquote(char)]])
516
end
532
517
end
533
518
534
519
defp string_escape(_rest, _data, skip, _unicode, _acc), do: syntax_error(skip)
535
520
536
- # http://www.ietf.org/rfc/rfc2781.txt
537
- # http://perldoc.perl.org/Encode/Unicode.html#Surrogate-Pairs
538
- # http://mathiasbynens.be/notes/javascript-encoding#surrogate-pairs
539
- defguardp is_surrogate(cp) when cp in 0xD800..0xDFFF
540
- defguardp is_surrogate_pair(hi, lo) when hi in 0xD800..0xDBFF and lo in 0xDC00..0xDFFF
521
# https://www.ietf.org/rfc/rfc2781.txt
522
# https://perldoc.perl.org/Encode::Unicode#Surrogate-Pairs
523
# https://mathiasbynens.be/notes/javascript-encoding#surrogate-pairs
524
defguardp is_hi_surrogate(cp) when cp in 0xD800..0xDBFF
525
defguardp is_lo_surrogate(cp) when cp in 0xDC00..0xDFFF
541
526
542
527
defmacrop get_codepoint(seq, skip) do
543
528
quote bind_quoted: [seq: seq, skip: skip] do
 
@@ -550,30 535,45 @@ defmodule Poison.Parser do
550
535
end
551
536
end
552
537
553
- @compile {:inline, string_escape_unicode: 5}
538
@compile {:inline, string_escape_unicode: 4}
554
539
555
- defp string_escape_unicode(<<"\\u", seq2::binary-size(4), rest::bits>>, data, skip, acc, seq1) do
556
- hi = get_codepoint(seq1, skip)
557
- lo = get_codepoint(seq2, skip 6)
540
defp string_escape_unicode(<<seq1::binary-size(4), rest::bits>>, data, skip, acc) do
541
case get_codepoint(seq1, skip) do
542
hi when is_hi_surrogate(hi) ->
543
string_escape_surrogate_pair(rest, data, skip, acc, seq1, hi)
558
544
559
- cond do
560
- is_surrogate_pair(hi, lo) ->
561
- codepoint = 0x10000 ((hi &&& 0x03FF) <<< 10) (lo &&& 0x03FF)
562
- string_continue(rest, data, skip 11, true, 0, [acc, codepoint])
545
lo when is_lo_surrogate(lo) ->
546
raise ParseError, skip: skip, value: "\\u#{seq1}"
563
547
564
- is_surrogate(hi) ->
565
- raise ParseError, skip: skip, value: "\\u#{seq1}\\u#{seq2}"
566
-
567
- is_surrogate(lo) ->
568
- raise ParseError, skip: skip 6, value: "\\u#{seq2}"
569
-
570
- true ->
571
- string_continue(rest, data, skip 11, true, 0, [acc, hi, lo])
548
codepoint ->
549
string_continue(rest, data, skip 5, true, 0, [acc | [codepoint]])
572
550
end
573
551
end
574
552
575
- defp string_escape_unicode(rest, data, skip, acc, seq1) do
576
- string_continue(rest, data, skip 5, true, 0, [acc, get_codepoint(seq1, skip)])
553
defp string_escape_unicode(_rest, _data, skip, _acc), do: syntax_error(skip 1)
554
555
@compile {:inline, string_escape_surrogate_pair: 6}
556
557
defp string_escape_surrogate_pair(
558
<<"\\u", seq2::binary-size(4), rest::bits>>,
559
data,
560
skip,
561
acc,
562
seq1,
563
hi
564
) do
565
case get_codepoint(seq2, skip 6) do
566
lo when is_lo_surrogate(lo) ->
567
codepoint = 0x10000 ((hi &&& 0x03FF) <<< 10) (lo &&& 0x03FF)
568
string_continue(rest, data, skip 11, true, 0, [acc | [codepoint]])
569
570
_other ->
571
raise ParseError, skip: skip, value: "\\u#{seq1}\\u#{seq2}"
572
end
573
end
574
575
defp string_escape_surrogate_pair(_rest, _data, skip, _acc, seq1, _hi) do
576
raise ParseError, skip: skip, value: "\\u#{seq1}"
577
577
end
578
578
579
579
## Whitespace
changed mix.exs
 
@@ -11,7 11,7 @@ defmodule Poison.Mixfile do
11
11
app: :poison,
12
12
name: "Poison",
13
13
version: @version,
14
- elixir: "~> 1.11",
14
elixir: "~> 1.12",
15
15
description: "An incredibly fast, pure Elixir JSON library",
16
16
source_url: "https://github.com/devinus/poison",
17
17
start_permanent: Mix.env() == :prod,
 
@@ -23,13 23,13 @@ defmodule Poison.Mixfile do
23
23
aliases: aliases(),
24
24
xref: [exclude: [Decimal]],
25
25
dialyzer: [
26
- ignore_warnings: ".dialyzer_ignore.exs",
27
26
plt_add_apps: [:decimal],
28
27
flags: [
29
28
:error_handling,
30
- :race_conditions,
31
- :underspecs,
32
- :unmatched_returns
29
:extra_return,
30
:missing_return,
31
:unmatched_returns,
32
:underspecs
33
33
]
34
34
],
35
35
test_coverage: [tool: ExCoveralls],
 
@@ -38,7 38,7 @@ defmodule Poison.Mixfile do
38
38
"coveralls.detail": :test,
39
39
"coveralls.html": :test,
40
40
"coveralls.post": :test,
41
- "coveralls.travis": :test
41
"coveralls.github": :test
42
42
]
43
43
]
44
44
end
 
@@ -46,18 46,16 @@ defmodule Poison.Mixfile do
46
46
# Run "mix help compile.app" to learn about applications.
47
47
def application do
48
48
# Specify extra applications you'll use from Erlang/Elixir
49
- spec = [extra_applications: []]
50
-
51
- if Mix.env() != :bench do
52
- spec
49
if Mix.env() == :bench do
50
[extra_applications: [:eex]]
53
51
else
54
- Keyword.put_new(spec, :applications, [:logger])
52
[]
55
53
end
56
54
end
57
55
58
- defp elixirc_paths() do
56
defp elixirc_paths do
59
57
if Mix.env() == :profile do
60
- ["lib", "profile"]
58
~w(lib profile)
61
59
else
62
60
["lib"]
63
61
end
 
@@ -67,19 65,22 @@ defmodule Poison.Mixfile do
67
65
defp deps do
68
66
[
69
67
{:benchee_html, "~> 1.0", only: :bench, runtime: false},
70
- {:benchee, "~> 1.0", only: :bench, runtime: false},
71
- {:credo, "~> 1.5", only: [:dev, :test], runtime: false},
72
- {:decimal, "~> 2.0", optional: true},
73
- {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false},
74
- {:ex_doc, "~> 0.25", only: [:dev, :test], runtime: false},
75
- {:excoveralls, "~> 0.14", only: :test, runtime: false},
68
{:benchee_markdown, "~> 0.3", only: :bench, runtime: false},
69
{:benchee, "~> 1.3", only: :bench, runtime: false},
70
{:castore, "~> 1.0", only: :test, runtime: false},
71
{:credo, "~> 1.7.7-rc", only: [:dev, :test], runtime: false},
72
{:decimal, "~> 2.1", optional: true},
73
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
74
{:ex_doc, "~> 0.34", only: [:dev, :test], runtime: false},
75
{:excoveralls, "~> 0.18", only: :test, runtime: false},
76
76
{:exjsx, "~> 4.0", only: [:bench, :profile], runtime: false},
77
- {:jason, "~> 1.2", only: [:dev, :test, :bench, :profile], runtime: false},
78
- {:jiffy, "~> 1.0", only: [:bench, :profile], runtime: false},
79
- {:json, "~> 1.4", only: [:bench, :profile], runtime: false},
80
- {:jsone, "~> 1.6", only: [:bench, :profile], runtime: false},
81
- {:junit_formatter, "~> 3.3", only: :test, runtime: false},
82
- {:stream_data, "~> 0.5", only: [:dev, :test], runtime: false},
77
{:jason, "~> 1.5.0-alpha", only: [:dev, :test, :bench, :profile], runtime: false},
78
{:jiffy, "~> 1.1", only: [:bench, :profile], runtime: false},
79
{:jsone, "~> 1.8", only: [:bench, :profile], runtime: false},
80
{:junit_formatter, "~> 3.4", only: :test, runtime: false},
81
{:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false},
82
{:stream_data, "~> 1.1", only: [:dev, :test], runtime: false},
83
{:thoas, "~> 1.2", only: [:bench, :profile], runtime: false},
83
84
{:tiny, "~> 1.0", only: [:bench, :profile], runtime: false}
84
85
]
85
86
end
 
@@ -88,7 89,12 @@ defmodule Poison.Mixfile do
88
89
[
89
90
main: "Poison",
90
91
canonical: "https://hexdocs.pm/poison",
91
- extras: ["README.md"]
92
extras: [
93
"README.md",
94
"CHANGELOG.md": [title: "Changelog"],
95
LICENSE: [title: "License"]
96
],
97
source_ref: "master"
92
98
]
93
99
end
94
100
 
@@ -104,8 110,12 @@ defmodule Poison.Mixfile do
104
110
defp aliases do
105
111
[
106
112
"deps.get": [
107
- fn _ ->
108
- System.cmd("git", ["submodule", "update", "--init"], cd: __DIR__, parallelism: true)
113
fn _args ->
114
System.cmd("git", ["submodule", "update", "--init"],
115
cd: __DIR__,
116
env: [],
117
parallelism: true
118
)
109
119
end,
110
120
"deps.get"
111
121
]