usamin 7.7.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/.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
|
+
}
|