json-extended 2.20.0
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.
- checksums.yaml +7 -0
- data/BSDL +22 -0
- data/CHANGES.md +798 -0
- data/COPYING +56 -0
- data/LEGAL +20 -0
- data/README.md +308 -0
- data/ext/json/ext/fbuffer/fbuffer.h +259 -0
- data/ext/json/ext/generator/extconf.rb +19 -0
- data/ext/json/ext/generator/generator.c +1997 -0
- data/ext/json/ext/json.h +179 -0
- data/ext/json/ext/parser/extconf.rb +53 -0
- data/ext/json/ext/parser/parser.c +2827 -0
- data/ext/json/ext/simd/conf.rb +24 -0
- data/ext/json/ext/simd/simd.h +208 -0
- data/ext/json/ext/vendor/fast_float_parser.h +814 -0
- data/ext/json/ext/vendor/fpconv.c +480 -0
- data/ext/json/ext/vendor/jeaiii-ltoa.h +267 -0
- data/json.gemspec +62 -0
- data/lib/json/add/bigdecimal.rb +58 -0
- data/lib/json/add/complex.rb +51 -0
- data/lib/json/add/core.rb +13 -0
- data/lib/json/add/date.rb +54 -0
- data/lib/json/add/date_time.rb +67 -0
- data/lib/json/add/exception.rb +49 -0
- data/lib/json/add/ostruct.rb +54 -0
- data/lib/json/add/range.rb +54 -0
- data/lib/json/add/rational.rb +49 -0
- data/lib/json/add/regexp.rb +48 -0
- data/lib/json/add/set.rb +48 -0
- data/lib/json/add/string.rb +35 -0
- data/lib/json/add/struct.rb +52 -0
- data/lib/json/add/symbol.rb +52 -0
- data/lib/json/add/time.rb +52 -0
- data/lib/json/common.rb +1173 -0
- data/lib/json/ext/generator/state.rb +103 -0
- data/lib/json/ext.rb +45 -0
- data/lib/json/generic_object.rb +67 -0
- data/lib/json/truffle_ruby/generator.rb +755 -0
- data/lib/json/version.rb +5 -0
- data/lib/json.rb +689 -0
- metadata +90 -0
data/COPYING
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
|
|
2
|
+
You can redistribute it and/or modify it under either the terms of the
|
|
3
|
+
2-clause BSDL (see the file BSDL), or the conditions below:
|
|
4
|
+
|
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
|
6
|
+
software without restriction, provided that you duplicate all of the
|
|
7
|
+
original copyright notices and associated disclaimers.
|
|
8
|
+
|
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
|
10
|
+
you do at least ONE of the following:
|
|
11
|
+
|
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
|
13
|
+
make them Freely Available, such as by posting said
|
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
|
15
|
+
the author to include your modifications in the software.
|
|
16
|
+
|
|
17
|
+
b) use the modified software only within your corporation or
|
|
18
|
+
organization.
|
|
19
|
+
|
|
20
|
+
c) give non-standard binaries non-standard names, with
|
|
21
|
+
instructions on where to get the original software distribution.
|
|
22
|
+
|
|
23
|
+
d) make other distribution arrangements with the author.
|
|
24
|
+
|
|
25
|
+
3. You may distribute the software in object code or binary form,
|
|
26
|
+
provided that you do at least ONE of the following:
|
|
27
|
+
|
|
28
|
+
a) distribute the binaries and library files of the software,
|
|
29
|
+
together with instructions (in the manual page or equivalent)
|
|
30
|
+
on where to get the original distribution.
|
|
31
|
+
|
|
32
|
+
b) accompany the distribution with the machine-readable source of
|
|
33
|
+
the software.
|
|
34
|
+
|
|
35
|
+
c) give non-standard binaries non-standard names, with
|
|
36
|
+
instructions on where to get the original software distribution.
|
|
37
|
+
|
|
38
|
+
d) make other distribution arrangements with the author.
|
|
39
|
+
|
|
40
|
+
4. You may modify and include the part of the software into any other
|
|
41
|
+
software (possibly commercial). But some files in the distribution
|
|
42
|
+
are not written by the author, so that they are not under these terms.
|
|
43
|
+
|
|
44
|
+
For the list of those files and their copying conditions, see the
|
|
45
|
+
file LEGAL.
|
|
46
|
+
|
|
47
|
+
5. The scripts and library files supplied as input to or produced as
|
|
48
|
+
output from the software do not automatically fall under the
|
|
49
|
+
copyright of the software, but belong to whomever generated them,
|
|
50
|
+
and may be sold commercially, and may be aggregated with this
|
|
51
|
+
software.
|
|
52
|
+
|
|
53
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
|
54
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
|
55
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
56
|
+
PURPOSE.
|
data/LEGAL
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- rdoc -*-
|
|
2
|
+
|
|
3
|
+
= LEGAL NOTICE INFORMATION
|
|
4
|
+
--------------------------
|
|
5
|
+
|
|
6
|
+
All the files in this distribution are covered under either the Ruby's
|
|
7
|
+
license (see the file COPYING) or public-domain except some files
|
|
8
|
+
mentioned below.
|
|
9
|
+
|
|
10
|
+
ext/json/ext/vendor/fpconv.h::
|
|
11
|
+
This file is adapted from https://github.com/night-shift/fpconv
|
|
12
|
+
It is licensed under Boost Software License 1.0.
|
|
13
|
+
|
|
14
|
+
ext/json/ext/vendor/jeaiii-ltoa.h::
|
|
15
|
+
This file is adapted from https://github.com/jeaiii/itoa
|
|
16
|
+
It is licensed under the MIT License
|
|
17
|
+
|
|
18
|
+
ext/json/ext/vendor/fast_float_parser.h::
|
|
19
|
+
This file is adapted from the Fast Float C++ library by The fast_float authors https://github.com/fastfloat/fast_float
|
|
20
|
+
It is licensed under the MIT License
|
data/README.md
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# JSON implementation for Ruby
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ruby/json/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
This is an implementation of the JSON specification according to RFC 7159
|
|
8
|
+
http://www.ietf.org/rfc/rfc7159.txt .
|
|
9
|
+
|
|
10
|
+
The JSON generator generate UTF-8 character sequences by default.
|
|
11
|
+
If an :ascii\_only option with a true value is given, they escape all
|
|
12
|
+
non-ASCII and control characters with \uXXXX escape sequences, and support
|
|
13
|
+
UTF-16 surrogate pairs in order to be able to generate the whole range of
|
|
14
|
+
unicode code points.
|
|
15
|
+
|
|
16
|
+
All strings, that are to be encoded as JSON strings, should be UTF-8 byte
|
|
17
|
+
sequences on the Ruby side. To encode raw binary strings, that aren't UTF-8
|
|
18
|
+
encoded, please use the to\_json\_raw\_object method of String (which produces
|
|
19
|
+
an object, that contains a byte array) and decode the result on the receiving
|
|
20
|
+
endpoint.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
25
|
+
|
|
26
|
+
$ bundle add json
|
|
27
|
+
|
|
28
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
29
|
+
|
|
30
|
+
$ gem install json
|
|
31
|
+
|
|
32
|
+
## Basic Usage
|
|
33
|
+
|
|
34
|
+
To use JSON you can
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
require 'json'
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Now you can parse a JSON document into a ruby data structure by calling
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
JSON.parse(document)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
If you want to generate a JSON document from a ruby data structure call
|
|
47
|
+
```ruby
|
|
48
|
+
JSON.generate(data)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
You can also use the `pretty_generate` method (which formats the output more
|
|
52
|
+
verbosely and nicely) or `fast_generate` (which doesn't do any of the security
|
|
53
|
+
checks generate performs, e. g. nesting deepness checks).
|
|
54
|
+
|
|
55
|
+
## Casting non native types
|
|
56
|
+
|
|
57
|
+
JSON documents can only support Hashes, Arrays, Strings, Integers and Floats.
|
|
58
|
+
|
|
59
|
+
By default if you attempt to serialize something else, `JSON.generate` will
|
|
60
|
+
search for a `#to_json` method on that object:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
Position = Struct.new(:latitude, :longitude) do
|
|
64
|
+
def to_json(state = nil, *)
|
|
65
|
+
JSON::State.from_state(state).generate({
|
|
66
|
+
latitude: latitude,
|
|
67
|
+
longitude: longitude,
|
|
68
|
+
})
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
JSON.generate([
|
|
73
|
+
Position.new(12323.234, 435345.233),
|
|
74
|
+
Position.new(23434.676, 159435.324),
|
|
75
|
+
]) # => [{"latitude":12323.234,"longitude":435345.233},{"latitude":23434.676,"longitude":159435.324}]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If a `#to_json` method isn't defined on the object, `JSON.generate` will fallback to call `#to_s`:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
JSON.generate(Object.new) # => "#<Object:0x000000011e768b98>"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Both of these behavior can be disabled using the `strict: true` option:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
JSON.generate(Object.new, strict: true) # => Object not allowed in JSON (JSON::GeneratorError)
|
|
88
|
+
JSON.generate(Position.new(1, 2)) # => Position not allowed in JSON (JSON::GeneratorError)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## JSON::Coder
|
|
92
|
+
|
|
93
|
+
Since `#to_json` methods are global, it can sometimes be problematic if you need a given type to be
|
|
94
|
+
serialized in different ways in different locations.
|
|
95
|
+
|
|
96
|
+
Instead it is recommended to use the newer `JSON::Coder` API:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
module MyApp
|
|
100
|
+
API_JSON_CODER = JSON::Coder.new do |object, is_object_key|
|
|
101
|
+
case object
|
|
102
|
+
when Time
|
|
103
|
+
object.iso8601(3)
|
|
104
|
+
else
|
|
105
|
+
object
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The provided block is called for all objects that don't have a native JSON equivalent, and
|
|
114
|
+
must return a Ruby object that has a native JSON equivalent.
|
|
115
|
+
|
|
116
|
+
It is also called for objects that do have a JSON equivalent, but are used as Hash keys, for instance `{ 1 => 2}`,
|
|
117
|
+
as well as for strings that aren't valid UTF-8:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
coder = JSON::Combining.new do |object, is_object_key|
|
|
121
|
+
case object
|
|
122
|
+
when String
|
|
123
|
+
if !string.valid_encoding? || string.encoding != Encoding::UTF_8
|
|
124
|
+
Base64.encode64(string)
|
|
125
|
+
else
|
|
126
|
+
string
|
|
127
|
+
end
|
|
128
|
+
else
|
|
129
|
+
object
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Combining JSON fragments
|
|
135
|
+
|
|
136
|
+
To combine JSON fragments into a bigger JSON document, you can use `JSON::Fragment`:
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
posts_json = cache.fetch_multi(post_ids) do |post_id|
|
|
140
|
+
JSON.generate(Post.find(post_id))
|
|
141
|
+
end
|
|
142
|
+
posts_json.map! { |post_json| JSON::Fragment.new(post_json) }
|
|
143
|
+
JSON.generate({ posts: posts_json, count: posts_json.count })
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Round-tripping arbitrary types
|
|
147
|
+
|
|
148
|
+
> [!CAUTION]
|
|
149
|
+
> You should never use `JSON.unsafe_load` nor `JSON.parse(str, create_additions: true)` to parse untrusted user input,
|
|
150
|
+
> as it can lead to remote code execution vulnerabilities.
|
|
151
|
+
|
|
152
|
+
To create a JSON document from a ruby data structure, you can call
|
|
153
|
+
`JSON.generate` like that:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
|
|
157
|
+
# => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
To get back a ruby data structure from a JSON document, you have to call
|
|
161
|
+
JSON.parse on it:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
JSON.parse json
|
|
165
|
+
# => [1, 2, {"a"=>3.141}, false, true, nil, "4..10"]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Note, that the range from the original data structure is a simple
|
|
169
|
+
string now. The reason for this is, that JSON doesn't support ranges
|
|
170
|
+
or arbitrary classes. In this case the json library falls back to call
|
|
171
|
+
`Object#to_json`, which is the same as `#to_s.to_json`.
|
|
172
|
+
|
|
173
|
+
It's possible to add JSON support serialization to arbitrary classes by
|
|
174
|
+
simply implementing a more specialized version of the `#to_json method`, that
|
|
175
|
+
should return a JSON object (a hash converted to JSON with `#to_json`) like
|
|
176
|
+
this (don't forget the `*a` for all the arguments):
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
class Range
|
|
180
|
+
def to_json(*a)
|
|
181
|
+
{
|
|
182
|
+
'json_class' => self.class.name, # = 'Range'
|
|
183
|
+
'data' => [ first, last, exclude_end? ]
|
|
184
|
+
}.to_json(*a)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The hash key `json_class` is the class, that will be asked to deserialise the
|
|
190
|
+
JSON representation later. In this case it's `Range`, but any namespace of
|
|
191
|
+
the form `A::B` or `::A::B` will do. All other keys are arbitrary and can be
|
|
192
|
+
used to store the necessary data to configure the object to be deserialised.
|
|
193
|
+
|
|
194
|
+
If the key `json_class` is found in a JSON object, the JSON parser checks
|
|
195
|
+
if the given class responds to the `json_create` class method. If so, it is
|
|
196
|
+
called with the JSON object converted to a Ruby hash. So a range can
|
|
197
|
+
be deserialised by implementing `Range.json_create` like this:
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
class Range
|
|
201
|
+
def self.json_create(o)
|
|
202
|
+
new(*o['data'])
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Now it possible to serialise/deserialise ranges as well:
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
|
|
211
|
+
# => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]"
|
|
212
|
+
JSON.parse json
|
|
213
|
+
# => [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
|
|
214
|
+
json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
|
|
215
|
+
# => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]"
|
|
216
|
+
JSON.unsafe_load json
|
|
217
|
+
# => [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`JSON.generate` always creates the shortest possible string representation of a
|
|
221
|
+
ruby data structure in one line. This is good for data storage or network
|
|
222
|
+
protocols, but not so good for humans to read. Fortunately there's also
|
|
223
|
+
`JSON.pretty_generate` (or `JSON.pretty_generate`) that creates a more readable
|
|
224
|
+
output:
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
puts JSON.pretty_generate([1, 2, {"a"=>3.141}, false, true, nil, 4..10])
|
|
228
|
+
[
|
|
229
|
+
1,
|
|
230
|
+
2,
|
|
231
|
+
{
|
|
232
|
+
"a": 3.141
|
|
233
|
+
},
|
|
234
|
+
false,
|
|
235
|
+
true,
|
|
236
|
+
null,
|
|
237
|
+
{
|
|
238
|
+
"json_class": "Range",
|
|
239
|
+
"data": [
|
|
240
|
+
4,
|
|
241
|
+
10,
|
|
242
|
+
false
|
|
243
|
+
]
|
|
244
|
+
}
|
|
245
|
+
]
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
There are also the methods `Kernel#j` for generate, and `Kernel#jj` for
|
|
249
|
+
`pretty_generate` output to the console, that work analogous to Core Ruby's `p` and
|
|
250
|
+
the `pp` library's `pp` methods.
|
|
251
|
+
|
|
252
|
+
## Security
|
|
253
|
+
|
|
254
|
+
When parsing or serializing untrusted input, parser and generator options should never be user controlled.
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
# Dangerous, DO NOT DO THIS.
|
|
258
|
+
JSON.generate(params[:data], params[:options])
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Security vulnerability reports relying on attacker controlled parsing or generator options will be handled as regular bug fixes.
|
|
262
|
+
|
|
263
|
+
## Development
|
|
264
|
+
|
|
265
|
+
### Prerequisites
|
|
266
|
+
|
|
267
|
+
1. Clone the repository
|
|
268
|
+
2. Install dependencies with `bundle install`
|
|
269
|
+
|
|
270
|
+
### Testing
|
|
271
|
+
|
|
272
|
+
The full test suite can be run with:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
bundle exec rake test
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Release
|
|
279
|
+
|
|
280
|
+
Update the `lib/json/version.rb` file.
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
rbenv shell 2.6.5
|
|
284
|
+
rake build
|
|
285
|
+
gem push pkg/json-2.3.0.gem
|
|
286
|
+
|
|
287
|
+
rbenv shell jruby-9.2.9.0
|
|
288
|
+
rake build
|
|
289
|
+
gem push pkg/json-2.3.0-java.gem
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Author
|
|
293
|
+
|
|
294
|
+
Florian Frank <mailto:flori@ping.de>
|
|
295
|
+
|
|
296
|
+
## License
|
|
297
|
+
|
|
298
|
+
Ruby License, see https://www.ruby-lang.org/en/about/license.txt.
|
|
299
|
+
|
|
300
|
+
## Download
|
|
301
|
+
|
|
302
|
+
The latest version of this library can be downloaded at
|
|
303
|
+
|
|
304
|
+
* https://rubygems.org/gems/json
|
|
305
|
+
|
|
306
|
+
Online Documentation should be located at
|
|
307
|
+
|
|
308
|
+
* https://www.rubydoc.info/gems/json
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#ifndef _FBUFFER_H_
|
|
2
|
+
#define _FBUFFER_H_
|
|
3
|
+
|
|
4
|
+
#include "../json.h"
|
|
5
|
+
#include "../vendor/jeaiii-ltoa.h"
|
|
6
|
+
|
|
7
|
+
enum fbuffer_type {
|
|
8
|
+
FBUFFER_HEAP_ALLOCATED = 0,
|
|
9
|
+
FBUFFER_STACK_ALLOCATED = 1,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
typedef struct FBufferStruct {
|
|
13
|
+
enum fbuffer_type type;
|
|
14
|
+
size_t initial_length;
|
|
15
|
+
size_t len;
|
|
16
|
+
size_t capa;
|
|
17
|
+
#if JSON_DEBUG
|
|
18
|
+
size_t requested;
|
|
19
|
+
#endif
|
|
20
|
+
char *ptr;
|
|
21
|
+
VALUE io;
|
|
22
|
+
} FBuffer;
|
|
23
|
+
|
|
24
|
+
#define FBUFFER_STACK_SIZE 512
|
|
25
|
+
#define FBUFFER_IO_BUFFER_SIZE (16384 - 1)
|
|
26
|
+
#define FBUFFER_INITIAL_LENGTH_DEFAULT 1024
|
|
27
|
+
|
|
28
|
+
#define FBUFFER_PTR(fb) ((fb)->ptr)
|
|
29
|
+
#define FBUFFER_LEN(fb) ((fb)->len)
|
|
30
|
+
#define FBUFFER_CAPA(fb) ((fb)->capa)
|
|
31
|
+
#define FBUFFER_PAIR(fb) FBUFFER_PTR(fb), FBUFFER_LEN(fb)
|
|
32
|
+
|
|
33
|
+
static void fbuffer_free(FBuffer *fb);
|
|
34
|
+
static void fbuffer_clear(FBuffer *fb);
|
|
35
|
+
static void fbuffer_append(FBuffer *fb, const char *newstr, size_t len);
|
|
36
|
+
static void fbuffer_append_long(FBuffer *fb, long number);
|
|
37
|
+
static inline void fbuffer_append_char(FBuffer *fb, char newchr);
|
|
38
|
+
static VALUE fbuffer_finalize(FBuffer *fb);
|
|
39
|
+
|
|
40
|
+
static void fbuffer_init(FBuffer *fb, size_t initial_length, VALUE io, char *stack_buffer, size_t stack_buffer_size)
|
|
41
|
+
{
|
|
42
|
+
if (RTEST(io)) {
|
|
43
|
+
JSON_ASSERT(fb->type == FBUFFER_HEAP_ALLOCATED);
|
|
44
|
+
fb->io = io;
|
|
45
|
+
fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_IO_BUFFER_SIZE;
|
|
46
|
+
} else {
|
|
47
|
+
fb->type = FBUFFER_STACK_ALLOCATED;
|
|
48
|
+
fb->ptr = stack_buffer;
|
|
49
|
+
fb->capa = stack_buffer_size;
|
|
50
|
+
fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT;
|
|
51
|
+
}
|
|
52
|
+
#if JSON_DEBUG
|
|
53
|
+
fb->requested = 0;
|
|
54
|
+
#endif
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static inline void fbuffer_consumed(FBuffer *fb, size_t consumed)
|
|
58
|
+
{
|
|
59
|
+
#if JSON_DEBUG
|
|
60
|
+
if (consumed > fb->requested) {
|
|
61
|
+
rb_bug("fbuffer: Out of bound write");
|
|
62
|
+
}
|
|
63
|
+
fb->requested = 0;
|
|
64
|
+
#endif
|
|
65
|
+
fb->len += consumed;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static void fbuffer_free(FBuffer *fb)
|
|
69
|
+
{
|
|
70
|
+
if (fb->ptr && fb->type == FBUFFER_HEAP_ALLOCATED) {
|
|
71
|
+
JSON_SIZED_FREE_N(fb->ptr, fb->capa);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static void fbuffer_clear(FBuffer *fb)
|
|
76
|
+
{
|
|
77
|
+
fb->len = 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static void fbuffer_flush(FBuffer *fb)
|
|
81
|
+
{
|
|
82
|
+
rb_io_write(fb->io, rb_utf8_str_new(fb->ptr, fb->len));
|
|
83
|
+
fbuffer_clear(fb);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static void fbuffer_realloc(FBuffer *fb, size_t new_capa)
|
|
87
|
+
{
|
|
88
|
+
if (new_capa > fb->capa) {
|
|
89
|
+
if (fb->type == FBUFFER_STACK_ALLOCATED) {
|
|
90
|
+
const char *old_buffer = fb->ptr;
|
|
91
|
+
fb->ptr = ALLOC_N(char, new_capa);
|
|
92
|
+
fb->type = FBUFFER_HEAP_ALLOCATED;
|
|
93
|
+
MEMCPY(fb->ptr, old_buffer, char, fb->len);
|
|
94
|
+
} else {
|
|
95
|
+
JSON_SIZED_REALLOC_N(fb->ptr, char, new_capa, fb->capa);
|
|
96
|
+
}
|
|
97
|
+
fb->capa = new_capa;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static void fbuffer_do_inc_capa(FBuffer *fb, size_t requested)
|
|
102
|
+
{
|
|
103
|
+
if (RB_UNLIKELY(fb->io)) {
|
|
104
|
+
if (fb->capa != 0) {
|
|
105
|
+
fbuffer_flush(fb);
|
|
106
|
+
if (RB_LIKELY(requested < fb->capa)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
size_t new_capa = fb->capa ? fb->capa : fb->initial_length;
|
|
113
|
+
size_t needed_capa = requested + fb->len;
|
|
114
|
+
|
|
115
|
+
while (new_capa < needed_capa) {
|
|
116
|
+
new_capa *= 2;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fbuffer_realloc(fb, new_capa);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested)
|
|
123
|
+
{
|
|
124
|
+
#if JSON_DEBUG
|
|
125
|
+
fb->requested = requested;
|
|
126
|
+
#endif
|
|
127
|
+
|
|
128
|
+
if (RB_UNLIKELY(requested > fb->capa - fb->len)) {
|
|
129
|
+
fbuffer_do_inc_capa(fb, requested);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static inline size_t fbuffer_size_mul_or_raise(size_t a, size_t b)
|
|
134
|
+
{
|
|
135
|
+
size_t result = a * b;
|
|
136
|
+
if (RB_UNLIKELY(a != 0 && (result / a) != b)) {
|
|
137
|
+
rb_raise(rb_eArgError, "Buffer overflow, the resulting document is too large to be generated");
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, size_t len)
|
|
143
|
+
{
|
|
144
|
+
MEMCPY(fb->ptr + fb->len, newstr, char, len);
|
|
145
|
+
fbuffer_consumed(fb, len);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static inline void fbuffer_append(FBuffer *fb, const char *newstr, size_t len)
|
|
149
|
+
{
|
|
150
|
+
if (len > 0) {
|
|
151
|
+
fbuffer_inc_capa(fb, len);
|
|
152
|
+
fbuffer_append_reserved(fb, newstr, len);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Appends a character into a buffer. The buffer needs to have sufficient capacity, via fbuffer_inc_capa(...). */
|
|
157
|
+
static inline void fbuffer_append_reserved_char(FBuffer *fb, char chr)
|
|
158
|
+
{
|
|
159
|
+
#if JSON_DEBUG
|
|
160
|
+
if (fb->requested < 1) {
|
|
161
|
+
rb_bug("fbuffer: unreserved write");
|
|
162
|
+
}
|
|
163
|
+
fb->requested--;
|
|
164
|
+
#endif
|
|
165
|
+
|
|
166
|
+
fb->ptr[fb->len] = chr;
|
|
167
|
+
fb->len++;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
static void fbuffer_append_str(FBuffer *fb, VALUE str)
|
|
171
|
+
{
|
|
172
|
+
const char *ptr;
|
|
173
|
+
size_t len;
|
|
174
|
+
RSTRING_GETMEM(str, ptr, len);
|
|
175
|
+
|
|
176
|
+
fbuffer_append(fb, ptr, len);
|
|
177
|
+
RB_GC_GUARD(str);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat)
|
|
181
|
+
{
|
|
182
|
+
const char *ptr;
|
|
183
|
+
size_t len;
|
|
184
|
+
RSTRING_GETMEM(str, ptr, len);
|
|
185
|
+
|
|
186
|
+
fbuffer_inc_capa(fb, fbuffer_size_mul_or_raise(repeat, len));
|
|
187
|
+
while (repeat) {
|
|
188
|
+
#if JSON_DEBUG
|
|
189
|
+
fb->requested = len;
|
|
190
|
+
#endif
|
|
191
|
+
fbuffer_append_reserved(fb, ptr, len);
|
|
192
|
+
repeat--;
|
|
193
|
+
}
|
|
194
|
+
RB_GC_GUARD(str);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
static inline void fbuffer_append_char(FBuffer *fb, char newchr)
|
|
198
|
+
{
|
|
199
|
+
fbuffer_inc_capa(fb, 1);
|
|
200
|
+
*(fb->ptr + fb->len) = newchr;
|
|
201
|
+
fbuffer_consumed(fb, 1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
static inline char *fbuffer_cursor(FBuffer *fb)
|
|
205
|
+
{
|
|
206
|
+
return fb->ptr + fb->len;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
static inline void fbuffer_advance_to(FBuffer *fb, char *end)
|
|
210
|
+
{
|
|
211
|
+
fbuffer_consumed(fb, (end - fb->ptr) - fb->len);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/*
|
|
215
|
+
* Appends the decimal string representation of \a number into the buffer.
|
|
216
|
+
*/
|
|
217
|
+
static void fbuffer_append_long(FBuffer *fb, long number)
|
|
218
|
+
{
|
|
219
|
+
/*
|
|
220
|
+
* The jeaiii_ultoa() function produces digits left-to-right,
|
|
221
|
+
* allowing us to write directly into the buffer, but we don't know
|
|
222
|
+
* the number of resulting characters.
|
|
223
|
+
*
|
|
224
|
+
* We do know, however, that the `number` argument is always in the
|
|
225
|
+
* range 0xc000000000000000 to 0x3fffffffffffffff, or, in decimal,
|
|
226
|
+
* -4611686018427387904 to 4611686018427387903. The max number of chars
|
|
227
|
+
* generated is therefore 20 (including a potential sign character).
|
|
228
|
+
*/
|
|
229
|
+
|
|
230
|
+
static const int MAX_CHARS_FOR_LONG = 20;
|
|
231
|
+
|
|
232
|
+
fbuffer_inc_capa(fb, MAX_CHARS_FOR_LONG);
|
|
233
|
+
|
|
234
|
+
if (number < 0) {
|
|
235
|
+
fbuffer_append_reserved_char(fb, '-');
|
|
236
|
+
|
|
237
|
+
/*
|
|
238
|
+
* Since number is always > LONG_MIN, `-number` will not overflow
|
|
239
|
+
* and is always the positive abs() value.
|
|
240
|
+
*/
|
|
241
|
+
number = -number;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
char *end = jeaiii_ultoa(fbuffer_cursor(fb), number);
|
|
245
|
+
fbuffer_advance_to(fb, end);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
static VALUE fbuffer_finalize(FBuffer *fb)
|
|
249
|
+
{
|
|
250
|
+
if (fb->io) {
|
|
251
|
+
fbuffer_flush(fb);
|
|
252
|
+
rb_io_flush(fb->io);
|
|
253
|
+
return fb->io;
|
|
254
|
+
} else {
|
|
255
|
+
return rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#endif // _FBUFFER_H_
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'mkmf'
|
|
2
|
+
|
|
3
|
+
if RUBY_ENGINE == 'truffleruby'
|
|
4
|
+
# The pure-Ruby generator is faster on TruffleRuby, so skip compiling the generator extension
|
|
5
|
+
File.write('Makefile', dummy_makefile("").join)
|
|
6
|
+
else
|
|
7
|
+
append_cflags("-std=c99")
|
|
8
|
+
have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3
|
|
9
|
+
have_func("ruby_xfree_sized", "ruby.h") # RUBY_VERSION >= 4.1
|
|
10
|
+
|
|
11
|
+
$defs << "-DJSON_GENERATOR"
|
|
12
|
+
$defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0"
|
|
13
|
+
|
|
14
|
+
if enable_config('generator-use-simd', default=!ENV["JSON_DISABLE_SIMD"])
|
|
15
|
+
load __dir__ + "/../simd/conf.rb"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
create_makefile 'json/ext/generator'
|
|
19
|
+
end
|