usamin 7.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +29 -0
- data/LICENSE.txt +21 -0
- data/README.md +255 -0
- data/Rakefile +18 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ext/usamin/extconf.rb +6 -0
- data/ext/usamin/usamin.cpp +987 -0
- data/lib/usamin.rb +2 -0
- data/lib/usamin/version.rb +3 -0
- data/usamin.gemspec +29 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 38c48b49c05c5a95289abb777b32db4b47252fd31c5553acf09b11f3c076b500
|
4
|
+
data.tar.gz: 023641ec4fb9fc7277044ae4c6a1dcca134ace8ed4aaddf2b64c80a4fb7ef89e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 493e984049b444963a1be108cc624bc6a426edbf3c951c860f4662f4f3121e309e1768be11a919a16353c36fc96fb90124daf68bb68ec8f5756a82850c4a7618
|
7
|
+
data.tar.gz: 74cfe2c5148808a6be7f43a6bddf5ec1306ee3692a7833ea775392b61948fee537fc83befe55d0961d5ff3771e43a91d208294d2a8b4c41c8df20c738976cb5a
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
usamin (7.7.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
coderay (1.1.2)
|
10
|
+
method_source (0.9.0)
|
11
|
+
pry (0.11.3)
|
12
|
+
coderay (~> 1.1.0)
|
13
|
+
method_source (~> 0.9.0)
|
14
|
+
rake (12.3.0)
|
15
|
+
rake-compiler (1.0.4)
|
16
|
+
rake
|
17
|
+
|
18
|
+
PLATFORMS
|
19
|
+
ruby
|
20
|
+
|
21
|
+
DEPENDENCIES
|
22
|
+
bundler (~> 1.16)
|
23
|
+
pry (~> 0.11)
|
24
|
+
rake (~> 12.3)
|
25
|
+
rake-compiler
|
26
|
+
usamin!
|
27
|
+
|
28
|
+
BUNDLED WITH
|
29
|
+
1.16.1
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Ishotihadus
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
# Usamin
|
2
|
+
|
3
|
+
A fast JSON serializer / deserializer for Ruby with [RapidJSON](http://rapidjson.org/).
|
4
|
+
|
5
|
+
The name of "Usamin" is derived from [Nana Abe](https://www.project-imas.com/wiki/Nana_Abe).
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Install RapidJSON beforehand.
|
10
|
+
|
11
|
+
Next, add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'usamin'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install usamin
|
24
|
+
|
25
|
+
The directory of RapidJSON can be explicitly specified with `--with-rapidjson-dir` option.
|
26
|
+
|
27
|
+
$ gem install usamin -- --with-rapidjson-dir=/usr/local/opt/rapidjson
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
### Loading library
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'usamin'
|
35
|
+
```
|
36
|
+
|
37
|
+
### Parsing
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
json = '{
|
41
|
+
"miku maekawa": {
|
42
|
+
"age": 15,
|
43
|
+
"height": 152,
|
44
|
+
"weight": 45,
|
45
|
+
"body_size": [85, 55, 81],
|
46
|
+
"birthday": [2, 22],
|
47
|
+
"cv": "natsumi takamori"
|
48
|
+
},
|
49
|
+
"nana abe": {
|
50
|
+
"age": 17,
|
51
|
+
"height": 146,
|
52
|
+
"weight": 40,
|
53
|
+
"body_size": [84, 57, 84],
|
54
|
+
"birthday": [5, 15],
|
55
|
+
"cv": "marie miyake"
|
56
|
+
}
|
57
|
+
}'
|
58
|
+
|
59
|
+
Usamin.parse(json)
|
60
|
+
```
|
61
|
+
|
62
|
+
### Lazy loading
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
data = Usamin.load(json)
|
66
|
+
```
|
67
|
+
|
68
|
+
Here, `data` is not a Hash, but this can be handled like a Hash.
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
data.keys
|
72
|
+
#=> ["miku maekawa", "nana abe"]
|
73
|
+
|
74
|
+
data.map{|k, v| [k, v['age']]}.to_h
|
75
|
+
#=> {"miku maekawa"=>15, "nana abe"=>17}
|
76
|
+
```
|
77
|
+
|
78
|
+
Array data also can be handled like an Array object.
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
data['nana abe']['body_size'].size
|
82
|
+
#=> 3
|
83
|
+
|
84
|
+
data['nana abe']['body_size'].inject(:+)
|
85
|
+
#=> 225
|
86
|
+
```
|
87
|
+
|
88
|
+
The `eval` and `eval_r` converts these data structures into the Ruby data structures. `_r` means recursiveness.
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
data.eval
|
92
|
+
#=> {"miku maekawa"=>#<Usamin::Hash>, "nana abe"=>#<Usamin::Hash>}
|
93
|
+
|
94
|
+
data['miku maekawa'].eval_r
|
95
|
+
#=> {"age"=>15, "height"=>152, "weight"=>45, "body_size"=>[85, 55, 81], "birthday"=>[2, 22], "cv"=>"natsumi takamori"}
|
96
|
+
|
97
|
+
data.eval_r
|
98
|
+
#=> same as Usamin.parse(json)
|
99
|
+
```
|
100
|
+
|
101
|
+
#### Notes about lazy loading data
|
102
|
+
|
103
|
+
- Frozen. Modification is not allowed.
|
104
|
+
- A key list of Hash is based on not hash tables but arrays. An index access to hash costs O(n).
|
105
|
+
|
106
|
+
### Generating
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
data = {
|
110
|
+
"miku maekawa" => {
|
111
|
+
"age"=>15,
|
112
|
+
"height"=>152,
|
113
|
+
"weight"=>45,
|
114
|
+
"body_size"=>[85, 55, 81],
|
115
|
+
"birthday"=>[2, 22],
|
116
|
+
"cv"=>"natsumi takamori"},
|
117
|
+
"nana abe"=> {
|
118
|
+
"age"=>17,
|
119
|
+
"height"=>146,
|
120
|
+
"weight"=>40,
|
121
|
+
"body_size"=>[84, 57, 84],
|
122
|
+
"birthday"=>[5, 15],
|
123
|
+
"cv"=>"marie miyake"}}
|
124
|
+
|
125
|
+
Usamin.generate(data)
|
126
|
+
|
127
|
+
# pretty generation is also supported
|
128
|
+
Usamin.pretty_generate(data)
|
129
|
+
```
|
130
|
+
|
131
|
+
Of course, UsaminValue also can be serialized.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
data = Usamin.load(json)
|
135
|
+
|
136
|
+
Usamin.generate(data['nana abe'])
|
137
|
+
```
|
138
|
+
|
139
|
+
### Fast parsing
|
140
|
+
|
141
|
+
Usamin uses `kParseFullPrecisionFlag` by default, but this option makes parsing a little slow.
|
142
|
+
|
143
|
+
You can use `:fast` option to avoid this.
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
# default
|
147
|
+
Usamin.parse('77.777700000000795')
|
148
|
+
#=> 77.77770000000079
|
149
|
+
|
150
|
+
# fast but not precise
|
151
|
+
Usamin.parse('77.777700000000795', fast: true)
|
152
|
+
#=> 77.7777000000008
|
153
|
+
```
|
154
|
+
|
155
|
+
### Error handling
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
str = '{"this is bad example"'
|
159
|
+
Usamin.parse(str)
|
160
|
+
# Usamin::ParserError: Missing a colon after a name of object member. Offset: 22
|
161
|
+
```
|
162
|
+
|
163
|
+
### Documentation
|
164
|
+
|
165
|
+
See: http://www.rubydoc.info/gems/usamin/
|
166
|
+
|
167
|
+
## Benchmarks
|
168
|
+
|
169
|
+
Based on [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark).
|
170
|
+
|
171
|
+
### Roundtrips
|
172
|
+
|
173
|
+
Usamin passes all roundtrips, and the results are same as official JSON package.
|
174
|
+
|
175
|
+
### Reliability of parsed data
|
176
|
+
|
177
|
+
Usamin and JSON load the same data from 3 big json data in nativejson-benchmark.
|
178
|
+
|
179
|
+
### Performance
|
180
|
+
|
181
|
+
The values show the elapsed time for operating 20 times.
|
182
|
+
|
183
|
+
#### Parsing
|
184
|
+
|
185
|
+
```
|
186
|
+
nativejson-benchmark/data/canada.json
|
187
|
+
json 0.785524 0.007927 0.793451 ( 0.805269)
|
188
|
+
oj 1.833580 0.043648 1.877228 ( 1.895704)
|
189
|
+
usamin 0.595523 0.012299 0.607822 ( 0.612215)
|
190
|
+
usamin (fast) 0.286458 0.008710 0.295168 ( 0.297998)
|
191
|
+
usamin (load) 0.484056 0.011770 0.495826 ( 0.498110)
|
192
|
+
usamin (load / fast) 0.187712 0.020202 0.207914 ( 0.210711)
|
193
|
+
|
194
|
+
nativejson-benchmark/data/citm_catalog.json
|
195
|
+
json 0.469471 0.005208 0.474679 ( 0.478920)
|
196
|
+
oj 0.327143 0.002947 0.330090 ( 0.331882)
|
197
|
+
usamin 0.362764 0.005633 0.368397 ( 0.370940)
|
198
|
+
usamin (fast) 0.359859 0.005884 0.365743 ( 0.371016)
|
199
|
+
usamin (load) 0.117140 0.007003 0.124143 ( 0.126548)
|
200
|
+
usamin (load / fast) 0.115237 0.010061 0.125298 ( 0.128470)
|
201
|
+
|
202
|
+
nativejson-benchmark/data/twitter.json
|
203
|
+
json 0.238582 0.002561 0.241143 ( 0.243726)
|
204
|
+
oj 0.162212 0.001031 0.163243 ( 0.165047)
|
205
|
+
usamin 0.194476 0.001523 0.195999 ( 0.197920)
|
206
|
+
usamin (fast) 0.192985 0.001339 0.194324 ( 0.197404)
|
207
|
+
usamin (load) 0.070360 0.005012 0.075372 ( 0.078090)
|
208
|
+
usamin (load / fast) 0.067618 0.006416 0.074034 ( 0.076244)
|
209
|
+
```
|
210
|
+
|
211
|
+
#### Generating
|
212
|
+
|
213
|
+
```
|
214
|
+
nativejson-benchmark/data/canada.json
|
215
|
+
json 1.988155 0.029728 2.017883 ( 2.023475)
|
216
|
+
oj 2.092999 0.033136 2.126135 ( 2.135765)
|
217
|
+
usamin 0.296385 0.031412 0.327797 ( 0.330314)
|
218
|
+
|
219
|
+
nativejson-benchmark/data/citm_catalog.json
|
220
|
+
json 0.243795 0.007463 0.251258 ( 0.256621)
|
221
|
+
oj 0.076474 0.009617 0.086091 ( 0.087966)
|
222
|
+
usamin 0.059434 0.009616 0.069050 ( 0.071158)
|
223
|
+
|
224
|
+
nativejson-benchmark/data/twitter.json
|
225
|
+
json 0.159030 0.006730 0.165760 ( 0.170796)
|
226
|
+
oj 0.052856 0.009164 0.062020 ( 0.064344)
|
227
|
+
usamin 0.047707 0.010265 0.057972 ( 0.061729)
|
228
|
+
```
|
229
|
+
|
230
|
+
#### Pretty Generating
|
231
|
+
|
232
|
+
```
|
233
|
+
nativejson-benchmark/data/canada.json
|
234
|
+
json 2.188493 0.066003 2.254496 ( 2.260332)
|
235
|
+
oj 1.583613 0.029798 1.613411 ( 1.619652)
|
236
|
+
usamin 0.399066 0.081314 0.480380 ( 0.482841)
|
237
|
+
|
238
|
+
nativejson-benchmark/data/citm_catalog.json
|
239
|
+
json 0.300601 0.029596 0.330197 ( 0.335351)
|
240
|
+
oj 0.065151 0.009553 0.074704 ( 0.077010)
|
241
|
+
usamin 0.089872 0.022203 0.112075 ( 0.116063)
|
242
|
+
|
243
|
+
nativejson-benchmark/data/twitter.json
|
244
|
+
json 0.189439 0.010086 0.199525 ( 0.204491)
|
245
|
+
oj 0.040982 0.008203 0.049185 ( 0.049352)
|
246
|
+
usamin 0.044296 0.010095 0.054391 ( 0.056245)
|
247
|
+
```
|
248
|
+
|
249
|
+
## Contributing
|
250
|
+
|
251
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Ishotihadus/usamin.
|
252
|
+
|
253
|
+
## License
|
254
|
+
|
255
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT) at the request of RapidJSON.
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.libs << "test"
|
6
|
+
t.libs << "lib"
|
7
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
8
|
+
end
|
9
|
+
|
10
|
+
require "rake/extensiontask"
|
11
|
+
|
12
|
+
task :build => :compile
|
13
|
+
|
14
|
+
Rake::ExtensionTask.new("usamin") do |ext|
|
15
|
+
ext.lib_dir = "lib/usamin"
|
16
|
+
end
|
17
|
+
|
18
|
+
task :default => [:clobber, :compile, :test]
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "usamin"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "pry"
|
14
|
+
Pry.start
|
data/bin/setup
ADDED
@@ -0,0 +1,987 @@
|
|
1
|
+
#include <string.h>
|
2
|
+
#include <stdint.h>
|
3
|
+
#include <ruby.h>
|
4
|
+
#include <ruby/encoding.h>
|
5
|
+
#define RAPIDJSON_PARSE_DEFAULT_FLAGS \
|
6
|
+
rapidjson::kParseFullPrecisionFlag | rapidjson::kParseCommentsFlag | rapidjson::kParseTrailingCommasFlag | rapidjson::kParseNanAndInfFlag
|
7
|
+
#define kParseFastFlags (rapidjson::kParseDefaultFlags & ~rapidjson::kParseFullPrecisionFlag)
|
8
|
+
#define RAPIDJSON_WRITE_DEFAULT_FLAGS rapidjson::kWriteNanAndInfFlag
|
9
|
+
#include <rapidjson/document.h>
|
10
|
+
#include <rapidjson/writer.h>
|
11
|
+
#include <rapidjson/prettywriter.h>
|
12
|
+
#include <rapidjson/error/en.h>
|
13
|
+
|
14
|
+
#if SIZEOF_VALUE < SIZEOF_VOIDP
|
15
|
+
#error SIZEOF_VOIDP must not be greater than SIZEOF_VALUE.
|
16
|
+
#endif
|
17
|
+
|
18
|
+
|
19
|
+
rb_encoding *utf8;
|
20
|
+
int utf8index;
|
21
|
+
ID id_to_s;
|
22
|
+
VALUE rb_cUsaminValue, rb_cUsaminHash, rb_cUsaminArray, rb_eUsaminError, rb_eParserError;
|
23
|
+
VALUE utf8value, sym_fast, sym_indent, sym_single_line_array;
|
24
|
+
|
25
|
+
class UsaminValue {
|
26
|
+
public:
|
27
|
+
rapidjson::Value *value;
|
28
|
+
bool free_flag;
|
29
|
+
|
30
|
+
UsaminValue(rapidjson::Value *value = nullptr, bool free_flag = false);
|
31
|
+
~UsaminValue();
|
32
|
+
};
|
33
|
+
|
34
|
+
static inline VALUE get_utf8_str(VALUE str) {
|
35
|
+
Check_Type(str, T_STRING);
|
36
|
+
int encoding = rb_enc_get_index(str);
|
37
|
+
if (encoding == utf8index || rb_enc_compatible(str, utf8value) == utf8)
|
38
|
+
return str;
|
39
|
+
else
|
40
|
+
return rb_str_conv_enc(str, rb_enc_from_index(encoding), utf8);
|
41
|
+
}
|
42
|
+
|
43
|
+
static inline VALUE new_utf8_str(const char* cstr, const long len) {
|
44
|
+
VALUE ret = rb_str_new(cstr, len);
|
45
|
+
rb_enc_set_index(ret, utf8index);
|
46
|
+
return ret;
|
47
|
+
}
|
48
|
+
|
49
|
+
static inline bool str_compare(const char* str1, const long len1, const char* str2, const long len2) {
|
50
|
+
if (len1 != len2 || len1 < 0)
|
51
|
+
return false;
|
52
|
+
return memcmp(str1, str2, len1) == 0;
|
53
|
+
}
|
54
|
+
|
55
|
+
static inline void check_value(UsaminValue *ptr) {
|
56
|
+
if (!ptr || !ptr->value)
|
57
|
+
rb_raise(rb_eUsaminError, "Null Reference.");
|
58
|
+
}
|
59
|
+
|
60
|
+
static inline void check_object(UsaminValue *ptr) {
|
61
|
+
check_value(ptr);
|
62
|
+
if (!ptr->value->IsObject())
|
63
|
+
rb_raise(rb_eUsaminError, "Value is not an object.");
|
64
|
+
}
|
65
|
+
|
66
|
+
static inline void check_array(UsaminValue *ptr) {
|
67
|
+
check_value(ptr);
|
68
|
+
if (!ptr->value->IsArray())
|
69
|
+
rb_raise(rb_eUsaminError, "Value is not an array.");
|
70
|
+
}
|
71
|
+
|
72
|
+
|
73
|
+
static VALUE usamin_free(UsaminValue **ptr) {
|
74
|
+
if (*ptr)
|
75
|
+
delete *ptr;
|
76
|
+
ruby_xfree(ptr);
|
77
|
+
return Qnil;
|
78
|
+
}
|
79
|
+
|
80
|
+
static VALUE usamin_alloc(const VALUE klass) {
|
81
|
+
UsaminValue** ptr = (UsaminValue**)ruby_xmalloc(sizeof(UsaminValue*));
|
82
|
+
*ptr = nullptr;
|
83
|
+
return Data_Wrap_Struct(klass, NULL, usamin_free, ptr);
|
84
|
+
}
|
85
|
+
|
86
|
+
|
87
|
+
static inline UsaminValue* get_value(VALUE value) {
|
88
|
+
UsaminValue** ptr;
|
89
|
+
Data_Get_Struct(value, UsaminValue*, ptr);
|
90
|
+
return *ptr;
|
91
|
+
}
|
92
|
+
|
93
|
+
static inline void set_value(VALUE value, UsaminValue *usamin) {
|
94
|
+
UsaminValue **ptr;
|
95
|
+
Data_Get_Struct(value, UsaminValue*, ptr);
|
96
|
+
*ptr = usamin;
|
97
|
+
}
|
98
|
+
|
99
|
+
static inline VALUE make_hash(UsaminValue *value) {
|
100
|
+
VALUE ret = rb_obj_alloc(rb_cUsaminHash);
|
101
|
+
set_value(ret, value);
|
102
|
+
return ret;
|
103
|
+
}
|
104
|
+
|
105
|
+
static inline VALUE make_array(UsaminValue *value) {
|
106
|
+
VALUE ret = rb_obj_alloc(rb_cUsaminArray);
|
107
|
+
set_value(ret, value);
|
108
|
+
return ret;
|
109
|
+
}
|
110
|
+
|
111
|
+
static inline rapidjson::ParseResult parse(rapidjson::Document &doc, const VALUE str, bool fast = false) {
|
112
|
+
VALUE v = get_utf8_str(str);
|
113
|
+
return fast ? doc.Parse<kParseFastFlags>(RSTRING_PTR(v), RSTRING_LEN(v)) : doc.Parse(RSTRING_PTR(v), RSTRING_LEN(v));
|
114
|
+
}
|
115
|
+
|
116
|
+
|
117
|
+
static inline VALUE eval_num(rapidjson::Value &value);
|
118
|
+
static inline VALUE eval_str(rapidjson::Value &value);
|
119
|
+
static inline VALUE eval_object(rapidjson::Value &value);
|
120
|
+
static inline VALUE eval_object_r(rapidjson::Value &value);
|
121
|
+
static inline VALUE eval_array(rapidjson::Value &value);
|
122
|
+
static inline VALUE eval_array_r(rapidjson::Value &value);
|
123
|
+
|
124
|
+
static VALUE eval(rapidjson::Value &value) {
|
125
|
+
switch (value.GetType()) {
|
126
|
+
case rapidjson::kObjectType:
|
127
|
+
return make_hash(new UsaminValue(&value, false));
|
128
|
+
case rapidjson::kArrayType:
|
129
|
+
return make_array(new UsaminValue(&value, false));
|
130
|
+
case rapidjson::kNullType:
|
131
|
+
return Qnil;
|
132
|
+
case rapidjson::kFalseType:
|
133
|
+
return Qfalse;
|
134
|
+
case rapidjson::kTrueType:
|
135
|
+
return Qtrue;
|
136
|
+
case rapidjson::kNumberType:
|
137
|
+
return eval_num(value);
|
138
|
+
case rapidjson::kStringType:
|
139
|
+
return eval_str(value);
|
140
|
+
default:
|
141
|
+
rb_raise(rb_eUsaminError, "Unknown Value Type: %d", value.GetType());
|
142
|
+
return Qnil;
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
static VALUE eval_r(rapidjson::Value &value) {
|
147
|
+
switch (value.GetType()) {
|
148
|
+
case rapidjson::kObjectType:
|
149
|
+
return eval_object_r(value);
|
150
|
+
case rapidjson::kArrayType:
|
151
|
+
return eval_array_r(value);
|
152
|
+
case rapidjson::kNullType:
|
153
|
+
return Qnil;
|
154
|
+
case rapidjson::kFalseType:
|
155
|
+
return Qfalse;
|
156
|
+
case rapidjson::kTrueType:
|
157
|
+
return Qtrue;
|
158
|
+
case rapidjson::kNumberType:
|
159
|
+
return eval_num(value);
|
160
|
+
case rapidjson::kStringType:
|
161
|
+
return eval_str(value);
|
162
|
+
default:
|
163
|
+
rb_raise(rb_eUsaminError, "Unknown Value Type: %d", value.GetType());
|
164
|
+
return Qnil;
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
static inline VALUE eval_num(rapidjson::Value &value) {
|
169
|
+
if (value.IsInt())
|
170
|
+
return INT2FIX(value.GetInt());
|
171
|
+
else if (value.IsUint())
|
172
|
+
return UINT2NUM(value.GetUint());
|
173
|
+
else if (value.IsInt64())
|
174
|
+
return LL2NUM(value.GetInt64());
|
175
|
+
else if (value.IsUint64())
|
176
|
+
return ULL2NUM(value.GetUint64());
|
177
|
+
else
|
178
|
+
return DBL2NUM(value.GetDouble());
|
179
|
+
}
|
180
|
+
|
181
|
+
static inline VALUE eval_str(rapidjson::Value &value) {
|
182
|
+
return new_utf8_str(value.GetString(), value.GetStringLength());
|
183
|
+
}
|
184
|
+
|
185
|
+
static inline VALUE eval_object(rapidjson::Value &value) {
|
186
|
+
VALUE ret = rb_hash_new();
|
187
|
+
for (auto &m : value.GetObject())
|
188
|
+
rb_hash_aset(ret, eval_str(m.name), eval(m.value));
|
189
|
+
return ret;
|
190
|
+
}
|
191
|
+
|
192
|
+
static inline VALUE eval_object_r(rapidjson::Value &value) {
|
193
|
+
VALUE ret = rb_hash_new();
|
194
|
+
for (auto &m : value.GetObject())
|
195
|
+
rb_hash_aset(ret, eval_str(m.name), eval_r(m.value));
|
196
|
+
return ret;
|
197
|
+
}
|
198
|
+
|
199
|
+
static inline VALUE eval_array(rapidjson::Value &value) {
|
200
|
+
VALUE ret = rb_ary_new2(value.Size());
|
201
|
+
for (auto &v : value.GetArray())
|
202
|
+
rb_ary_push(ret, eval(v));
|
203
|
+
return ret;
|
204
|
+
}
|
205
|
+
|
206
|
+
static inline VALUE eval_array_r(rapidjson::Value &value) {
|
207
|
+
VALUE ret = rb_ary_new2(value.Size());
|
208
|
+
for (auto &v : value.GetArray())
|
209
|
+
rb_ary_push(ret, eval_r(v));
|
210
|
+
return ret;
|
211
|
+
}
|
212
|
+
|
213
|
+
|
214
|
+
UsaminValue::UsaminValue(rapidjson::Value *value, bool free_flag) {
|
215
|
+
this->value = value;
|
216
|
+
this->free_flag = free_flag;
|
217
|
+
}
|
218
|
+
|
219
|
+
UsaminValue::~UsaminValue() {
|
220
|
+
if (value && free_flag)
|
221
|
+
delete value;
|
222
|
+
}
|
223
|
+
|
224
|
+
/*
|
225
|
+
* Parse the JSON string into UsaminValue.
|
226
|
+
* If the document root is not an object or an array, the same value as {#parse} will be returned.
|
227
|
+
*
|
228
|
+
* @overload load(source, opts = {})
|
229
|
+
* @param [String] source JSON string to parse
|
230
|
+
* @param [::Hash] opts options
|
231
|
+
* @option opts :fast fast mode (but not precise)
|
232
|
+
* @return [Object]
|
233
|
+
*/
|
234
|
+
static VALUE w_load(const int argc, VALUE *argv, const VALUE self) {
|
235
|
+
VALUE source, options;
|
236
|
+
rb_scan_args(argc, argv, "1:", &source, &options);
|
237
|
+
rapidjson::Document *doc = new rapidjson::Document;
|
238
|
+
auto result = parse(*doc, source, argc > 1 && RTEST(rb_hash_lookup(options, sym_fast)));
|
239
|
+
if (!result) {
|
240
|
+
delete doc;
|
241
|
+
rb_raise(rb_eParserError, "%s Offset: %lu", GetParseError_En(result.Code()), result.Offset());
|
242
|
+
}
|
243
|
+
|
244
|
+
VALUE ret;
|
245
|
+
switch (doc->GetType()) {
|
246
|
+
case rapidjson::kObjectType:
|
247
|
+
return make_hash(new UsaminValue(doc, true));
|
248
|
+
case rapidjson::kArrayType:
|
249
|
+
return make_array(new UsaminValue(doc, true));
|
250
|
+
case rapidjson::kNullType:
|
251
|
+
delete doc;
|
252
|
+
return Qnil;
|
253
|
+
case rapidjson::kFalseType:
|
254
|
+
delete doc;
|
255
|
+
return Qfalse;
|
256
|
+
case rapidjson::kTrueType:
|
257
|
+
delete doc;
|
258
|
+
return Qtrue;
|
259
|
+
case rapidjson::kNumberType:
|
260
|
+
ret = eval_num(*doc);
|
261
|
+
delete doc;
|
262
|
+
return ret;
|
263
|
+
case rapidjson::kStringType:
|
264
|
+
ret = eval_str(*doc);
|
265
|
+
delete doc;
|
266
|
+
return ret;
|
267
|
+
default:
|
268
|
+
rb_raise(rb_eUsaminError, "Unknown Value Type: %d", doc->GetType());
|
269
|
+
return Qnil;
|
270
|
+
}
|
271
|
+
}
|
272
|
+
|
273
|
+
/*
|
274
|
+
* Parse the JSON string into Ruby data structures.
|
275
|
+
*
|
276
|
+
* @overload parse(source, opts = {})
|
277
|
+
* @param [String] source a JSON string to parse
|
278
|
+
* @param [::Hash] opts options
|
279
|
+
* @option opts :fast fast mode (but not precise)
|
280
|
+
* @return [Object]
|
281
|
+
*/
|
282
|
+
static VALUE w_parse(const int argc, VALUE *argv, const VALUE self) {
|
283
|
+
VALUE source, options;
|
284
|
+
rb_scan_args(argc, argv, "1:", &source, &options);
|
285
|
+
rapidjson::Document doc;
|
286
|
+
auto result = parse(doc, source, argc > 1 && RTEST(rb_hash_lookup(options, sym_fast)));
|
287
|
+
if (!result)
|
288
|
+
rb_raise(rb_eParserError, "%s Offset: %lu", GetParseError_En(result.Code()), result.Offset());
|
289
|
+
return eval_r(doc);
|
290
|
+
}
|
291
|
+
|
292
|
+
/*
|
293
|
+
* Returns whether the value is array.
|
294
|
+
*/
|
295
|
+
static VALUE w_value_isarray(const VALUE self) {
|
296
|
+
UsaminValue *value = get_value(self);
|
297
|
+
check_value(value);
|
298
|
+
return value->value->IsArray() ? Qtrue : Qfalse;
|
299
|
+
}
|
300
|
+
|
301
|
+
/*
|
302
|
+
* Returns whether the value is object.
|
303
|
+
*/
|
304
|
+
static VALUE w_value_isobject(const VALUE self) {
|
305
|
+
UsaminValue *value = get_value(self);
|
306
|
+
check_value(value);
|
307
|
+
return value->value->IsObject() ? Qtrue : Qfalse;
|
308
|
+
}
|
309
|
+
|
310
|
+
/*
|
311
|
+
* Convert to Ruby data structures.
|
312
|
+
*
|
313
|
+
* @return [Object]
|
314
|
+
*/
|
315
|
+
static VALUE w_value_eval(const VALUE self) {
|
316
|
+
UsaminValue *value = get_value(self);
|
317
|
+
check_value(value);
|
318
|
+
if (value->value->IsObject())
|
319
|
+
return eval_object(*(value->value));
|
320
|
+
else if (value->value->IsArray())
|
321
|
+
return eval_array(*(value->value));
|
322
|
+
return Qnil;
|
323
|
+
}
|
324
|
+
|
325
|
+
/*
|
326
|
+
* Convert to Ruby data structures recursively.
|
327
|
+
*
|
328
|
+
* @return [Object]
|
329
|
+
*/
|
330
|
+
static VALUE w_value_eval_r(const VALUE self) {
|
331
|
+
UsaminValue *value = get_value(self);
|
332
|
+
check_value(value);
|
333
|
+
return eval_r(*(value->value));
|
334
|
+
}
|
335
|
+
|
336
|
+
/*
|
337
|
+
* Always true.
|
338
|
+
*/
|
339
|
+
static VALUE w_value_isfrozen(const VALUE self) {
|
340
|
+
return Qtrue;
|
341
|
+
}
|
342
|
+
|
343
|
+
template <class Writer> static void write_value(Writer&, rapidjson::Value&);
|
344
|
+
|
345
|
+
/*
|
346
|
+
* Dumps data in JSON.
|
347
|
+
*
|
348
|
+
* @return [String]
|
349
|
+
*/
|
350
|
+
static VALUE w_value_marshal_dump(const VALUE self) {
|
351
|
+
UsaminValue *value = get_value(self);
|
352
|
+
check_value(value);
|
353
|
+
rapidjson::StringBuffer buf;
|
354
|
+
rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
|
355
|
+
write_value(writer, *(value->value));
|
356
|
+
return rb_str_new(buf.GetString(), buf.GetSize());
|
357
|
+
}
|
358
|
+
|
359
|
+
/*
|
360
|
+
* Loads marshal data.
|
361
|
+
*
|
362
|
+
* @return [self]
|
363
|
+
*/
|
364
|
+
static VALUE w_value_marshal_load(const VALUE self, VALUE source) {
|
365
|
+
Check_Type(source, T_STRING);
|
366
|
+
rapidjson::Document *doc = new rapidjson::Document();
|
367
|
+
rapidjson::ParseResult result = doc->Parse<rapidjson::kParseFullPrecisionFlag | rapidjson::kParseNanAndInfFlag>(RSTRING_PTR(source), RSTRING_LEN(source));
|
368
|
+
if (!result) {
|
369
|
+
delete doc;
|
370
|
+
rb_raise(rb_eParserError, "%s Offset: %lu", GetParseError_En(result.Code()), result.Offset());
|
371
|
+
}
|
372
|
+
if (doc->IsObject() || doc->IsArray()) {
|
373
|
+
set_value(self, new UsaminValue(doc, true));
|
374
|
+
return self;
|
375
|
+
}
|
376
|
+
auto type = doc->GetType();
|
377
|
+
delete doc;
|
378
|
+
rb_raise(rb_eUsaminError, "Invalid Value Type for marshal_load: %d", type);
|
379
|
+
return Qnil;
|
380
|
+
}
|
381
|
+
|
382
|
+
|
383
|
+
/*
|
384
|
+
* @overload [](name)
|
385
|
+
* @return [Object | nil]
|
386
|
+
*
|
387
|
+
* @note This method has linear time complexity.
|
388
|
+
*/
|
389
|
+
static VALUE w_hash_operator_indexer(const VALUE self, VALUE key) {
|
390
|
+
UsaminValue *value = get_value(self);
|
391
|
+
check_object(value);
|
392
|
+
VALUE kvalue = get_utf8_str(key);
|
393
|
+
for (auto &m : value->value->GetObject())
|
394
|
+
if (str_compare(RSTRING_PTR(kvalue), RSTRING_LEN(kvalue), m.name.GetString(), m.name.GetStringLength()))
|
395
|
+
return eval(m.value);
|
396
|
+
return Qnil;
|
397
|
+
}
|
398
|
+
|
399
|
+
static VALUE hash_enum_size(const VALUE self, VALUE args, VALUE eobj) {
|
400
|
+
UsaminValue *value = get_value(self);
|
401
|
+
check_object(value);
|
402
|
+
return UINT2NUM(value->value->MemberCount());
|
403
|
+
}
|
404
|
+
|
405
|
+
/*
|
406
|
+
* @yield [key, value]
|
407
|
+
* @yieldparam key [String]
|
408
|
+
* @return [Enumerator | self]
|
409
|
+
*/
|
410
|
+
static VALUE w_hash_each(const VALUE self) {
|
411
|
+
UsaminValue *value = get_value(self);
|
412
|
+
check_object(value);
|
413
|
+
RETURN_SIZED_ENUMERATOR(self, 0, nullptr, hash_enum_size);
|
414
|
+
if (rb_proc_arity(rb_block_proc()) > 1) {
|
415
|
+
for (auto &m : value->value->GetObject()) {
|
416
|
+
VALUE args[] = { eval_str(m.name), eval(m.value) };
|
417
|
+
rb_yield_values2(2, args);
|
418
|
+
}
|
419
|
+
} else {
|
420
|
+
for (auto &m : value->value->GetObject())
|
421
|
+
rb_yield(rb_assoc_new(eval_str(m.name), eval(m.value)));
|
422
|
+
}
|
423
|
+
return self;
|
424
|
+
}
|
425
|
+
|
426
|
+
/*
|
427
|
+
* @yield [key]
|
428
|
+
* @yieldparam key [String]
|
429
|
+
* @return [Enumerator | self]
|
430
|
+
*/
|
431
|
+
static VALUE w_hash_each_key(const VALUE self) {
|
432
|
+
UsaminValue *value = get_value(self);
|
433
|
+
check_object(value);
|
434
|
+
RETURN_SIZED_ENUMERATOR(self, 0, nullptr, hash_enum_size);
|
435
|
+
for (auto &m : value->value->GetObject())
|
436
|
+
rb_yield(eval_str(m.name));
|
437
|
+
return self;
|
438
|
+
}
|
439
|
+
|
440
|
+
/*
|
441
|
+
* @yield [value]
|
442
|
+
* @return [Enumerator | self]
|
443
|
+
*/
|
444
|
+
static VALUE w_hash_each_value(const VALUE self) {
|
445
|
+
UsaminValue *value = get_value(self);
|
446
|
+
check_object(value);
|
447
|
+
RETURN_SIZED_ENUMERATOR(self, 0, nullptr, hash_enum_size);
|
448
|
+
for (auto &m : value->value->GetObject())
|
449
|
+
rb_yield(eval(m.value));
|
450
|
+
return self;
|
451
|
+
}
|
452
|
+
|
453
|
+
static VALUE w_hash_isempty(const VALUE self) {
|
454
|
+
UsaminValue *value = get_value(self);
|
455
|
+
check_object(value);
|
456
|
+
return value->value->MemberCount() == 0 ? Qtrue : Qfalse;
|
457
|
+
}
|
458
|
+
|
459
|
+
/*
|
460
|
+
* @note This method has linear time complexity.
|
461
|
+
*/
|
462
|
+
static VALUE w_hash_haskey(const VALUE self, VALUE name) {
|
463
|
+
UsaminValue *value = get_value(self);
|
464
|
+
check_object(value);
|
465
|
+
VALUE key = get_utf8_str(name);
|
466
|
+
for (auto &m : value->value->GetObject())
|
467
|
+
if (str_compare(RSTRING_PTR(key), RSTRING_LEN(key), m.name.GetString(), m.name.GetStringLength()))
|
468
|
+
return Qtrue;
|
469
|
+
return Qfalse;
|
470
|
+
}
|
471
|
+
|
472
|
+
/*
|
473
|
+
* @return [::Array<String>]
|
474
|
+
*/
|
475
|
+
static VALUE w_hash_keys(const VALUE self) {
|
476
|
+
UsaminValue *value = get_value(self);
|
477
|
+
check_object(value);
|
478
|
+
VALUE ret = rb_ary_new2(value->value->MemberCount());
|
479
|
+
for (auto &m : value->value->GetObject())
|
480
|
+
rb_ary_push(ret, eval_str(m.name));
|
481
|
+
return ret;
|
482
|
+
}
|
483
|
+
|
484
|
+
/*
|
485
|
+
* @return [Integer]
|
486
|
+
*/
|
487
|
+
static VALUE w_hash_length(const VALUE self) {
|
488
|
+
UsaminValue *value = get_value(self);
|
489
|
+
check_object(value);
|
490
|
+
return UINT2NUM(value->value->MemberCount());
|
491
|
+
}
|
492
|
+
|
493
|
+
/*
|
494
|
+
* Convert to Ruby Hash. Same as {Value#eval}.
|
495
|
+
*
|
496
|
+
* @return [::Hash]
|
497
|
+
*/
|
498
|
+
static VALUE w_hash_eval(const VALUE self) {
|
499
|
+
UsaminValue *value = get_value(self);
|
500
|
+
check_object(value);
|
501
|
+
return eval_object(*(value->value));
|
502
|
+
}
|
503
|
+
|
504
|
+
/*
|
505
|
+
* @return [::Array<Object>]
|
506
|
+
*/
|
507
|
+
static VALUE w_hash_values(const VALUE self) {
|
508
|
+
UsaminValue *value = get_value(self);
|
509
|
+
check_object(value);
|
510
|
+
VALUE ret = rb_ary_new2(value->value->MemberCount());
|
511
|
+
for (auto &m : value->value->GetObject())
|
512
|
+
rb_ary_push(ret, eval(m.value));
|
513
|
+
return ret;
|
514
|
+
}
|
515
|
+
|
516
|
+
|
517
|
+
/*
|
518
|
+
* @overload [](nth)
|
519
|
+
* @param [Integer] nth
|
520
|
+
* @return [Object | nil]
|
521
|
+
*
|
522
|
+
* @overload [](start, length)
|
523
|
+
* @param [Integer] start
|
524
|
+
* @param [Integer] length
|
525
|
+
* @return [::Array<Object> | nil]
|
526
|
+
*
|
527
|
+
* @overload [](range)
|
528
|
+
* @param [Range] range
|
529
|
+
* @return [::Array<Object> | nil]
|
530
|
+
*/
|
531
|
+
static VALUE w_array_operator_indexer(const int argc, VALUE* argv, const VALUE self) {
|
532
|
+
rb_check_arity(argc, 1, 2);
|
533
|
+
UsaminValue *value = get_value(self);
|
534
|
+
check_array(value);
|
535
|
+
if (argc == 2) {
|
536
|
+
long beg = NUM2LONG(argv[0]);
|
537
|
+
long len = NUM2LONG(argv[1]);
|
538
|
+
if (beg < 0)
|
539
|
+
beg = beg + value->value->Size();
|
540
|
+
if (beg >= 0 && len >= 0) {
|
541
|
+
long end = beg + len;
|
542
|
+
if (end > value->value->Size())
|
543
|
+
end = value->value->Size();
|
544
|
+
VALUE ret = rb_ary_new2(end - beg);
|
545
|
+
for (long i = beg; i < end; i++)
|
546
|
+
rb_ary_push(ret, eval((*(value->value))[static_cast<unsigned int>(i)]));
|
547
|
+
return ret;
|
548
|
+
}
|
549
|
+
} else if (rb_obj_is_kind_of(argv[0], rb_cRange)) {
|
550
|
+
long beg, len;
|
551
|
+
if (rb_range_beg_len(argv[0], &beg, &len, value->value->Size(), 0) == Qtrue) {
|
552
|
+
VALUE ret = rb_ary_new2(len);
|
553
|
+
for (long i = beg; i < beg + len; i++)
|
554
|
+
rb_ary_push(ret, eval((*(value->value))[static_cast<unsigned int>(i)]));
|
555
|
+
return ret;
|
556
|
+
}
|
557
|
+
} else {
|
558
|
+
long l = NUM2LONG(argv[0]);
|
559
|
+
if (l < 0)
|
560
|
+
l = l + value->value->Size();
|
561
|
+
if (l >= 0) {
|
562
|
+
unsigned int i = static_cast<unsigned int>(l);
|
563
|
+
if (i < value->value->Size())
|
564
|
+
return eval((*(value->value))[i]);
|
565
|
+
}
|
566
|
+
}
|
567
|
+
return Qnil;
|
568
|
+
}
|
569
|
+
|
570
|
+
static VALUE array_enum_size(const VALUE self, VALUE args, VALUE eobj) {
|
571
|
+
UsaminValue *value = get_value(self);
|
572
|
+
check_array(value);
|
573
|
+
return UINT2NUM(value->value->Size());
|
574
|
+
}
|
575
|
+
|
576
|
+
/*
|
577
|
+
* @yield [value]
|
578
|
+
* @return [Enumerator | self]
|
579
|
+
*/
|
580
|
+
static VALUE w_array_each(const VALUE self) {
|
581
|
+
UsaminValue *value = get_value(self);
|
582
|
+
check_array(value);
|
583
|
+
RETURN_SIZED_ENUMERATOR(self, 0, nullptr, array_enum_size);
|
584
|
+
for (auto &v : value->value->GetArray())
|
585
|
+
rb_yield(eval(v));
|
586
|
+
return self;
|
587
|
+
}
|
588
|
+
|
589
|
+
/*
|
590
|
+
* @yield [index]
|
591
|
+
* @yieldparam index [Integer]
|
592
|
+
* @return [Enumerator | self]
|
593
|
+
*/
|
594
|
+
static VALUE w_array_each_index(const VALUE self) {
|
595
|
+
UsaminValue *value = get_value(self);
|
596
|
+
check_array(value);
|
597
|
+
RETURN_SIZED_ENUMERATOR(self, 0, nullptr, array_enum_size);
|
598
|
+
for (rapidjson::SizeType i = 0; i < value->value->Size(); i++)
|
599
|
+
rb_yield(UINT2NUM(i));
|
600
|
+
return self;
|
601
|
+
}
|
602
|
+
|
603
|
+
static VALUE w_array_isempty(const VALUE self) {
|
604
|
+
UsaminValue *value = get_value(self);
|
605
|
+
check_array(value);
|
606
|
+
return value->value->Size() == 0 ? Qtrue : Qfalse;
|
607
|
+
}
|
608
|
+
|
609
|
+
/*
|
610
|
+
* @yield [value]
|
611
|
+
* @return [Integer]
|
612
|
+
*/
|
613
|
+
static VALUE w_array_index(int argc, VALUE* argv, const VALUE self) {
|
614
|
+
rb_check_arity(argc, 0, 1);
|
615
|
+
UsaminValue *value = get_value(self);
|
616
|
+
check_array(value);
|
617
|
+
|
618
|
+
if (argc == 1) {
|
619
|
+
for (rapidjson::SizeType i = 0; i < value->value->Size(); i++) {
|
620
|
+
if (rb_equal(argv[0], eval((*(value->value))[i])) == Qtrue)
|
621
|
+
return UINT2NUM(i);
|
622
|
+
}
|
623
|
+
return Qnil;
|
624
|
+
}
|
625
|
+
|
626
|
+
RETURN_SIZED_ENUMERATOR(self, 0, nullptr, array_enum_size);
|
627
|
+
for (rapidjson::SizeType i = 0; i < value->value->Size(); i++) {
|
628
|
+
if (RTEST(rb_yield(eval((*(value->value))[i]))))
|
629
|
+
return UINT2NUM(i);
|
630
|
+
}
|
631
|
+
return Qnil;
|
632
|
+
}
|
633
|
+
|
634
|
+
/*
|
635
|
+
* @overload first
|
636
|
+
* @return [Object | nil]
|
637
|
+
*
|
638
|
+
* @overload first(n)
|
639
|
+
* @return [::Array<Object>]
|
640
|
+
*/
|
641
|
+
static VALUE w_array_first(const int argc, VALUE* argv, const VALUE self) {
|
642
|
+
rb_check_arity(argc, 0, 1);
|
643
|
+
UsaminValue *value = get_value(self);
|
644
|
+
check_array(value);
|
645
|
+
|
646
|
+
if (argc == 0) {
|
647
|
+
if (value->value->Size() == 0)
|
648
|
+
return Qnil;
|
649
|
+
return eval((*(value->value))[0]);
|
650
|
+
}
|
651
|
+
|
652
|
+
long l = NUM2LONG(argv[0]);
|
653
|
+
if (l < 0)
|
654
|
+
l = 0;
|
655
|
+
else if (l > value->value->Size())
|
656
|
+
l = value->value->Size();
|
657
|
+
unsigned int li = static_cast<unsigned int>(l);
|
658
|
+
VALUE ret = rb_ary_new2(li);
|
659
|
+
for (unsigned int i = 0; i < li; i++)
|
660
|
+
rb_ary_push(ret, eval((*(value->value))[i]));
|
661
|
+
|
662
|
+
return ret;
|
663
|
+
}
|
664
|
+
|
665
|
+
/*
|
666
|
+
* @return [Integer]
|
667
|
+
*/
|
668
|
+
static VALUE w_array_length(const VALUE self) {
|
669
|
+
UsaminValue *value = get_value(self);
|
670
|
+
check_array(value);
|
671
|
+
return UINT2NUM(value->value->Size());
|
672
|
+
}
|
673
|
+
|
674
|
+
/*
|
675
|
+
* Convert to Ruby data structures. Same as {Value#eval}.
|
676
|
+
*
|
677
|
+
* @return [::Array<Object>]
|
678
|
+
*/
|
679
|
+
static VALUE w_array_eval(const VALUE self) {
|
680
|
+
UsaminValue *value = get_value(self);
|
681
|
+
check_array(value);
|
682
|
+
return eval_array(*(value->value));
|
683
|
+
}
|
684
|
+
|
685
|
+
|
686
|
+
template <class Writer> static void write(Writer&, const VALUE);
|
687
|
+
|
688
|
+
template <class Writer> static inline void write_str(Writer &writer, const VALUE value) {
|
689
|
+
VALUE v = get_utf8_str(value);
|
690
|
+
writer.String(RSTRING_PTR(v), static_cast<unsigned int>(RSTRING_LEN(v)));
|
691
|
+
}
|
692
|
+
|
693
|
+
template <class Writer> static inline void write_to_s(Writer &writer, const VALUE value) {
|
694
|
+
write_str(writer, rb_funcall(value, id_to_s, 0));
|
695
|
+
}
|
696
|
+
|
697
|
+
template <class Writer> static inline void write_key_str(Writer &writer, const VALUE value) {
|
698
|
+
VALUE v = get_utf8_str(value);
|
699
|
+
writer.Key(RSTRING_PTR(v), static_cast<unsigned int>(RSTRING_LEN(v)));
|
700
|
+
}
|
701
|
+
|
702
|
+
template <class Writer> static inline void write_key_to_s(Writer &writer, const VALUE value) {
|
703
|
+
write_key_str(writer, rb_funcall(value, id_to_s, 0));
|
704
|
+
}
|
705
|
+
|
706
|
+
template <class Writer> static inline void write_bignum(Writer &writer, const VALUE value) {
|
707
|
+
VALUE v = rb_big2str(value, 10);
|
708
|
+
writer.RawValue(RSTRING_PTR(v), static_cast<unsigned int>(RSTRING_LEN(v)), rapidjson::kNumberType);
|
709
|
+
}
|
710
|
+
|
711
|
+
template <class Writer> static inline int write_hash_each(VALUE key, VALUE value, Writer *writer) {
|
712
|
+
if (RB_TYPE_P(key, T_STRING))
|
713
|
+
write_key_str(*writer, key);
|
714
|
+
else if (RB_TYPE_P(key, T_SYMBOL))
|
715
|
+
write_key_str(*writer, rb_sym_to_s(key));
|
716
|
+
else
|
717
|
+
write_key_to_s(*writer, key);
|
718
|
+
write(*writer, value);
|
719
|
+
return ST_CONTINUE;
|
720
|
+
}
|
721
|
+
|
722
|
+
template <class Writer> static inline void write_hash(Writer &writer, const VALUE hash) {
|
723
|
+
writer.StartObject();
|
724
|
+
rb_hash_foreach(hash, (int (*)(ANYARGS))write_hash_each<Writer>, reinterpret_cast<VALUE>((&writer)));
|
725
|
+
writer.EndObject();
|
726
|
+
}
|
727
|
+
|
728
|
+
template <class Writer> static inline void write_array(Writer &writer, const VALUE value) {
|
729
|
+
writer.StartArray();
|
730
|
+
const VALUE *ptr = rb_array_const_ptr(value);
|
731
|
+
for (long i = 0; i < rb_array_len(value); i++, ptr++)
|
732
|
+
write(writer, *ptr);
|
733
|
+
writer.EndArray();
|
734
|
+
}
|
735
|
+
|
736
|
+
template <class Writer> static inline void write_struct(Writer &writer, const VALUE value) {
|
737
|
+
writer.StartObject();
|
738
|
+
VALUE members = rb_struct_members(value);
|
739
|
+
const VALUE *ptr = rb_array_const_ptr(members);
|
740
|
+
for (long i = 0; i < rb_array_len(members); i++, ptr++) {
|
741
|
+
if (RB_TYPE_P(*ptr, T_SYMBOL))
|
742
|
+
write_key_str(writer, rb_sym_to_s(*ptr));
|
743
|
+
else if (RB_TYPE_P(*ptr, T_STRING))
|
744
|
+
write_key_str(writer, *ptr);
|
745
|
+
else
|
746
|
+
write_key_to_s(writer, *ptr);
|
747
|
+
write(writer, rb_struct_aref(value, *ptr));
|
748
|
+
}
|
749
|
+
writer.EndObject();
|
750
|
+
}
|
751
|
+
|
752
|
+
template <class Writer> static void write_value(Writer &writer, rapidjson::Value &value) {
|
753
|
+
switch (value.GetType()) {
|
754
|
+
case rapidjson::kObjectType:
|
755
|
+
writer.StartObject();
|
756
|
+
for (auto &m : value.GetObject()) {
|
757
|
+
writer.Key(m.name.GetString(), m.name.GetStringLength());
|
758
|
+
write_value(writer, m.value);
|
759
|
+
}
|
760
|
+
writer.EndObject();
|
761
|
+
break;
|
762
|
+
case rapidjson::kArrayType:
|
763
|
+
writer.StartArray();
|
764
|
+
for (auto &v : value.GetArray())
|
765
|
+
write_value(writer, v);
|
766
|
+
writer.EndArray();
|
767
|
+
break;
|
768
|
+
case rapidjson::kNullType:
|
769
|
+
writer.Null();
|
770
|
+
break;
|
771
|
+
case rapidjson::kFalseType:
|
772
|
+
writer.Bool(false);
|
773
|
+
break;
|
774
|
+
case rapidjson::kTrueType:
|
775
|
+
writer.Bool(true);
|
776
|
+
break;
|
777
|
+
case rapidjson::kNumberType:
|
778
|
+
if (value.IsInt())
|
779
|
+
writer.Int(value.GetInt());
|
780
|
+
else if (value.IsUint())
|
781
|
+
writer.Uint(value.GetUint());
|
782
|
+
else if (value.IsInt64())
|
783
|
+
writer.Int64(value.GetInt64());
|
784
|
+
else if (value.IsUint64())
|
785
|
+
writer.Uint64(value.GetUint64());
|
786
|
+
else
|
787
|
+
writer.Double(value.GetDouble());
|
788
|
+
break;
|
789
|
+
case rapidjson::kStringType:
|
790
|
+
writer.String(value.GetString(), value.GetStringLength());
|
791
|
+
break;
|
792
|
+
default:
|
793
|
+
rb_raise(rb_eUsaminError, "Unknown Value Type: %d", value.GetType());
|
794
|
+
}
|
795
|
+
}
|
796
|
+
|
797
|
+
template <class Writer> static inline void write_usamin(Writer &writer, const VALUE self) {
|
798
|
+
UsaminValue *value = get_value(self);
|
799
|
+
check_value(value);
|
800
|
+
write_value(writer, *(value->value));
|
801
|
+
}
|
802
|
+
|
803
|
+
template <class Writer> static void write(Writer &writer, const VALUE value) {
|
804
|
+
if (value == Qnil) {
|
805
|
+
writer.Null();
|
806
|
+
} else if (value == Qfalse) {
|
807
|
+
writer.Bool(false);
|
808
|
+
} else if (value == Qtrue) {
|
809
|
+
writer.Bool(true);
|
810
|
+
} else if (RB_FIXNUM_P(value)) {
|
811
|
+
writer.Int64(FIX2LONG(value));
|
812
|
+
} else if (RB_FLOAT_TYPE_P(value)) {
|
813
|
+
writer.Double(NUM2DBL(value));
|
814
|
+
} else if (RB_STATIC_SYM_P(value)) {
|
815
|
+
write_str(writer, rb_sym_to_s(value));
|
816
|
+
} else {
|
817
|
+
switch (RB_BUILTIN_TYPE(value)) {
|
818
|
+
case T_STRING:
|
819
|
+
write_str(writer, value);
|
820
|
+
break;
|
821
|
+
case T_HASH:
|
822
|
+
write_hash(writer, value);
|
823
|
+
break;
|
824
|
+
case T_ARRAY:
|
825
|
+
write_array(writer, value);
|
826
|
+
break;
|
827
|
+
case T_BIGNUM:
|
828
|
+
write_bignum(writer, value);
|
829
|
+
break;
|
830
|
+
case T_STRUCT:
|
831
|
+
write_struct(writer, value);
|
832
|
+
break;
|
833
|
+
default:
|
834
|
+
if (rb_obj_is_kind_of(value, rb_cUsaminValue))
|
835
|
+
write_usamin(writer, value);
|
836
|
+
else
|
837
|
+
write_to_s(writer, value);
|
838
|
+
break;
|
839
|
+
}
|
840
|
+
}
|
841
|
+
}
|
842
|
+
|
843
|
+
/*
|
844
|
+
* Generate the JSON string from Ruby data structures.
|
845
|
+
*
|
846
|
+
* @overload generate(obj)
|
847
|
+
* @param [Object] obj an object to serialize
|
848
|
+
* @return [String]
|
849
|
+
*/
|
850
|
+
static VALUE w_generate(const VALUE self, VALUE value) {
|
851
|
+
rapidjson::StringBuffer buf;
|
852
|
+
rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
|
853
|
+
write(writer, value);
|
854
|
+
return new_utf8_str(buf.GetString(), buf.GetSize());
|
855
|
+
}
|
856
|
+
|
857
|
+
/*
|
858
|
+
* Generate the prettified JSON string from Ruby data structures.
|
859
|
+
*
|
860
|
+
* @overload generate(obj, opts = {})
|
861
|
+
* @param [Object] obj an object to serialize
|
862
|
+
* @param [::Hash] opts options
|
863
|
+
* @option opts [String] :indent (' ') a string used to indent
|
864
|
+
* @return [String]
|
865
|
+
*/
|
866
|
+
static VALUE w_pretty_generate(const int argc, VALUE *argv, const VALUE self) {
|
867
|
+
VALUE value, options;
|
868
|
+
rb_scan_args(argc, argv, "1:", &value, &options);
|
869
|
+
rapidjson::StringBuffer buf;
|
870
|
+
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buf);
|
871
|
+
|
872
|
+
char indent_char = ' ';
|
873
|
+
unsigned int indent_count = 2;
|
874
|
+
if (argc > 1) {
|
875
|
+
VALUE v_indent = rb_hash_lookup(options, sym_indent);
|
876
|
+
VALUE v_single_line_array = rb_hash_lookup(options, sym_single_line_array);
|
877
|
+
|
878
|
+
if (RTEST(v_indent)) {
|
879
|
+
if (RB_FIXNUM_P(v_indent)) {
|
880
|
+
long l = FIX2LONG(v_indent);
|
881
|
+
indent_count = l > 0 ? static_cast<unsigned int>(l) : 0;
|
882
|
+
} else {
|
883
|
+
VALUE v = get_utf8_str(v_indent);
|
884
|
+
long vlen = RSTRING_LEN(v);
|
885
|
+
if (vlen == 0) {
|
886
|
+
indent_count = 0;
|
887
|
+
} else {
|
888
|
+
const char *indent_str = RSTRING_PTR(v);
|
889
|
+
switch (indent_str[0]) {
|
890
|
+
case '\0':
|
891
|
+
indent_count = 0;
|
892
|
+
break;
|
893
|
+
case ' ':
|
894
|
+
case '\t':
|
895
|
+
case '\r':
|
896
|
+
case '\n':
|
897
|
+
indent_char = indent_str[0];
|
898
|
+
break;
|
899
|
+
default:
|
900
|
+
rb_raise(rb_eUsaminError, ":indent must be a repetation of \" \", \"\\t\", \"\\r\" or \"\\n\".");
|
901
|
+
}
|
902
|
+
|
903
|
+
for (long i = 1; i < vlen; i++)
|
904
|
+
if (indent_str[0] != indent_str[i])
|
905
|
+
rb_raise(rb_eUsaminError, ":indent must be a repetation of \" \", \"\\t\", \"\\r\" or \"\\n\".");
|
906
|
+
indent_count = static_cast<unsigned int>(vlen);
|
907
|
+
}
|
908
|
+
}
|
909
|
+
}
|
910
|
+
|
911
|
+
if (RTEST(v_single_line_array))
|
912
|
+
writer.SetFormatOptions(rapidjson::kFormatSingleLineArray);
|
913
|
+
}
|
914
|
+
writer.SetIndent(indent_char, indent_count);
|
915
|
+
|
916
|
+
write(writer, value);
|
917
|
+
return new_utf8_str(buf.GetString(), buf.GetSize());
|
918
|
+
}
|
919
|
+
|
920
|
+
|
921
|
+
extern "C" void Init_usamin(void) {
|
922
|
+
utf8 = rb_utf8_encoding();
|
923
|
+
utf8index = rb_enc_to_index(utf8);
|
924
|
+
utf8value = rb_enc_from_encoding(utf8);
|
925
|
+
sym_fast = rb_id2sym(rb_intern("fast"));
|
926
|
+
sym_indent = rb_id2sym(rb_intern("indent"));
|
927
|
+
sym_single_line_array = rb_id2sym(rb_intern("single_line_array"));
|
928
|
+
id_to_s = rb_intern("to_s");
|
929
|
+
|
930
|
+
VALUE rb_mUsamin = rb_define_module("Usamin");
|
931
|
+
rb_define_module_function(rb_mUsamin, "load", RUBY_METHOD_FUNC(w_load), -1);
|
932
|
+
rb_define_module_function(rb_mUsamin, "parse", RUBY_METHOD_FUNC(w_parse), -1);
|
933
|
+
rb_define_module_function(rb_mUsamin, "generate", RUBY_METHOD_FUNC(w_generate), 1);
|
934
|
+
rb_define_module_function(rb_mUsamin, "pretty_generate", RUBY_METHOD_FUNC(w_pretty_generate), -1);
|
935
|
+
|
936
|
+
rb_cUsaminValue = rb_define_class_under(rb_mUsamin, "Value", rb_cObject);
|
937
|
+
rb_define_alloc_func(rb_cUsaminValue, usamin_alloc);
|
938
|
+
rb_undef_method(rb_cUsaminValue, "initialize");
|
939
|
+
rb_define_method(rb_cUsaminValue, "array?", RUBY_METHOD_FUNC(w_value_isarray), 0);
|
940
|
+
rb_define_method(rb_cUsaminValue, "hash?", RUBY_METHOD_FUNC(w_value_isobject), 0);
|
941
|
+
rb_define_method(rb_cUsaminValue, "object?", RUBY_METHOD_FUNC(w_value_isobject), 0);
|
942
|
+
rb_define_method(rb_cUsaminValue, "eval", RUBY_METHOD_FUNC(w_value_eval), 0);
|
943
|
+
rb_define_method(rb_cUsaminValue, "eval_r", RUBY_METHOD_FUNC(w_value_eval_r), 0);
|
944
|
+
rb_define_method(rb_cUsaminValue, "frozen?", RUBY_METHOD_FUNC(w_value_isfrozen), 0);
|
945
|
+
rb_define_method(rb_cUsaminValue, "marshal_dump", RUBY_METHOD_FUNC(w_value_marshal_dump), 0);
|
946
|
+
rb_define_method(rb_cUsaminValue, "marshal_load", RUBY_METHOD_FUNC(w_value_marshal_load), 1);
|
947
|
+
|
948
|
+
rb_cUsaminHash = rb_define_class_under(rb_mUsamin, "Hash", rb_cUsaminValue);
|
949
|
+
rb_include_module(rb_cUsaminHash, rb_mEnumerable);
|
950
|
+
rb_define_alloc_func(rb_cUsaminHash, usamin_alloc);
|
951
|
+
rb_undef_method(rb_cUsaminHash, "initialize");
|
952
|
+
rb_define_method(rb_cUsaminHash, "[]", RUBY_METHOD_FUNC(w_hash_operator_indexer), 1);
|
953
|
+
rb_define_method(rb_cUsaminHash, "each", RUBY_METHOD_FUNC(w_hash_each), 0);
|
954
|
+
rb_define_method(rb_cUsaminHash, "each_pair", RUBY_METHOD_FUNC(w_hash_each), 0);
|
955
|
+
rb_define_method(rb_cUsaminHash, "each_key", RUBY_METHOD_FUNC(w_hash_each_key), 0);
|
956
|
+
rb_define_method(rb_cUsaminHash, "each_value", RUBY_METHOD_FUNC(w_hash_each_value), 0);
|
957
|
+
rb_define_method(rb_cUsaminHash, "empty?", RUBY_METHOD_FUNC(w_hash_isempty), 0);
|
958
|
+
rb_define_method(rb_cUsaminHash, "has_key?", RUBY_METHOD_FUNC(w_hash_haskey), 1);
|
959
|
+
rb_define_method(rb_cUsaminHash, "include?", RUBY_METHOD_FUNC(w_hash_haskey), 1);
|
960
|
+
rb_define_method(rb_cUsaminHash, "key?", RUBY_METHOD_FUNC(w_hash_haskey), 1);
|
961
|
+
rb_define_method(rb_cUsaminHash, "member?", RUBY_METHOD_FUNC(w_hash_haskey), 1);
|
962
|
+
rb_define_method(rb_cUsaminHash, "keys", RUBY_METHOD_FUNC(w_hash_keys), 0);
|
963
|
+
rb_define_method(rb_cUsaminHash, "length", RUBY_METHOD_FUNC(w_hash_length), 0);
|
964
|
+
rb_define_method(rb_cUsaminHash, "size", RUBY_METHOD_FUNC(w_hash_length), 0);
|
965
|
+
rb_define_method(rb_cUsaminHash, "to_h", RUBY_METHOD_FUNC(w_hash_eval), 0);
|
966
|
+
rb_define_method(rb_cUsaminHash, "to_hash", RUBY_METHOD_FUNC(w_hash_eval), 0);
|
967
|
+
rb_define_method(rb_cUsaminHash, "values", RUBY_METHOD_FUNC(w_hash_values), 0);
|
968
|
+
|
969
|
+
rb_cUsaminArray = rb_define_class_under(rb_mUsamin, "Array", rb_cUsaminValue);
|
970
|
+
rb_include_module(rb_cUsaminArray, rb_mEnumerable);
|
971
|
+
rb_define_alloc_func(rb_cUsaminArray, usamin_alloc);
|
972
|
+
rb_undef_method(rb_cUsaminArray, "initialize");
|
973
|
+
rb_define_method(rb_cUsaminArray, "[]", RUBY_METHOD_FUNC(w_array_operator_indexer), -1);
|
974
|
+
rb_define_method(rb_cUsaminArray, "each", RUBY_METHOD_FUNC(w_array_each), 0);
|
975
|
+
rb_define_method(rb_cUsaminArray, "each_index", RUBY_METHOD_FUNC(w_array_each_index), 0);
|
976
|
+
rb_define_method(rb_cUsaminArray, "empty?", RUBY_METHOD_FUNC(w_array_isempty), 0);
|
977
|
+
rb_define_method(rb_cUsaminArray, "index", RUBY_METHOD_FUNC(w_array_index), -1);
|
978
|
+
rb_define_method(rb_cUsaminArray, "find_index", RUBY_METHOD_FUNC(w_array_index), -1);
|
979
|
+
rb_define_method(rb_cUsaminArray, "first", RUBY_METHOD_FUNC(w_array_first), -1);
|
980
|
+
rb_define_method(rb_cUsaminArray, "length", RUBY_METHOD_FUNC(w_array_length), 0);
|
981
|
+
rb_define_method(rb_cUsaminArray, "size", RUBY_METHOD_FUNC(w_array_length), 0);
|
982
|
+
rb_define_method(rb_cUsaminArray, "to_a", RUBY_METHOD_FUNC(w_array_eval), 0);
|
983
|
+
rb_define_method(rb_cUsaminArray, "to_ary", RUBY_METHOD_FUNC(w_array_eval), 0);
|
984
|
+
|
985
|
+
rb_eUsaminError = rb_define_class_under(rb_mUsamin, "UsaminError", rb_eStandardError);
|
986
|
+
rb_eParserError = rb_define_class_under(rb_mUsamin, "ParserError", rb_eUsaminError);
|
987
|
+
}
|