diff --git a/.all_images.yml b/.all_images.yml deleted file mode 100644 index 9178beb50..000000000 --- a/.all_images.yml +++ /dev/null @@ -1,17 +0,0 @@ -dockerfile: |- - RUN apk add --no-cache build-base git ragel - RUN gem update --system - RUN gem install gem_hadar bundler -script: &script |- - echo -e "\e[1m" - ruby -v - echo -e "\e[0m" - bundle - rake clean test - -images: - ruby:3.1-alpine: *script - ruby:3.0-alpine: *script - ruby:2.7-alpine: *script - ruby:2.6-alpine: *script - ruby:2.5-alpine: *script diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9574d30c..1d8d18e21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,46 +14,49 @@ jobs: host: needs: ruby-versions - name: ${{ matrix.os }} ${{ matrix.ruby }} + name: ${{ matrix.os }} ${{ matrix.ruby }} ${{ matrix.env }} runs-on: ${{ matrix.os }} + env: + BUNDLE_WITHOUT: release strategy: fail-fast: false matrix: os: - - ubuntu-latest - - macos-14 - - windows-latest + - ubuntu-latest + - macos-latest + - windows-latest ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} + env: + - "" include: - - { os: ubuntu-24.04-arm, ruby: 3.4 } - - { os: macos-13, ruby: 3.4 } - - { os: windows-latest , ruby: mswin } # ruby/ruby windows CI - - { os: ubuntu-latest , ruby: jruby-9.4 } # Ruby 3.1 - - { os: macos-latest , ruby: truffleruby-head } - - { os: ubuntu-latest , ruby: truffleruby-head } + - { os: ubuntu-latest , ruby: debug } + - { os: ubuntu-latest , ruby: asan } + - { os: ubuntu-24.04-arm, ruby: 3.4 } + - { os: ubuntu-latest , ruby: 3.4, env: "JSON_DISABLE_SIMD=1" } + - { os: ubuntu-latest , ruby: 3.4, env: "JSON_DEBUG=1" } + - { os: macos-15, ruby: 3.4 } + - { os: windows-latest , ruby: mswin } # ruby/ruby windows CI + - { os: macos-latest , ruby: jruby-9.4 } # Ruby 3.1 + - { os: ubuntu-latest , ruby: jruby-9.4 } # Ruby 3.1 + - { os: macos-latest , ruby: truffleruby } + - { os: ubuntu-latest , ruby: truffleruby } exclude: - - { os: windows-latest, ruby: jruby } - - { os: windows-latest, ruby: jruby-head } + - { os: windows-latest, ruby: jruby } steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Set up Ruby uses: ruby/setup-ruby-pkgs@v1 with: + bundler-cache: true ruby-version: ${{ matrix.ruby }} - apt-get: "${{ startsWith(matrix.ruby, 'jruby') && 'ragel' || '' }}" - brew: "${{ startsWith(matrix.ruby, 'jruby') && 'ragel' || '' }}" - - run: | - bundle config --without benchmark - bundle install + - run: bundle exec rake compile ${{ matrix.env }} - - run: rake compile + - run: bundle exec rake test JSON_COMPACT=1 ${{ matrix.env }} - - run: rake test JSON_COMPACT=1 - - - run: rake build + - run: bundle exec rake build - run: gem install pkg/*.gem if: ${{ matrix.ruby != '3.2' }} @@ -65,18 +68,15 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Set up Ruby uses: ruby/setup-ruby-pkgs@v1 with: + bundler-cache: true ruby-version: "3.3" apt-get: valgrind - - run: | - bundle config --without benchmark - bundle install - - - run: rake compile + - run: bundle exec rake compile - - run: rake valgrind JSON_COMPACT=1 + - run: bundle exec rake valgrind JSON_COMPACT=1 diff --git a/.github/workflows/sync-ruby.yml b/.github/workflows/sync-ruby.yml new file mode 100644 index 000000000..d6d95946a --- /dev/null +++ b/.github/workflows/sync-ruby.yml @@ -0,0 +1,33 @@ +name: Sync ruby +on: + push: + branches: [master] +jobs: + sync: + name: Sync ruby + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'ruby' }} + steps: + - uses: actions/checkout@v7 + + - name: Create GitHub App token + id: app-token + uses: actions/create-github-app-token@v3 + with: + app-id: 2060836 + private-key: ${{ secrets.RUBY_SYNC_DEFAULT_GEMS_PRIVATE_KEY }} + owner: ruby + repositories: ruby + + - name: Sync to ruby/ruby + uses: convictional/trigger-workflow-and-wait@v1.6.5 + with: + owner: ruby + repo: ruby + workflow_file_name: sync_default_gems.yml + github_token: ${{ steps.app-token.outputs.token }} + ref: master + client_payload: | + {"gem":"${{ github.event.repository.name }}","before":"${{ github.event.before }}","after":"${{ github.event.after }}"} + propagate_failure: true + wait_interval: 10 diff --git a/.gitignore b/.gitignore index 8ae6ac119..5865b9834 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.bundle.dSYM *.so coverage +.coverage tags pkg .nfs.* @@ -21,4 +22,4 @@ Gemfile.lock .byebug_history *.log /tmp - +/doc/ diff --git a/CHANGES.md b/CHANGES.md index d2ed40525..2bad52aed 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,305 @@ # Changes +### Unreleased + +* Fix `JSON::ParserError#json_path` raising `NoMethodError` when the document was parsed with `symbolize_names: true`. +* Fix `JSON.generate` not detecting duplicate keys such as `{ "1" => 1, 1 => 2 }`. +* JRuby: raise `TypeError` when a String subclass used as a Hash key has a `to_s` that doesn't return a String, like the C extension. + +### 2026-10-13 (3.1.0) + +* Added `rfc8785` generator option to canonicalize the output. Replaces the `json-canonicalization` with numerous correctness fixes. +* Use the `rb_len_t` type introduced in Ruby 4.1 to allow for proper 64bit support on LP64 platforms (Windows). +* JRuby: parser errors now include the position (`line`, `column` and the message suffix) as well as `json_path`, matching the C extension. +* Improved the accuracy of parser error's `line`, `column` and snipets in presence of multibyte characters. +* Fix `JSON.load` to work correctly when provided a `Zlib::GzipReader` as IO source. +* Reject non-ASCII BINARY strings during JSON generation. This was deprecated since a long time and scheduled to be removed for 3.0. + +### 2026-09-09 (3.0.2) + +* Fix `JSON.load_file` and `JSON.load_file!` to load on Ruby 2.7.0 through 2.7.2, which cannot parse a leading parameter before `...`. + +### 2026-09-08 (3.0.1) + +* Restore the `limit` positional argument of `JSON.dump`. + +### 2026-09-07 (3.0.0) + +* Add `JSON::ParserError#json_path` to locate parse errors in the document as a JSONPath-style string (e.g. `$.foo[0].bar`). For duplicate key errors it points at the duplicated key itself. +* Fix the parser to also reject lone trailing UTF-16 surrogates (`\uDCxx` with no leading partner), symmetric to the leading-surrogate case. The Java parser already rejected these; this closes the CRuby/JRuby parity gap. + +### 2026-08-11 (3.0.0.rc1) + +With the removal of the insecure `create_additions` option, `JSON.load` and `JSON.dump` are +now safe to use. Them being unsafe by default caused multiple security vulnerabilites in the past. + +If you did depend on `create_additions`, the recommended migration is to [implement a custom serializer using +`JSON::Coder`](https://byroot.github.io/ruby/json/2025/08/02/whats-wrong-with-the-json-gem-api.html#the-create_additions-option). + +All the mutable default options, such as `JSON.load_default_options` have been removed. +They were preventing Ractor compatiblity, and causing bug in libraries using JSON expecting the default behavior. +`JSON` methods now always behave the same unless monkey patched. + +All methods options are now either keyword arguments or checked like keyword arguments, meaning +unknown options such as typos raise `ArgumentError`. + +Duplicated keys are now rejected by default. + +JavaScript comments in documents are no longer supported by default. + +Numerous rarely used aliases have been removed. + +* `JSON.load` defaults are now safe to use. +* All unknown options will now cause an `ArgumentError` rather than to be ignored. +* The `allow_comments` parsing option now defaults to `false`. +* The `allow_duplicate_key` option now defaults to `false`, for both parsing and generating JSON. +* Removed the `limit` positional argument of `JSON.dump`. +* Removed the `escape_slash` alias of `script_safe`. +* Removed `Kernel#j` and `Kernel#jj`. +* Removed `JSON.load_default_options`. +* Removed `JSON.unsafe_load_default_options`. +* Removed `JSON.dump_default_options`. +* Removed `JSON::State#[]` and `JSON::State#[]=`. +* Removed `JSON.unparse`. +* Removed `JSON.fast_generate`. +* Removed `JSON.fast_unparse`. +* Removed `JSON.pretty_unparse`. +* Removed `JSON.restore`. +* Removed `JSON::PRETTY_STATE_PROTOTYPE`. +* Removed the insecure `create_additions` option. +* Removed `JSON::GenericObject`. + +### 2026-07-31 (2.21.2) + +* Fix a use-after-free bug in `JSON::ResumableParser`. [GHSA-9hj4-r449-hfvc][CVE-2026-71847]. + +### 2026-07-13 (2.21.1) + +* Fix a compilation issue on Window and Microsoft Visual C++. + +### 2026-07-12 (2.21.0) + +* `JSON.generate` now accept a `sort_keys` option, which takes either a boolean or a block. +* Added `#empty?` and `#partial_value?` methods on `JSON::ResumableParser`. +* Numerous correctness and performance fixes for `JSON::ResumableParser`. +* Avoid triggering Ruby's `float out of range` warning when parsing out of range numbers. +* Declare C types with Ruby 4.1 `RUBY_TYPED_THREAD_SAFE_FREE`. + +### 2026-06-23 (2.20.0) + +* Both C and Java parsers are no longer recursive, so parsing very deep documents with `max_nesting: false` will no longer + result in `SystemStackError stack level too deep` errors. + * The `:max_nesting` option still defaults to `100`. +* Optimized floating point number parsing further by replacing the ryu algorithm by a port of Eisel-Lemire Fast Float. +* Added `JSON::ResumableParser` to parse streams of JSON documents. Not yet available on JRuby. +* Deprecate default support of JavaScript comments in the parser and add `allow_comments: true` parsing option. +* Integrate with Ruby 4.1 `ruby_sized_xfree`. + +### 2026-06-11 (2.19.9) + +* Fix buffer overflow that could lead to a crash when writing JSON directly into an IO + with `JSON.generate(object, io)`. [CVE-2026-54696]. + +### 2026-06-03 (2.19.8) + +* Fix 1-byte buffer overread on EOS errors. +* Handle invalid types passed as `max_nesting` option. + +### 2026-05-28 (2.19.7) + +* Fix some more edge cases with out of range floats. +* Ensure the string provided to `JSON.parse` can't be mutated during parsing. +* Add missing write barriers in `State#dup`. +* Further validate generator `depth` config. + +### 2026-05-28 (2.19.6) + +* Cleanly handle overly large `depth` generator argument. +* Add missing write barrier in `ParserConfig`. + +### 2026-05-04 (2.19.5) + +* Cap the parser to emit a maximum of 5 deprecation warnings per document. Emitting more is not helpful. + +### 2026-04-19 (2.19.4) + +* Fix parsing of out of range floats (very large exponents that lead to either `0.0` or `Inf`). + +### 2026-03-25 (2.19.3) + +* Fix handling of unescaped control characters preceeded by a backslash. + +### 2026-03-18 (2.19.2) + +* Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`. + +### 2026-03-08 (2.19.1) + +* Fix a compiler dependent GC bug introduced in `2.18.0`. + +### 2026-03-06 (2.19.0) + +* Fix `allow_blank` parsing option to no longer allow invalid types (e.g. `load([], allow_blank: true)` now raise a type error). +* Add `allow_invalid_escape` parsing option to ignore backslashes that aren't followed by one of the valid escape characters. + +### 2026-11-02 (2.18.2) + +* Backport handling of unescaped control characters preceeded by a backslash from 2.19.3. + GHSA-cjpr-jw2g-fw99. + +### 2026-02-03 (2.18.1) + +* Fix a potential crash in very specific circumstance if GC triggers during a call to `to_json` + without first invoking a user defined `#to_json` method. + +### 2025-12-11 (2.18.0) + +* Add `:allow_control_characters` parser options, to allow JSON strings containing unescaped ASCII control characters (e.g. newlines). + +### 2026-03-18 (2.17.1.2) - Security Backport + +* Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`. + +### 2025-12-04 (2.17.1) + +* Fix a regression in parsing of unicode surogate pairs (`\uXX\uXX`) that could cause an invalid string to be returned. + +### 2025-12-03 (2.17.0) + +* Improve `JSON.load` and `JSON.unsafe_load` to allow passing options as second argument. +* Fix the parser to no longer ignore invalid escapes in strings. + Only `\"`, `\\`, `\b`, `\f`, `\n`, `\r`, `\t` and `\u` are valid JSON escapes. +* Fixed `JSON::Coder` to use the depth it was initialized with. +* On TruffleRuby, fix the generator to not call `to_json` on the return value of `as_json` for `Float::NAN`. +* Fixed handling of `state.depth`: when `to_json` changes `state.depth` but does not restore it, it is reset + automatically to its initial value. + In particular, when a `NestingError` is raised, `depth` is no longer equal to `max_nesting` after the call to + generate, and is reset to its initial value. Similarly when `to_json` raises an exception. + +### 2025-11-07 (2.16.0) + +* Deprecate `JSON::State#[]` and `JSON::State#[]=`. Consider using `JSON::Coder` instead. +* `JSON::Coder` now also yields to the block when encountering strings with invalid encoding. +* Fix GeneratorError messages to be UTF-8 encoded. +* Fix memory leak when `Exception` is raised, or `throw` is used during JSON generation. +* Optimized floating point number parsing by integrating the ryu algorithm (thanks to Josef Šimánek). +* Optimized numbers parsing using SWAR (thanks to Scott Myron). +* Optimized parsing of pretty printed documents using SWAR (thanks to Scott Myron). + +### 2026-03-18 (2.15.2.1) - Security Backport + +* Fix a format string injection vulnerability in `JSON.parse(doc, allow_duplicate_key: false)`. `CVE-2026-33210`. + +### 2025-10-25 (2.15.2) + +* Fix `JSON::Coder` to have one dedicated depth counter per invocation. + After encountering a circular reference in `JSON::Coder#dump`, any further `#dump` call would raise `JSON::NestingError`. + +### 2025-10-07 (2.15.1) + +* Fix incorrect escaping in the JRuby extension when encoding shared strings. + +### 2025-09-22 (2.15.0) + +* `JSON::Coder` callback now receive a second argument to convey whether the object is a hash key. +* Tuned the floating point number generator to not use scientific notation as aggressively. + +### 2025-09-18 (2.14.1) + +* Fix `IndexOutOfBoundsException` in the JRuby extension when encoding shared strings. + +### 2025-09-18 (2.14.0) + +* Add new `allow_duplicate_key` generator options. By default a warning is now emitted when a duplicated key is encountered. + In `json 3.0` an error will be raised. + ```ruby + >> Warning[:deprecated] = true + >> puts JSON.generate({ foo: 1, "foo" => 2 }) + (irb):2: warning: detected duplicate key "foo" in {foo: 1, "foo" => 2}. + This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true` + {"foo":1,"foo":2} + >> JSON.generate({ foo: 1, "foo" => 2 }, allow_duplicate_key: false) + detected duplicate key "foo" in {foo: 1, "foo" => 2} (JSON::GeneratorError) + ``` +* Fix `JSON.generate` `strict: true` mode to also restrict hash keys. +* Fix `JSON::Coder` to also invoke block for hash keys that aren't strings nor symbols. +* Fix `JSON.unsafe_load` usage with proc +* Fix the parser to more consistently reject invalid UTF-16 surogate pairs. +* Stop defining `String.json_create`, `String#to_json_raw`, `String#to_json_raw_object` when `json/add` isn't loaded. + +### 2025-07-28 (2.13.2) + +* Improve duplicate key warning and errors to include the key name and point to the right caller. + +### 2025-07-24 (2.13.1) + +* Fix support for older compilers without `__builtin_cpu_supports`. + +### 2025-07-17 (2.13.0) + +* Add new `allow_duplicate_key` parsing options. By default a warning is now emitted when a duplicated key is encountered. + In `json 3.0` an error will be raised. +* Optimize parsing further using SIMD to scan strings. + +### 2025-05-23 (2.12.2) + +* Fix compiler optimization level. + +### 2025-05-23 (2.12.1) + +* Fix a potential crash in large negative floating point number generation. +* Fix for JSON.pretty_generate to use passed state object's generate instead of state class as the required parameters aren't available. + +### 2025-05-12 (2.12.0) + +* Improve floating point generation to not use scientific notation as much. +* Include line and column in parser errors. Both in the message and as exception attributes. +* Handle non-string hash keys with broken `to_s` implementations. +* `JSON.generate` now uses SSE2 (x86) or NEON (arm64) instructions when available to escape strings. + +### 2025-04-25 (2.11.3) + +* Fix a regression in `JSON.pretty_generate` that could cause indentation to be off once some `#to_json` has been called. + +### 2025-04-24 (2.11.2) + +* Add back `JSON::PRETTY_STATE_PROTOTYPE`. This constant was private API but is used by popular gems like `multi_json`. + It now emits a deprecation warning. + +### 2025-04-24 (2.11.1) + +* Add back `JSON.restore`, `JSON.unparse`, `JSON.fast_unparse` and `JSON.pretty_unparse`. + These were deprecated 16 years ago, but never emitted warnings, only undocumented, so are + still used by a few gems. + +### 2025-04-24 (2.11.0) + +* Optimize Integer generation to be ~1.8x faster. +* Optimize Float generation to be ~10x faster. +* Fix `JSON.load` proc argument to substitute the parsed object with the return value. + This better match `Marshal.load` behavior. +* Deprecate `JSON.fast_generate` (it's not any faster, so pointless). +* Deprecate `JSON.load_default_options`. +* Deprecate `JSON.unsafe_load_default_options`. +* Deprecate `JSON.dump_default_options`. +* Deprecate `Kernel#j` +* Deprecate `Kernel#jj` +* Remove outdated `JSON.iconv`. +* Remove `Class#json_creatable?` monkey patch. +* Remove deprecated `JSON.restore` method. +* Remove deprecated `JSON.unparse` method. +* Remove deprecated `JSON.fast_unparse` method. +* Remove deprecated `JSON.pretty_unparse` method. +* Remove deprecated `JSON::UnparserError` constant. +* Remove outdated `JSON::MissingUnicodeSupport` constant. + +### 2025-03-12 (2.10.2) + +* Fix a potential crash in the C extension parser. +* Raise a ParserError on all incomplete unicode escape sequence. This was the behavior until `2.10.0` inadvertently changed it. * Ensure document snippets that are included in parser errors don't include truncated multibyte characters. +* Ensure parser error snippets are valid UTF-8. +* Fix `JSON::GeneratorError#detailed_message` on Ruby < 3.2 ### 2025-02-10 (2.10.1) @@ -28,7 +327,7 @@ ### 2024-11-14 (2.8.2) -* `JSON.load_file` explictly read the file as UTF-8. +* `JSON.load_file` explicitly read the file as UTF-8. ### 2024-11-06 (2.8.1) @@ -36,7 +335,7 @@ ### 2024-11-06 (2.8.0) -* Emit a deprecation warning when `JSON.load` create custom types without the `create_additions` option being explictly enabled. +* Emit a deprecation warning when `JSON.load` create custom types without the `create_additions` option being explicitly enabled. * Prefer to use `JSON.unsafe_load(string)` or `JSON.load(string, create_additions: true)`. * Emit a deprecation warning when serializing valid UTF-8 strings encoded in `ASCII_8BIT` aka `BINARY`. * Bump required Ruby version to 2.7. @@ -44,7 +343,7 @@ pre-existing support for comments, make it suitable to parse `jsonc` documents. * Many performance improvements to `JSON.parse` and `JSON.load`, up to `1.7x` faster on real world documents. * Some minor performance improvements to `JSON.dump` and `JSON.generate`. -* `JSON.pretty_generate` no longer include newline inside empty object and arrays. +* `JSON.pretty_generate` no longer includes newlines inside empty object and arrays. ### 2024-11-04 (2.7.6) @@ -61,13 +360,13 @@ * Workaround a bug in 3.4.8 and older https://github.com/rubygems/rubygems/pull/6490. This bug would cause some gems with native extension to fail during compilation. * Workaround different versions of `json` and `json_pure` being loaded (not officially supported). -* Make `json_pure` Ractor compatible. +* Make `json_pure` Ractor compatible. ### 2024-10-24 (2.7.3) * Numerous performance optimizations in `JSON.generate` and `JSON.dump` (up to 2 times faster). -* Limit the size of ParserError exception messages, only include up to 32 bytes of the unparseable source. -* Fix json-pure's `Object#to_json` to accept non state arguments +* Limit the size of ParserError exception messages, only include up to 32 bytes of the unparsable source. +* Fix json-pure's `Object#to_json` to accept non-state arguments. * Fix multiline comment support in `json-pure`. * Fix `JSON.parse` to no longer mutate the argument encoding when passed an ASCII-8BIT string. * Fix `String#to_json` to raise on invalid encoding in `json-pure`. @@ -212,6 +511,7 @@ ## 2015-09-11 (2.0.0) * Now complies to newest JSON RFC 7159. * Implements compatibility to ruby 2.4 integer unification. + * Removed support for `quirks_mode` option. * Drops support for old rubies whose life has ended, that is rubies < 2.0. Also see https://www.ruby-lang.org/en/news/2014/07/01/eol-for-1-8-7-and-1-9-2/ * There were still some mentions of dual GPL licensing in the source, but JSON diff --git a/Gemfile b/Gemfile index 98956c7e2..407a3f9b0 100644 --- a/Gemfile +++ b/Gemfile @@ -4,18 +4,28 @@ gemspec group :development do gem "ruby_memcheck" if RUBY_PLATFORM =~ /linux/i - gem "ostruct" + gem "bigdecimal" gem "rake" gem "rake-compiler" gem "test-unit" gem "test-unit-ruby-core" - gem "all_images", "~> 0" unless RUBY_PLATFORM =~ /java/ + gem "simplecov", require: false - if ENV['BENCHMARK'] - gem "benchmark-ips" - unless RUBY_PLATFORM =~ /java/ - gem "oj" - gem "rapidjson" - end + if RUBY_VERSION < "3.1." + # Ref: https://github.com/test-unit/test-unit/pull/329 + gem 'power_assert', '< 3.0.0' + end +end + +group :release do + gem "net-http" + gem "uri" +end + +if ENV['BENCHMARK'] + gem "benchmark-ips" + unless RUBY_PLATFORM =~ /java/ + gem "oj" + gem "rapidjson" end end diff --git a/LEGAL b/LEGAL index 737d18cb8..3d3e0935a 100644 --- a/LEGAL +++ b/LEGAL @@ -6,3 +6,15 @@ All the files in this distribution are covered under either the Ruby's license (see the file COPYING) or public-domain except some files mentioned below. + +ext/json/ext/vendor/fpconv.h:: + This file is adapted from https://github.com/night-shift/fpconv + It is licensed under Boost Software License 1.0. + +ext/json/ext/vendor/jeaiii-ltoa.h:: + This file is adapted from https://github.com/jeaiii/itoa + It is licensed under the MIT License + +ext/json/ext/vendor/fast_float_parser.h:: + This file is adapted from the Fast Float C++ library by The fast_float authors https://github.com/fastfloat/fast_float + It is licensed under the MIT License diff --git a/README-json-jruby.md b/README-json-jruby.md deleted file mode 100644 index a66ebd621..000000000 --- a/README-json-jruby.md +++ /dev/null @@ -1,32 +0,0 @@ -JSON-JRuby -========== - -JSON-JRuby is a port of Florian Frank's native -[`json` library](http://json.rubyforge.org/) to JRuby. - - -Development version -=================== - -The latest version is available from the -[Git repository](http://github.com/mernen/json-jruby/tree): - - git clone git://github.com/mernen/json-jruby.git - - -Compiling -========= - -You'll need JRuby version 1.2 or greater to build JSON-JRuby. -Its path must be set on the `jruby.dir` property of -`nbproject/project.properties` (defaults to `../jruby`). - -Additionally, you'll need [Ant](http://ant.apache.org/), and -[Ragel](http://www.colm.net/open-source/ragel/) 6.4 or greater. - -Then, from the folder where the sources are located, type: - - ant clean jar - -to clean any leftovers from previous builds and generate the `.jar` files. -To generate a RubyGem, specify the `gem` action rather than `jar`. diff --git a/README.md b/README.md index d327f74a1..ab550ad7c 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,7 @@ UTF-16 surrogate pairs in order to be able to generate the whole range of unicode code points. All strings, that are to be encoded as JSON strings, should be UTF-8 byte -sequences on the Ruby side. To encode raw binary strings, that aren't UTF-8 -encoded, please use the to\_json\_raw\_object method of String (which produces -an object, that contains a byte array) and decode the result on the receiving -endpoint. +sequences on the Ruby side. ## Installation @@ -49,8 +46,7 @@ JSON.generate(data) ``` You can also use the `pretty_generate` method (which formats the output more -verbosely and nicely) or `fast_generate` (which doesn't do any of the security -checks generate performs, e. g. nesting deepness checks). +verbosely and nicely). ## Casting non native types @@ -85,7 +81,7 @@ Both of these behavior can be disabled using the `strict: true` option: ```ruby JSON.generate(Object.new, strict: true) # => Object not allowed in JSON (JSON::GeneratorError) -JSON.generate(Position.new(1, 2)) # => Position not allowed in JSON (JSON::GeneratorError) +JSON.generate(Position.new(1, 2), strict: true) # => Position not allowed in JSON (JSON::GeneratorError) ``` ## JSON::Coder @@ -97,7 +93,7 @@ Instead it is recommended to use the newer `JSON::Coder` API: ```ruby module MyApp - API_JSON_CODER = JSON::Coder.new do |object| + API_JSON_CODER = JSON::Coder.new do |object, is_object_key| case object when Time object.iso8601(3) @@ -113,96 +109,42 @@ puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z" The provided block is called for all objects that don't have a native JSON equivalent, and must return a Ruby object that has a native JSON equivalent. -## Combining JSON fragments - -To combine JSON fragments into a bigger JSON document, you can use `JSON::Fragment`: - -```ruby -posts_json = cache.fetch_multi(post_ids) do |post_id| - JSON.generate(Post.find(post_id)) -end -posts_json.map! { |post_json| JSON::Fragment.new(post_json) } -JSON.generate({ posts: posts_json, count: posts_json.count }) -``` - -## Round-tripping arbitrary types - -> [!CAUTION] -> You should never use `JSON.unsafe_load` nor `JSON.parse(str, create_additions: true)` to parse untrusted user input, -> as it can lead to remote code execution vulnerabilities. - -To create a JSON document from a ruby data structure, you can call -`JSON.generate` like that: - -```ruby -json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10] -# => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]" -``` - -To get back a ruby data structure from a JSON document, you have to call -JSON.parse on it: +It is also called for objects that do have a JSON equivalent, but are used as Hash keys, for instance `{ 1 => 2}`, +as well as for strings that aren't valid UTF-8: ```ruby -JSON.parse json -# => [1, 2, {"a"=>3.141}, false, true, nil, "4..10"] -``` - -Note, that the range from the original data structure is a simple -string now. The reason for this is, that JSON doesn't support ranges -or arbitrary classes. In this case the json library falls back to call -`Object#to_json`, which is the same as `#to_s.to_json`. - -It's possible to add JSON support serialization to arbitrary classes by -simply implementing a more specialized version of the `#to_json method`, that -should return a JSON object (a hash converted to JSON with `#to_json`) like -this (don't forget the `*a` for all the arguments): - -```ruby -class Range - def to_json(*a) - { - 'json_class' => self.class.name, # = 'Range' - 'data' => [ first, last, exclude_end? ] - }.to_json(*a) +coder = JSON::Coder.new do |object, is_object_key| + case object + when String + if !object.valid_encoding? || object.encoding != Encoding::UTF_8 + Base64.encode64(object) + else + object + end + else + object end end ``` -The hash key `json_class` is the class, that will be asked to deserialise the -JSON representation later. In this case it's `Range`, but any namespace of -the form `A::B` or `::A::B` will do. All other keys are arbitrary and can be -used to store the necessary data to configure the object to be deserialised. +## Combining JSON fragments -If the key `json_class` is found in a JSON object, the JSON parser checks -if the given class responds to the `json_create` class method. If so, it is -called with the JSON object converted to a Ruby hash. So a range can -be deserialised by implementing `Range.json_create` like this: +To combine JSON fragments into a bigger JSON document, you can use `JSON::Fragment`: ```ruby -class Range - def self.json_create(o) - new(*o['data']) - end +posts_json = cache.fetch_multi(post_ids) do |post_id| + JSON.generate(Post.find(post_id)) end +posts_json.map! { |post_json| JSON::Fragment.new(post_json) } +JSON.generate({ posts: posts_json, count: posts_json.count }) ``` -Now it possible to serialise/deserialise ranges as well: - -```ruby -json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10] -# => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]" -JSON.parse json -# => [1, 2, {"a"=>3.141}, false, true, nil, 4..10] -json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10] -# => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]" -JSON.unsafe_load json -# => [1, 2, {"a"=>3.141}, false, true, nil, 4..10] -``` +## Pretty Printing `JSON.generate` always creates the shortest possible string representation of a ruby data structure in one line. This is good for data storage or network protocols, but not so good for humans to read. Fortunately there's also -`JSON.pretty_generate` (or `JSON.pretty_generate`) that creates a more readable +`JSON.pretty_generate` that creates a more readable output: ```ruby @@ -227,12 +169,32 @@ output: ] ``` -There are also the methods `Kernel#j` for generate, and `Kernel#jj` for -`pretty_generate` output to the console, that work analogous to Core Ruby's `p` and -the `pp` library's `pp` methods. +## Security + +When parsing or serializing untrusted input, parser and generator options should never be user controlled. + +```ruby +# Dangerous, DO NOT DO THIS. +JSON.generate(params[:data], params[:options]) +``` + +Security vulnerability reports relying on attacker controlled parsing or generator options will be handled as regular bug fixes. ## Development +### Prerequisites + +1. Clone the repository +2. Install dependencies with `bundle install` + +### Testing + +The full test suite can be run with: + +```bash +bundle exec rake test +``` + ### Release Update the `lib/json/version.rb` file. @@ -264,5 +226,3 @@ The latest version of this library can be downloaded at Online Documentation should be located at * https://www.rubydoc.info/gems/json - -[Ragel]: http://www.colm.net/open-source/ragel/ diff --git a/Rakefile b/Rakefile index 5fc7fa6dc..db8ea3ac4 100644 --- a/Rakefile +++ b/Rakefile @@ -6,48 +6,98 @@ include RbConfig require 'rake/testtask' class UndocumentedTestTask < Rake::TestTask def desc(*) end + + if RUBY_ENGINE != "jruby" + def ruby(...) + env_was = ENV["JSON_COVERAGE"] + ENV["JSON_COVERAGE"] = "1" + ret = super + ENV["JSON_COVERAGE"] = env_was + ret + end + end end PKG_VERSION = File.foreach(File.join(__dir__, "lib/json/version.rb")) do |line| /^\s*VERSION\s*=\s*'(.*)'/ =~ line and break $1 end rescue nil -JAVA_DIR = "java/src/json/ext" -JAVA_RAGEL_PATH = "#{JAVA_DIR}/ParserConfig.rl" -JAVA_PARSER_SRC = "#{JAVA_DIR}/ParserConfig.java" -JAVA_SOURCES = FileList["#{JAVA_DIR}/*.java"] -JAVA_CLASSES = [] -JRUBY_PARSER_JAR = File.expand_path("lib/json/ext/parser.jar") -JRUBY_GENERATOR_JAR = File.expand_path("lib/json/ext/generator.jar") +module CI + extend self -which = lambda { |c| - w = `which #{c}` - break w.chomp unless w.empty? -} + def gem_published?(name, version) + require 'net/https' + require 'uri' + uri = URI.parse("https://rubygems.org/api/v2/rubygems/#{name}/versions/#{version}.json") + Net::HTTP.get_response(uri).is_a?(Net::HTTPSuccess) + end -if RUBY_PLATFORM =~ /mingw|mswin/ - # cleans up Windows CI output - RAGEL_CODEGEN = %w[ragel].find(&which) - RAGEL_DOTGEN = %w[ragel].find(&which) -else - RAGEL_CODEGEN = %w[rlcodegen rlgen-cd ragel].find(&which) - RAGEL_DOTGEN = %w[rlgen-dot rlgen-cd ragel].find(&which) + def changelog(version) + changelog_lines = File.readlines(File.expand_path("CHANGES.md", __dir__)) + changelog_lines = changelog_lines.drop_while { |line| !(line.start_with?("### ") && line.include?("(#{version})")) } + if changelog_lines.empty? + return false + end + changelog_lines.shift + changelog_lines = changelog_lines.take_while { |line| !(line.start_with?("### ") ) } + changelog_lines.join + end + + def prerelease?(version) + !version.match?(/\A[\d.]+\z/) + end end -file JAVA_PARSER_SRC => JAVA_RAGEL_PATH do - cd JAVA_DIR do - if RAGEL_CODEGEN == 'ragel' - sh "ragel ParserConfig.rl -J -o ParserConfig.java" +namespace :ci do + task :check_release do + unless PKG_VERSION + abort("Gem version couldn't be read") + end + + if CI.gem_published?("json", PKG_VERSION) + $stderr.puts "Version #{PKG_VERSION} was already released. Nothing to do." + exit 0 + end + + if changelog = CI.changelog(PKG_VERSION) + puts "Changelog:" + puts changelog else - sh "ragel -x ParserConfig.rl | #{RAGEL_CODEGEN} -J" + abort("Could not find version #{PKG_VERSION} in CHANGES.md") end + + if ENV["GITHUB_OUTPUT"] + $stderr.puts "Triggering release" + File.open(ENV["GITHUB_OUTPUT"], "a+") do |f| + f.puts "run_publish=true" + end + end + end + + task :create_release do + tag = "v#{PKG_VERSION}" + args = [ + "--title", tag, + "--notes", changelog, + ] + args << "--prerelease" << "--latest=false" if CI.prerelease?(PKG_VERSION) + system("gh", "release", "create", tag, "--draft", *args) end end -desc "Generate parser with ragel" -task :ragel => [JAVA_PARSER_SRC] +JAVA_DIR = "java/src/json/ext" +JAVA_SOURCES = FileList["#{JAVA_DIR}/*.java"].exclude("#{JAVA_DIR}/Vectorized*.java") +JAVA_VEC_SOURCES = FileList["#{JAVA_DIR}/Vectorized*.java"] +JAVA_CLASSES = [] +JRUBY_PARSER_JAR = File.expand_path("lib/json/ext/parser.jar") +JRUBY_GENERATOR_JAR = File.expand_path("lib/json/ext/generator.jar") + +CLEAN.concat FileList["java/src/**/*.class"] +CLEAN << JRUBY_PARSER_JAR +CLEAN << JRUBY_GENERATOR_JAR if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' + path_separator = File::PATH_SEPARATOR ENV['JAVA_HOME'] ||= [ '/usr/local/java/jdk', '/usr/lib/jvm/java-6-openjdk', @@ -55,7 +105,7 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' ].find { |c| File.directory?(c) } if ENV['JAVA_HOME'] warn " *** JAVA_HOME is set to #{ENV['JAVA_HOME'].inspect}" - ENV['PATH'] = ENV['PATH'].split(/:/).unshift(java_path = "#{ENV['JAVA_HOME']}/bin") * ':' + ENV['PATH'] = ENV['PATH'].split(path_separator).unshift(java_path = "#{ENV['JAVA_HOME']}/bin") * path_separator warn " *** java binaries are assumed to be in #{java_path.inspect}" else warn " *** JAVA_HOME was not set or could not be guessed!" @@ -64,11 +114,29 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' JRUBY_JAR = File.join(CONFIG["libdir"], "jruby.jar") if File.exist?(JRUBY_JAR) + classpath = (Dir['java/lib/*.jar'] << 'java/src' << JRUBY_JAR) * path_separator JAVA_SOURCES.each do |src| - classpath = (Dir['java/lib/*.jar'] << 'java/src' << JRUBY_JAR) * ':' obj = src.sub(/\.java\Z/, '.class') file obj => src do - sh 'javac', '-classpath', classpath, '-source', '1.8', '-target', '1.8', src + if File.exist?(File.join(ENV['JAVA_HOME'], "lib", "modules")) + sh 'javac', '-classpath', classpath, '--release', '8', src + else + sh 'javac', '-classpath', classpath, '-source', '1.8', '-target', '1.8', src + end + end + JAVA_CLASSES << obj + end + + JAVA_VEC_SOURCES.each do |src| + obj = src.sub(/\.java\Z/, '.class') + file obj => src do + sh 'javac', '--add-modules', 'jdk.incubator.vector', '-classpath', classpath, '--release', '16', src do |success, status| + if success + puts "*** 'jdk.incubator.vector' support enabled ***" + else + puts "*** 'jdk.incubator.vector' support disabled ***" + end + end end JAVA_CLASSES << obj end @@ -77,10 +145,10 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' end desc "Compiling jruby extension" - task :compile => [:ragel] + JAVA_CLASSES + task compile: JAVA_CLASSES desc "Package the jruby gem" - task :jruby_gem => :create_jar do + task jruby_gem: :create_jar do mkdir_p 'pkg' sh "gem build -o pkg/json-#{PKG_VERSION}-java.gem json.gemspec" end @@ -92,7 +160,7 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' t.options = '-v' end desc "Testing library (jruby)" - task :test => [:create_jar ] + task test: [:create_jar ] file JRUBY_PARSER_JAR => :compile do cd 'java/src' do @@ -100,8 +168,10 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' "json/ext/ByteList*.class", "json/ext/OptionsReader*.class", "json/ext/Parser*.class", + "json/ext/Ryu*.class", "json/ext/RuntimeInfo*.class", "json/ext/StringDecoder*.class", + "json/ext/*StringScanner*.class", "json/ext/Utils*.class" ] sh 'jar', 'cf', File.basename(JRUBY_PARSER_JAR), *parser_classes @@ -110,33 +180,35 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' end desc "Create parser jar" - task :create_parser_jar => JRUBY_PARSER_JAR + task create_parser_jar: JRUBY_PARSER_JAR file JRUBY_GENERATOR_JAR => :compile do cd 'java/src' do generator_classes = FileList[ - "json/ext/ByteList*.class", + "json/ext/*ByteList*.class", "json/ext/OptionsReader*.class", + "json/ext/EscapeScanner*.class", "json/ext/Generator*.class", "json/ext/RuntimeInfo*.class", - "json/ext/StringEncoder*.class", + "json/ext/*StringEncoder*.class", "json/ext/Utils*.class" ] + puts "Creating generator jar with classes: #{generator_classes.join(', ')}" sh 'jar', 'cf', File.basename(JRUBY_GENERATOR_JAR), *generator_classes mv File.basename(JRUBY_GENERATOR_JAR), File.dirname(JRUBY_GENERATOR_JAR) end end desc "Create generator jar" - task :create_generator_jar => JRUBY_GENERATOR_JAR + task create_generator_jar: JRUBY_GENERATOR_JAR desc "Create parser and generator jars" - task :create_jar => [ :create_parser_jar, :create_generator_jar ] + task create_jar: [ :create_parser_jar, :create_generator_jar ] desc "Build all gems and archives for a new release of the jruby extension." - task :build => [ :clean, :jruby_gem ] + task build: [ :clean, :jruby_gem ] - task :release => :build + task release: :build else require 'rake/extensiontask' @@ -154,7 +226,7 @@ else end desc "Testing library (extension)" - task :test => [ :compile ] + task test: [ :compile ] begin require "ruby_memcheck" @@ -179,10 +251,10 @@ else end desc "Build all gems and archives for a new release of json" - task :build => [ :clean, :package ] + task build: [ :clean, :package ] - task :release => :build + task release: :build end desc "Compile in the the source directory" -task :default => [ :clean, :test ] +task default: [ :clean, :test ] diff --git a/benchmark/encoder.rb b/benchmark/encoder.rb index f0a05dbd2..f29334bd9 100644 --- a/benchmark/encoder.rb +++ b/benchmark/encoder.rb @@ -18,7 +18,6 @@ end def implementations(ruby_obj) - state = JSON::State.new(JSON.dump_default_options) coder = JSON::Coder.new implementations = { json: ["json", proc { JSON.generate(ruby_obj) }], @@ -64,37 +63,18 @@ def benchmark_encoding(benchmark_name, ruby_obj, check_expected: true, except: [ puts end -# NB: Notes are based on ruby 3.3.4 (2024-07-09 revision be1089c8ec) +YJIT [arm64-darwin23] - -# On the first two micro benchmarks, the limitting factor is the fixed cost of initializing the -# generator state. Since `JSON.generate` now lazily allocate the `State` object we're now ~10-20% faster -# than `Oj.dump`. benchmark_encoding "small mixed", [1, "string", { a: 1, b: 2 }, [3, 4, 5]] benchmark_encoding "small nested array", [[1,2,3,4,5]]*10 benchmark_encoding "small hash", { "username" => "jhawthorn", "id" => 123, "event" => "wrote json serializer" } -# On string encoding we're ~20% faster when dealing with mostly ASCII, but ~50% slower when dealing -# with mostly multi-byte characters. There's likely some gains left to be had in multi-byte handling. benchmark_encoding "mixed utf8", ([("a" * 5000) + "€" + ("a" * 5000)] * 500) benchmark_encoding "mostly utf8", ([("€" * 3333)] * 500) -# On these benchmarks we perform well, we're on par or a bit better. benchmark_encoding "integers", (1_000_000..1_001_000).to_a, except: %i(json_state) benchmark_encoding "activitypub.json", JSON.load_file("#{__dir__}/data/activitypub.json") benchmark_encoding "citm_catalog.json", JSON.load_file("#{__dir__}/data/citm_catalog.json") benchmark_encoding "twitter.json", JSON.load_file("#{__dir__}/data/twitter.json") -# This benchmark spent the overwhelming majority of its time in `ruby_dtoa`. We rely on Ruby's implementation -# which uses a relatively old version of dtoa.c from David M. Gay. -# Oj in `compat` mode is ~10% slower than `json`, but in its default mode is noticeably faster here because -# it limits the precision of floats, breaking roundtriping. That's not something we should emulate. -# -# Since a few years there are now much faster float to string implementations such as Ryu, Dragonbox, etc, -# but all these are implemented in C++11 or newer, making it hard if not impossible to include them. -# Short of a pure C99 implementation of these newer algorithms, there isn't much that can be done to match -# Oj speed without losing precision. benchmark_encoding "canada.json", JSON.load_file("#{__dir__}/data/canada.json"), check_expected: false -# We're about 10% faster when `to_json` calls are involved, but this wasn't particularly optimized, there might be -# opportunities here. benchmark_encoding "many #to_json calls", [{object: Object.new, int: 12, float: 54.3, class: Float, time: Time.now, date: Date.today}] * 20 diff --git a/benchmark/parser.rb b/benchmark/parser.rb index a2bd17efb..0ec73f4c1 100644 --- a/benchmark/parser.rb +++ b/benchmark/parser.rb @@ -21,7 +21,7 @@ end def benchmark_parsing(name, json_output) - puts "== Parsing #{name} (#{json_output.size} bytes)" + puts "== Parsing #{name} (#{json_output.bytesize} bytes)" coder = JSON::Coder.new Benchmark.ips do |x| @@ -42,24 +42,15 @@ def benchmark_parsing(name, json_output) puts end -# NB: Notes are based on ruby 3.3.4 (2024-07-09 revision be1089c8ec) +YJIT [arm64-darwin23] - benchmark_parsing "small nested array", JSON.dump([[1,2,3,4,5]]*10) benchmark_parsing "small hash", JSON.dump({ "username" => "jhawthorn", "id" => 123, "event" => "wrote json serializer" }) -benchmark_parsing "test from oj", <= 201103L) -# include -# endif -#elif defined(HAVE_STDBOOL_H) -# include -#elif !defined(HAVE__BOOL) -typedef unsigned char _Bool; -# define bool _Bool -# define true ((_Bool)+1) -# define false ((_Bool)+0) -# define __bool_true_false_are_defined -#endif -#endif - -#ifndef RB_UNLIKELY -#define RB_UNLIKELY(expr) expr -#endif - -#ifndef RB_LIKELY -#define RB_LIKELY(expr) expr -#endif - -#ifndef MAYBE_UNUSED -# define MAYBE_UNUSED(x) x -#endif +#include "../json.h" +#include "../vendor/jeaiii-ltoa.h" enum fbuffer_type { FBUFFER_HEAP_ALLOCATED = 0, @@ -42,9 +11,12 @@ enum fbuffer_type { typedef struct FBufferStruct { enum fbuffer_type type; - unsigned long initial_length; - unsigned long len; - unsigned long capa; + size_t initial_length; + size_t len; + size_t capa; +#if JSON_DEBUG + size_t requested; +#endif char *ptr; VALUE io; } FBuffer; @@ -60,25 +32,43 @@ typedef struct FBufferStruct { static void fbuffer_free(FBuffer *fb); static void fbuffer_clear(FBuffer *fb); -static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len); +static void fbuffer_append(FBuffer *fb, const char *newstr, size_t len); static void fbuffer_append_long(FBuffer *fb, long number); static inline void fbuffer_append_char(FBuffer *fb, char newchr); static VALUE fbuffer_finalize(FBuffer *fb); -static void fbuffer_stack_init(FBuffer *fb, unsigned long initial_length, char *stack_buffer, long stack_buffer_size) +static void fbuffer_init(FBuffer *fb, size_t initial_length, VALUE io, char *stack_buffer, size_t stack_buffer_size) { - fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT; - if (stack_buffer) { + if (RTEST(io)) { + JSON_ASSERT(fb->type == FBUFFER_HEAP_ALLOCATED); + fb->io = io; + fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_IO_BUFFER_SIZE; + } else { fb->type = FBUFFER_STACK_ALLOCATED; fb->ptr = stack_buffer; fb->capa = stack_buffer_size; + fb->initial_length = (initial_length > 0) ? initial_length : FBUFFER_INITIAL_LENGTH_DEFAULT; } +#if JSON_DEBUG + fb->requested = 0; +#endif +} + +static inline void fbuffer_consumed(FBuffer *fb, size_t consumed) +{ +#if JSON_DEBUG + if (consumed > fb->requested) { + rb_bug("fbuffer: Out of bound write"); + } + fb->requested = 0; +#endif + fb->len += consumed; } static void fbuffer_free(FBuffer *fb) { if (fb->ptr && fb->type == FBUFFER_HEAP_ALLOCATED) { - ruby_xfree(fb->ptr); + JSON_SIZED_FREE_N(fb->ptr, fb->capa); } } @@ -93,113 +83,181 @@ static void fbuffer_flush(FBuffer *fb) fbuffer_clear(fb); } -static void fbuffer_realloc(FBuffer *fb, unsigned long required) +static void fbuffer_realloc(FBuffer *fb, size_t new_capa) { - if (required > fb->capa) { + if (new_capa > fb->capa) { if (fb->type == FBUFFER_STACK_ALLOCATED) { const char *old_buffer = fb->ptr; - fb->ptr = ALLOC_N(char, required); + fb->ptr = ALLOC_N(char, new_capa); fb->type = FBUFFER_HEAP_ALLOCATED; MEMCPY(fb->ptr, old_buffer, char, fb->len); } else { - REALLOC_N(fb->ptr, char, required); + JSON_SIZED_REALLOC_N(fb->ptr, char, new_capa, fb->capa); } - fb->capa = required; + fb->capa = new_capa; } } -static void fbuffer_do_inc_capa(FBuffer *fb, unsigned long requested) +static void fbuffer_do_inc_capa(FBuffer *fb, size_t requested) { if (RB_UNLIKELY(fb->io)) { - if (fb->capa < FBUFFER_IO_BUFFER_SIZE) { - fbuffer_realloc(fb, FBUFFER_IO_BUFFER_SIZE); - } else { + if (fb->capa != 0) { fbuffer_flush(fb); - } - - if (RB_LIKELY(requested < fb->capa)) { - return; + if (RB_LIKELY(requested < fb->capa)) { + return; + } } } - unsigned long required; + size_t new_capa = fb->capa ? fb->capa : fb->initial_length; + size_t needed_capa = requested + fb->len; - if (RB_UNLIKELY(!fb->ptr)) { - fb->ptr = ALLOC_N(char, fb->initial_length); - fb->capa = fb->initial_length; + while (new_capa < needed_capa) { + new_capa *= 2; } - for (required = fb->capa; requested > required - fb->len; required <<= 1); - - fbuffer_realloc(fb, required); + fbuffer_realloc(fb, new_capa); } -static inline void fbuffer_inc_capa(FBuffer *fb, unsigned long requested) +static inline void fbuffer_inc_capa(FBuffer *fb, size_t requested) { +#if JSON_DEBUG + fb->requested = requested; +#endif + if (RB_UNLIKELY(requested > fb->capa - fb->len)) { fbuffer_do_inc_capa(fb, requested); } } -static void fbuffer_append(FBuffer *fb, const char *newstr, unsigned long len) +static inline size_t fbuffer_size_mul_or_raise(size_t a, size_t b) +{ + size_t result = a * b; + if (RB_UNLIKELY(a != 0 && (result / a) != b)) { + rb_raise(rb_eArgError, "Buffer overflow, the resulting document is too large to be generated"); + } + return result; +} + +static inline void fbuffer_append_reserved(FBuffer *fb, const char *newstr, size_t len) +{ + MEMCPY(fb->ptr + fb->len, newstr, char, len); + fbuffer_consumed(fb, len); +} + +static inline void fbuffer_append(FBuffer *fb, const char *newstr, size_t len) { if (len > 0) { fbuffer_inc_capa(fb, len); - MEMCPY(fb->ptr + fb->len, newstr, char, len); - fb->len += len; + fbuffer_append_reserved(fb, newstr, len); + } +} + +/* Appends a character into a buffer. The buffer needs to have sufficient capacity, via fbuffer_inc_capa(...). */ +static inline void fbuffer_append_reserved_char(FBuffer *fb, char chr) +{ +#if JSON_DEBUG + if (fb->requested < 1) { + rb_bug("fbuffer: unreserved write"); } + fb->requested--; +#endif + + fb->ptr[fb->len] = chr; + fb->len++; } static void fbuffer_append_str(FBuffer *fb, VALUE str) { - const char *newstr = StringValuePtr(str); - unsigned long len = RSTRING_LEN(str); + if (RB_UNLIKELY(fb->io)) { + // Growing the buffer may flush it and invoke arbitrary Ruby code. + str = rb_str_new_frozen(str); + } + const char *ptr; + size_t len; + RSTRING_GETMEM(str, ptr, len); + fbuffer_append(fb, ptr, len); RB_GC_GUARD(str); +} - fbuffer_append(fb, newstr, len); +static void fbuffer_append_str_repeat(FBuffer *fb, VALUE str, size_t repeat) +{ + const char *ptr; + size_t len; + RSTRING_GETMEM(str, ptr, len); + + fbuffer_inc_capa(fb, fbuffer_size_mul_or_raise(repeat, len)); + while (repeat) { +#if JSON_DEBUG + fb->requested = len; +#endif + fbuffer_append_reserved(fb, ptr, len); + repeat--; + } + RB_GC_GUARD(str); } static inline void fbuffer_append_char(FBuffer *fb, char newchr) { fbuffer_inc_capa(fb, 1); *(fb->ptr + fb->len) = newchr; - fb->len++; + fbuffer_consumed(fb, 1); } -static long fltoa(long number, char *buf) +static inline char *fbuffer_cursor(FBuffer *fb) { - static const char digits[] = "0123456789"; - long sign = number; - char* tmp = buf; + return fb->ptr + fb->len; +} - if (sign < 0) number = -number; - do *tmp-- = digits[number % 10]; while (number /= 10); - if (sign < 0) *tmp-- = '-'; - return buf - tmp; +static inline void fbuffer_advance_to(FBuffer *fb, char *end) +{ + fbuffer_consumed(fb, (end - fb->ptr) - fb->len); } -#define LONG_BUFFER_SIZE 20 +/* + * Appends the decimal string representation of \a number into the buffer. + */ static void fbuffer_append_long(FBuffer *fb, long number) { - char buf[LONG_BUFFER_SIZE]; - char *buffer_end = buf + LONG_BUFFER_SIZE; - long len = fltoa(number, buffer_end - 1); - fbuffer_append(fb, buffer_end - len, len); + /* + * The jeaiii_ultoa() function produces digits left-to-right, + * allowing us to write directly into the buffer, but we don't know + * the number of resulting characters. + * + * We do know, however, that the `number` argument is always in the + * range 0xc000000000000000 to 0x3fffffffffffffff, or, in decimal, + * -4611686018427387904 to 4611686018427387903. The max number of chars + * generated is therefore 20 (including a potential sign character). + */ + + static const int MAX_CHARS_FOR_LONG = 20; + + fbuffer_inc_capa(fb, MAX_CHARS_FOR_LONG); + + if (number < 0) { + fbuffer_append_reserved_char(fb, '-'); + + /* + * Since number is always > LONG_MIN, `-number` will not overflow + * and is always the positive abs() value. + */ + number = -number; + } + + char *end = jeaiii_ultoa(fbuffer_cursor(fb), number); + fbuffer_advance_to(fb, end); } static VALUE fbuffer_finalize(FBuffer *fb) { if (fb->io) { fbuffer_flush(fb); - fbuffer_free(fb); rb_io_flush(fb->io); return fb->io; } else { - VALUE result = rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb)); - fbuffer_free(fb); - return result; + return rb_utf8_str_new(FBUFFER_PTR(fb), FBUFFER_LEN(fb)); } } -#endif +#endif // _FBUFFER_H_ diff --git a/ext/json/ext/generator/depend b/ext/json/ext/generator/depend index 967aa7659..43fd7d33a 100644 --- a/ext/json/ext/generator/depend +++ b/ext/json/ext/generator/depend @@ -1 +1,8 @@ -generator.o: generator.c $(srcdir)/../fbuffer/fbuffer.h +# AUTOGENERATED DEPENDENCIES START +generator.o: $(srcdir)/../fbuffer/fbuffer.h +generator.o: $(srcdir)/../json.h +generator.o: $(srcdir)/../simd/simd.h +generator.o: $(srcdir)/../vendor/fpconv.c +generator.o: $(srcdir)/../vendor/jeaiii-ltoa.h +generator.o: generator.c +# AUTOGENERATED DEPENDENCIES END diff --git a/ext/json/ext/generator/extconf.rb b/ext/json/ext/generator/extconf.rb index 078068cf6..33af03ea3 100644 --- a/ext/json/ext/generator/extconf.rb +++ b/ext/json/ext/generator/extconf.rb @@ -5,6 +5,15 @@ File.write('Makefile', dummy_makefile("").join) else append_cflags("-std=c99") + have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3 + have_func("ruby_xfree_sized", "ruby.h") # RUBY_VERSION >= 4.1 + $defs << "-DJSON_GENERATOR" + $defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0" + + if enable_config('generator-use-simd', default=!ENV["JSON_DISABLE_SIMD"]) + load __dir__ + "/../simd/conf.rb" + end + create_makefile 'json/ext/generator' end diff --git a/ext/json/ext/generator/generator.c b/ext/json/ext/generator/generator.c index 083388537..87da10027 100644 --- a/ext/json/ext/generator/generator.c +++ b/ext/json/ext/generator/generator.c @@ -1,9 +1,12 @@ -#include "ruby.h" +#include "../json.h" #include "../fbuffer/fbuffer.h" +#include "../vendor/fpconv.c" #include #include +#include "../simd/simd.h" + /* ruby api and some helpers */ typedef struct JSON_Generator_StateStruct { @@ -18,21 +21,22 @@ typedef struct JSON_Generator_StateStruct { long depth; long buffer_initial_length; + bool allow_duplicate_key; + bool as_json_single_arg; bool allow_nan; bool ascii_only; bool script_safe; bool strict; + bool rfc8785; + VALUE sort_keys; } JSON_Generator_State; -#ifndef RB_UNLIKELY -#define RB_UNLIKELY(cond) (cond) -#endif - -static VALUE mJSON, cState, cFragment, mString_Extend, eGeneratorError, eNestingError, Encoding_UTF_8; +static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_UTF_8, default_sort_keys_proc, + rfc8785_number_formatter_proc, rfc8785_sort_keys_proc; -static ID i_to_s, i_to_json, i_new, i_pack, i_unpack, i_create_id, i_extend, i_encode; -static VALUE sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_nl, sym_max_nesting, sym_allow_nan, - sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_escape_slash, sym_strict, sym_as_json; +static ID i_to_s, i_to_json, i_new, i_encode; +static VALUE sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_nl, sym_max_nesting, sym_allow_nan, sym_allow_duplicate_key, + sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_strict, sym_as_json, sym_sort_keys, sym_rfc8785; #define GET_STATE_TO(self, state) \ @@ -44,7 +48,7 @@ static VALUE sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_n struct generate_json_data; -typedef void (*generator_func)(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); +typedef void (*generator_func)(FBuffer *buffer, struct generate_json_data *data, VALUE obj); struct generate_json_data { FBuffer *buffer; @@ -52,44 +56,39 @@ struct generate_json_data { JSON_Generator_State *state; VALUE obj; generator_func func; + long depth; }; +static SIMD_Implementation simd_impl; + static VALUE cState_from_state_s(VALUE self, VALUE opts); static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func, VALUE io); -static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); -static void generate_json_object(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); -static void generate_json_array(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); -static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); -static void generate_json_null(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); -static void generate_json_false(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); -static void generate_json_true(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); -#ifdef RUBY_INTEGER_UNIFICATION -static void generate_json_integer(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); -#endif -static void generate_json_fixnum(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); -static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); -static void generate_json_float(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); -static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj); - -static int usascii_encindex, utf8_encindex, binary_encindex; - -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif -static void raise_generator_error_str(VALUE invalid_object, VALUE str) -{ +static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALUE obj); +static void generate_json_object(FBuffer *buffer, struct generate_json_data *data, VALUE obj); +static void generate_json_array(FBuffer *buffer, struct generate_json_data *data, VALUE obj); +static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj); +static void generate_json_null(FBuffer *buffer, struct generate_json_data *data, VALUE obj); +static void generate_json_false(FBuffer *buffer, struct generate_json_data *data, VALUE obj); +static void generate_json_true(FBuffer *buffer, struct generate_json_data *data, VALUE obj); +static void generate_json_fixnum(FBuffer *buffer, struct generate_json_data *data, VALUE obj); +static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *data, VALUE obj); +static void generate_json_float(FBuffer *buffer, struct generate_json_data *data, VALUE obj); +static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *data, VALUE obj); + +static int usascii_encindex, utf8_encindex; + +NORETURN(static void) raise_generator_error_str(VALUE invalid_object, VALUE str) +{ + rb_enc_associate_index(str, utf8_encindex); VALUE exc = rb_exc_new_str(eGeneratorError, str); rb_ivar_set(exc, rb_intern("@invalid_object"), invalid_object); rb_exc_raise(exc); } -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif #ifdef RBIMPL_ATTR_FORMAT RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3) #endif -static void raise_generator_error(VALUE invalid_object, const char *fmt, ...) +NORETURN(static void) raise_generator_error(VALUE invalid_object, const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -108,12 +107,34 @@ typedef struct _search_state { const char *end; const char *cursor; FBuffer *buffer; + +#ifdef HAVE_SIMD + const char *chunk_base; + const char *chunk_end; + bool has_matches; + +#if defined(HAVE_SIMD_NEON) + uint64_t matches_mask; +#elif defined(HAVE_SIMD_SSE2) + int matches_mask; +#else +#error "Unknown SIMD Implementation." +#endif /* HAVE_SIMD_NEON */ +#endif /* HAVE_SIMD */ } search_state; -static inline void search_flush(search_state *search) -{ - fbuffer_append(search->buffer, search->cursor, search->ptr - search->cursor); - search->cursor = search->ptr; +ALWAYS_INLINE(static) void search_flush(search_state *search) +{ + // Do not remove this conditional without profiling, specifically escape-heavy text. + // escape_UTF8_char_basic will advance search->ptr and search->cursor (effectively a search_flush). + // For back-to-back characters that need to be escaped, specifically for the SIMD code paths, this method + // will be called just before calling escape_UTF8_char_basic. There will be no characters to append for the + // consecutive characters that need to be escaped. While the fbuffer_append is a no-op if + // nothing needs to be flushed, we can save a few memory references with this conditional. + if (search->ptr > search->cursor) { + fbuffer_append(search->buffer, search->cursor, search->ptr - search->cursor); + search->cursor = search->ptr; + } } static const unsigned char escape_table_basic[256] = { @@ -143,7 +164,8 @@ static inline unsigned char search_escape_basic(search_state *search) return 0; } -static inline void escape_UTF8_char_basic(search_state *search) { +ALWAYS_INLINE(static) void escape_UTF8_char_basic(search_state *search) +{ const unsigned char ch = (unsigned char)*search->ptr; switch (ch) { case '"': fbuffer_append(search->buffer, "\\\"", 2); break; @@ -183,14 +205,43 @@ static inline void escape_UTF8_char_basic(search_state *search) { * Everything else (should be UTF-8) is just passed through and * appended to the result. */ + + +#if defined(HAVE_SIMD_NEON) +static inline unsigned char search_escape_basic_neon(search_state *search); +#elif defined(HAVE_SIMD_SSE2) +static inline unsigned char search_escape_basic_sse2(search_state *search); +#endif + +static inline unsigned char search_escape_basic(search_state *search); + static inline void convert_UTF8_to_JSON(search_state *search) { +#ifdef HAVE_SIMD +#if defined(HAVE_SIMD_NEON) + while (search_escape_basic_neon(search)) { + escape_UTF8_char_basic(search); + } +#elif defined(HAVE_SIMD_SSE2) + if (simd_impl == SIMD_SSE2) { + while (search_escape_basic_sse2(search)) { + escape_UTF8_char_basic(search); + } + return; + } + while (search_escape_basic(search)) { + escape_UTF8_char_basic(search); + } +#endif +#else while (search_escape_basic(search)) { escape_UTF8_char_basic(search); } +#endif /* HAVE_SIMD */ } -static inline void escape_UTF8_char(search_state *search, unsigned char ch_len) { +static inline void escape_UTF8_char(search_state *search, unsigned char ch_len) +{ const unsigned char ch = (unsigned char)*search->ptr; switch (ch_len) { case 1: { @@ -226,6 +277,236 @@ static inline void escape_UTF8_char(search_state *search, unsigned char ch_len) search->cursor = (search->ptr += ch_len); } +#ifdef HAVE_SIMD + +ALWAYS_INLINE(static) char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len) +{ + RBIMPL_ASSERT_OR_ASSUME(len < vec_len); + + // Flush the buffer so everything up until the last 'len' characters are unflushed. + search_flush(search); + + FBuffer *buf = search->buffer; + fbuffer_inc_capa(buf, vec_len); + + char *s = (buf->ptr + buf->len); + + // Pad the buffer with dummy characters that won't need escaping. + // This seem wasteful at first sight, but memset of vector length is very fast. + // This is a space as it can be directly represented as an immediate on AArch64. + memset(s, ' ', vec_len); + + // Optimistically copy the remaining 'len' characters to the output FBuffer. If there are no characters + // to escape, then everything ends up in the correct spot. Otherwise it was convenient temporary storage. + if (vec_len == 16) { + RBIMPL_ASSERT_OR_ASSUME(len >= SIMD_MINIMUM_THRESHOLD); + json_fast_memcpy16(s, search->ptr, len); + } else { + MEMCPY(s, search->ptr, char, len); + } + + return s; +} + +#ifdef HAVE_SIMD_NEON + +ALWAYS_INLINE(static) unsigned char neon_next_match(search_state *search) +{ + uint64_t mask = search->matches_mask; + uint32_t index = trailing_zeros64(mask) >> 2; + + // It is assumed escape_UTF8_char_basic will only ever increase search->ptr by at most one character. + // If we want to use a similar approach for full escaping we'll need to ensure: + // search->chunk_base + index >= search->ptr + // However, since we know escape_UTF8_char_basic only increases search->ptr by one, if the next match + // is one byte after the previous match then: + // search->chunk_base + index == search->ptr + search->ptr = search->chunk_base + index; + mask &= mask - 1; + search->matches_mask = mask; + search_flush(search); + return 1; +} + +static inline unsigned char search_escape_basic_neon(search_state *search) +{ + if (RB_UNLIKELY(search->has_matches)) { + // There are more matches if search->matches_mask > 0. + if (search->matches_mask > 0) { + return neon_next_match(search); + } else { + // neon_next_match will only advance search->ptr up to the last matching character. + // Skip over any characters in the last chunk that occur after the last match. + search->has_matches = false; + search->ptr = search->chunk_end; + } + } + + /* + * The code below implements an SIMD-based algorithm to determine if N bytes at a time + * need to be escaped. + * + * Assume the ptr = "Te\sting!" (the double quotes are included in the string) + * + * The explanation will be limited to the first 8 bytes of the string for simplicity. However + * the vector insructions may work on larger vectors. + * + * First, we load three constants 'lower_bound', 'backslash' and 'dblquote" in vector registers. + * + * lower_bound: [20 20 20 20 20 20 20 20] + * backslash: [5C 5C 5C 5C 5C 5C 5C 5C] + * dblquote: [22 22 22 22 22 22 22 22] + * + * Next we load the first chunk of the ptr: + * [22 54 65 5C 73 74 69 6E] (" T e \ s t i n) + * + * First we check if any byte in chunk is less than 32 (0x20). This returns the following vector + * as no bytes are less than 32 (0x20): + * [0 0 0 0 0 0 0 0] + * + * Next, we check if any byte in chunk is equal to a backslash: + * [0 0 0 FF 0 0 0 0] + * + * Finally we check if any byte in chunk is equal to a double quote: + * [FF 0 0 0 0 0 0 0] + * + * Now we have three vectors where each byte indicates if the corresponding byte in chunk + * needs to be escaped. We combine these vectors with a series of logical OR instructions. + * This is the needs_escape vector and it is equal to: + * [FF 0 0 FF 0 0 0 0] + * + * Next we compute the bitwise AND between each byte and 0x1 and compute the horizontal sum of + * the values in the vector. This computes how many bytes need to be escaped within this chunk. + * + * Finally we compute a mask that indicates which bytes need to be escaped. If the mask is 0 then, + * no bytes need to be escaped and we can continue to the next chunk. If the mask is not 0 then we + * have at least one byte that needs to be escaped. + */ + + if (string_scan_simd_neon(&search->ptr, search->end, &search->matches_mask)) { + search->has_matches = true; + search->chunk_base = search->ptr; + search->chunk_end = search->ptr + sizeof(uint8x16_t); + return neon_next_match(search); + } + + // There are fewer than 16 bytes left. + unsigned long remaining = (search->end - search->ptr); + if (remaining >= SIMD_MINIMUM_THRESHOLD) { + char *s = copy_remaining_bytes(search, sizeof(uint8x16_t), remaining); + + uint64_t mask = compute_chunk_mask_neon(s); + + if (!mask) { + // Nothing to escape, ensure search_flush doesn't do anything by setting + // search->cursor to search->ptr. + fbuffer_consumed(search->buffer, remaining); + search->ptr = search->end; + search->cursor = search->end; + return 0; + } + + search->matches_mask = mask; + search->has_matches = true; + search->chunk_end = search->end; + search->chunk_base = search->ptr; + return neon_next_match(search); + } + + if (search->ptr < search->end) { + return search_escape_basic(search); + } + + search_flush(search); + return 0; +} +#endif /* HAVE_SIMD_NEON */ + +#ifdef HAVE_SIMD_SSE2 + +ALWAYS_INLINE(static) unsigned char sse2_next_match(search_state *search) +{ + int mask = search->matches_mask; + int index = trailing_zeros(mask); + + // It is assumed escape_UTF8_char_basic will only ever increase search->ptr by at most one character. + // If we want to use a similar approach for full escaping we'll need to ensure: + // search->chunk_base + index >= search->ptr + // However, since we know escape_UTF8_char_basic only increases search->ptr by one, if the next match + // is one byte after the previous match then: + // search->chunk_base + index == search->ptr + search->ptr = search->chunk_base + index; + mask &= mask - 1; + search->matches_mask = mask; + search_flush(search); + return 1; +} + +#if defined(__clang__) || defined(__GNUC__) +#define TARGET_SSE2 __attribute__((target("sse2"))) +#else +#define TARGET_SSE2 +#endif + +ALWAYS_INLINE(static) TARGET_SSE2 unsigned char search_escape_basic_sse2(search_state *search) +{ + if (RB_UNLIKELY(search->has_matches)) { + // There are more matches if search->matches_mask > 0. + if (search->matches_mask > 0) { + return sse2_next_match(search); + } else { + // sse2_next_match will only advance search->ptr up to the last matching character. + // Skip over any characters in the last chunk that occur after the last match. + search->has_matches = false; + if (RB_UNLIKELY(search->chunk_base + sizeof(__m128i) >= search->end)) { + search->ptr = search->end; + } else { + search->ptr = search->chunk_base + sizeof(__m128i); + } + } + } + + if (string_scan_simd_sse2(&search->ptr, search->end, &search->matches_mask)) { + search->has_matches = true; + search->chunk_base = search->ptr; + search->chunk_end = search->ptr + sizeof(__m128i); + return sse2_next_match(search); + } + + // There are fewer than 16 bytes left. + unsigned long remaining = (search->end - search->ptr); + if (remaining >= SIMD_MINIMUM_THRESHOLD) { + char *s = copy_remaining_bytes(search, sizeof(__m128i), remaining); + + int needs_escape_mask = compute_chunk_mask_sse2(s); + + if (needs_escape_mask == 0) { + // Nothing to escape, ensure search_flush doesn't do anything by setting + // search->cursor to search->ptr. + fbuffer_consumed(search->buffer, remaining); + search->ptr = search->end; + search->cursor = search->end; + return 0; + } + + search->has_matches = true; + search->matches_mask = needs_escape_mask; + search->chunk_base = search->ptr; + return sse2_next_match(search); + } + + if (search->ptr < search->end) { + return search_escape_basic(search); + } + + search_flush(search); + return 0; +} + +#endif /* HAVE_SIMD_SSE2 */ + +#endif /* HAVE_SIMD */ + static const unsigned char script_safe_escape_table[256] = { // ASCII Control Characters 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, @@ -329,7 +610,8 @@ static inline unsigned char search_ascii_only_escape(search_state *search, const return 0; } -static inline void full_escape_UTF8_char(search_state *search, unsigned char ch_len) { +static inline void full_escape_UTF8_char(search_state *search, unsigned char ch_len) +{ const unsigned char ch = (unsigned char)*search->ptr; switch (ch_len) { case 1: { @@ -359,7 +641,7 @@ static inline void full_escape_UTF8_char(search_state *search, unsigned char ch_ uint32_t wchar = 0; - switch(ch_len) { + switch (ch_len) { case 2: wchar = ch & 0x1F; break; @@ -414,288 +696,6 @@ static void convert_UTF8_to_ASCII_only_JSON(search_state *search, const unsigned } } -/* - * Document-module: JSON::Ext::Generator - * - * This is the JSON generator implemented as a C extension. It can be - * configured to be used by setting - * - * JSON.generator = JSON::Ext::Generator - * - * with the method generator= in JSON. - * - */ - -/* Explanation of the following: that's the only way to not pollute - * standard library's docs with GeneratorMethods:: which - * are uninformative and take a large place in a list of classes - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Array - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Bignum - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::FalseClass - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Fixnum - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Float - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Hash - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Integer - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::NilClass - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::Object - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::String - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::String::Extend - * :nodoc: - */ - -/* - * Document-module: JSON::Ext::Generator::GeneratorMethods::TrueClass - * :nodoc: - */ - -/* - * call-seq: to_json(state = nil) - * - * Returns a JSON string containing a JSON object, that is generated from - * this Hash instance. - * _state_ is a JSON::State object, that can also be used to configure the - * produced JSON string output further. - */ -static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_object, Qfalse); -} - -/* - * call-seq: to_json(state = nil) - * - * Returns a JSON string containing a JSON array, that is generated from - * this Array instance. - * _state_ is a JSON::State object, that can also be used to configure the - * produced JSON string output further. - */ -static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) { - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_array, Qfalse); -} - -#ifdef RUBY_INTEGER_UNIFICATION -/* - * call-seq: to_json(*) - * - * Returns a JSON string representation for this Integer number. - */ -static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_integer, Qfalse); -} - -#else -/* - * call-seq: to_json(*) - * - * Returns a JSON string representation for this Integer number. - */ -static VALUE mFixnum_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_fixnum, Qfalse); -} - -/* - * call-seq: to_json(*) - * - * Returns a JSON string representation for this Integer number. - */ -static VALUE mBignum_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_bignum, Qfalse); -} -#endif - -/* - * call-seq: to_json(*) - * - * Returns a JSON string representation for this Float number. - */ -static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_float, Qfalse); -} - -/* - * call-seq: String.included(modul) - * - * Extends _modul_ with the String::Extend module. - */ -static VALUE mString_included_s(VALUE self, VALUE modul) { - VALUE result = rb_funcall(modul, i_extend, 1, mString_Extend); - rb_call_super(1, &modul); - return result; -} - -/* - * call-seq: to_json(*) - * - * This string should be encoded with UTF-8 A call to this method - * returns a JSON string encoded with UTF16 big endian characters as - * \u????. - */ -static VALUE mString_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil); - return cState_partial_generate(Vstate, self, generate_json_string, Qfalse); -} - -/* - * call-seq: to_json_raw_object() - * - * This method creates a raw object hash, that can be nested into - * other data structures and will be generated as a raw string. This - * method should be used, if you want to convert raw strings to JSON - * instead of UTF-8 strings, e. g. binary data. - */ -static VALUE mString_to_json_raw_object(VALUE self) -{ - VALUE ary; - VALUE result = rb_hash_new(); - rb_hash_aset(result, rb_funcall(mJSON, i_create_id, 0), rb_class_name(rb_obj_class(self))); - ary = rb_funcall(self, i_unpack, 1, rb_str_new2("C*")); - rb_hash_aset(result, rb_utf8_str_new_lit("raw"), ary); - return result; -} - -/* - * call-seq: to_json_raw(*args) - * - * This method creates a JSON text from the result of a call to - * to_json_raw_object of this String. - */ -static VALUE mString_to_json_raw(int argc, VALUE *argv, VALUE self) -{ - VALUE obj = mString_to_json_raw_object(self); - Check_Type(obj, T_HASH); - return mHash_to_json(argc, argv, obj); -} - -/* - * call-seq: json_create(o) - * - * Raw Strings are JSON Objects (the raw bytes are stored in an array for the - * key "raw"). The Ruby String can be created by this module method. - */ -static VALUE mString_Extend_json_create(VALUE self, VALUE o) -{ - VALUE ary; - Check_Type(o, T_HASH); - ary = rb_hash_aref(o, rb_str_new2("raw")); - return rb_funcall(ary, i_pack, 1, rb_str_new2("C*")); -} - -/* - * call-seq: to_json(*) - * - * Returns a JSON string for true: 'true'. - */ -static VALUE mTrueClass_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - return rb_utf8_str_new("true", 4); -} - -/* - * call-seq: to_json(*) - * - * Returns a JSON string for false: 'false'. - */ -static VALUE mFalseClass_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - return rb_utf8_str_new("false", 5); -} - -/* - * call-seq: to_json(*) - * - * Returns a JSON string for nil: 'null'. - */ -static VALUE mNilClass_to_json(int argc, VALUE *argv, VALUE self) -{ - rb_check_arity(argc, 0, 1); - return rb_utf8_str_new("null", 4); -} - -/* - * call-seq: to_json(*) - * - * Converts this object to a string (calling #to_s), converts - * it to a JSON string, and returns the result. This is a fallback, if no - * special method #to_json was defined for some object. - */ -static VALUE mObject_to_json(int argc, VALUE *argv, VALUE self) -{ - VALUE state; - VALUE string = rb_funcall(self, i_to_s, 0); - rb_scan_args(argc, argv, "01", &state); - Check_Type(string, T_STRING); - state = cState_from_state_s(cState, state); - return cState_partial_generate(state, string, generate_json_string, Qfalse); -} - static void State_mark(void *ptr) { JSON_Generator_State *state = ptr; @@ -705,6 +705,7 @@ static void State_mark(void *ptr) rb_gc_mark_movable(state->object_nl); rb_gc_mark_movable(state->array_nl); rb_gc_mark_movable(state->as_json); + rb_gc_mark_movable(state->sort_keys); } static void State_compact(void *ptr) @@ -716,34 +717,27 @@ static void State_compact(void *ptr) state->object_nl = rb_gc_location(state->object_nl); state->array_nl = rb_gc_location(state->array_nl); state->as_json = rb_gc_location(state->as_json); -} - -static void State_free(void *ptr) -{ - JSON_Generator_State *state = ptr; - ruby_xfree(state); + state->sort_keys = rb_gc_location(state->sort_keys); } static size_t State_memsize(const void *ptr) { +#ifdef HAVE_RUBY_TYPED_EMBEDDABLE + return 0; +#else return sizeof(JSON_Generator_State); -} - -#ifndef HAVE_RB_EXT_RACTOR_SAFE -# undef RUBY_TYPED_FROZEN_SHAREABLE -# define RUBY_TYPED_FROZEN_SHAREABLE 0 #endif +} static const rb_data_type_t JSON_Generator_State_type = { - "JSON/Generator/State", - { + .wrap_struct_name = "JSON/Generator/State", + .function = { .dmark = State_mark, - .dfree = State_free, + .dfree = RUBY_DEFAULT_FREE, .dsize = State_memsize, .dcompact = State_compact, }, - 0, 0, - RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE, + .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE, }; static void state_init(JSON_Generator_State *state) @@ -773,21 +767,198 @@ static void vstate_spill(struct generate_json_data *data) RB_OBJ_WRITTEN(vstate, Qundef, state->object_nl); RB_OBJ_WRITTEN(vstate, Qundef, state->array_nl); RB_OBJ_WRITTEN(vstate, Qundef, state->as_json); + RB_OBJ_WRITTEN(vstate, Qundef, state->sort_keys); +} + +static inline VALUE json_to_s(VALUE obj) +{ + VALUE tmp = rb_funcall(obj, i_to_s, 0); + return StringValue(tmp); +} + +static inline VALUE json_call_to_json(struct generate_json_data *data, VALUE obj) +{ + if (RB_UNLIKELY(!data->vstate)) { + vstate_spill(data); + } + GET_STATE(data->vstate); + state->depth = data->depth; + VALUE tmp = rb_funcall(obj, i_to_json, 1, data->vstate); + // no need to restore state->depth, vstate is just a temporary State + return tmp; +} + +static VALUE +json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key) +{ + VALUE proc_args[2] = {object, is_key}; + return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil); +} + +static VALUE +convert_string_subclass(VALUE key) +{ + VALUE key_to_s = rb_funcall(key, i_to_s, 0); + + if (RB_UNLIKELY(!RB_TYPE_P(key_to_s, T_STRING))) { + VALUE cname = rb_obj_class(key); + rb_raise(rb_eTypeError, + "can't convert %"PRIsVALUE" to %s (%"PRIsVALUE"#%s gives %"PRIsVALUE")", + cname, "String", cname, "to_s", rb_obj_class(key_to_s)); + } + + return key_to_s; +} + +static bool enc_utf8_compatible_p(int enc_idx) +{ + if (enc_idx == usascii_encindex) return true; + if (enc_idx == utf8_encindex) return true; + return false; +} + +static VALUE encode_json_string_try(VALUE str) +{ + return rb_funcall(str, i_encode, 1, Encoding_UTF_8); +} + +static VALUE encode_json_string_rescue(VALUE str, VALUE exception) +{ + raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0)); + return Qundef; +} + +static inline int json_str_coderange(VALUE str) { + int coderange = RB_ENC_CODERANGE(str); + if (coderange == RUBY_ENC_CODERANGE_UNKNOWN) { + coderange = rb_enc_str_coderange(str); + } + return coderange; +} + +static inline bool valid_json_string_p(VALUE str) +{ + int coderange = json_str_coderange(str); + + if (RB_LIKELY(coderange == ENC_CODERANGE_7BIT)) { + return true; + } + + if (RB_LIKELY(coderange == ENC_CODERANGE_VALID)) { + return enc_utf8_compatible_p(RB_ENCODING_GET_INLINED(str)); + } + + return false; +} + +NOINLINE(static) VALUE convert_invalid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key) +{ + if (!as_json_called && data->state->strict && RTEST(data->state->as_json)) { + VALUE coerced_str = json_call_as_json(data->state, str, Qfalse); + if (coerced_str != str) { + if (RB_TYPE_P(coerced_str, T_STRING)) { + if (!valid_json_string_p(coerced_str)) { + raise_generator_error(str, "source sequence is illegal/malformed utf-8"); + } + } else { + // as_json could return another type than T_STRING + if (is_key) { + raise_generator_error(coerced_str, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(coerced_str)); + } + } + + return coerced_str; + } + } + + str = rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str); + Check_Type(str, T_STRING); + if (!valid_json_string_p(str)) { + raise_generator_error(str, "source sequence is illegal/malformed utf-8"); + } + return str; +} + +ALWAYS_INLINE(static) VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key) +{ + if (RB_LIKELY(valid_json_string_p(str))) { + return str; + } + else { + return convert_invalid_encoding(data, str, as_json_called, is_key); + } +} + +static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +{ + VALUE str = obj; + if (RB_UNLIKELY(buffer->io)) { + // IO writes can mutate the original string while we are reading it. + str = rb_str_new_frozen(str); + } + fbuffer_append_char(buffer, '"'); + + rb_len_t len; + search_state search; + search.buffer = buffer; + RSTRING_GETMEM(str, search.ptr, len); + search.cursor = search.ptr; + search.end = search.ptr + len; + +#ifdef HAVE_SIMD + search.matches_mask = 0; + search.has_matches = false; + search.chunk_base = NULL; + search.chunk_end = NULL; +#endif /* HAVE_SIMD */ + + switch (json_str_coderange(str)) { + case ENC_CODERANGE_7BIT: + case ENC_CODERANGE_VALID: + if (RB_UNLIKELY(data->state->ascii_only)) { + convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table); + } else if (RB_UNLIKELY(data->state->script_safe)) { + convert_UTF8_to_script_safe_JSON(&search); + } else { + convert_UTF8_to_JSON(&search); + } + break; + default: + raise_generator_error(obj, "source sequence is illegal/malformed utf-8"); + break; + } + fbuffer_append_char(buffer, '"'); + RB_GC_GUARD(str); } -static inline VALUE vstate_get(struct generate_json_data *data) +static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { - if (RB_UNLIKELY(!data->vstate)) { - vstate_spill(data); - } - return data->vstate; + obj = ensure_valid_encoding(data, obj, false, false); + raw_generate_json_string(buffer, data, obj); } struct hash_foreach_arg { + VALUE hash; struct generate_json_data *data; - int iter; + int first_key_type; + bool first; + bool mixed_keys_encountered; }; +NOINLINE(static) void +json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg) +{ + if (arg->mixed_keys_encountered) { + return; + } + arg->mixed_keys_encountered = true; + + JSON_Generator_State *state = arg->data->state; + if (!state->allow_duplicate_key) { + rb_funcall(mJSON, rb_intern("on_mixed_keys_hash"), 1, arg->hash); + } +} + static int json_object_i(VALUE key, VALUE val, VALUE _arg) { @@ -797,304 +968,281 @@ json_object_i(VALUE key, VALUE val, VALUE _arg) FBuffer *buffer = data->buffer; JSON_Generator_State *state = data->state; - long depth = state->depth; - int j; + long depth = data->depth; + int key_type = rb_type(key); - if (arg->iter > 0) fbuffer_append_char(buffer, ','); - if (RB_UNLIKELY(state->object_nl)) { - fbuffer_append_str(buffer, state->object_nl); + if (arg->first) { + arg->first = false; + arg->first_key_type = key_type; } - if (RB_UNLIKELY(state->indent)) { - for (j = 0; j < depth; j++) { - fbuffer_append_str(buffer, state->indent); - } + else { + fbuffer_append_char(buffer, ','); + } + + if (RB_UNLIKELY(data->state->object_nl)) { + fbuffer_append_str(buffer, data->state->object_nl); + } + if (RB_UNLIKELY(data->state->indent)) { + fbuffer_append_str_repeat(buffer, data->state->indent, depth); } VALUE key_to_s; - switch(rb_type(key)) { + bool as_json_called = false; + + start: + switch (key_type) { case T_STRING: + if (RB_UNLIKELY(arg->first_key_type != T_STRING)) { + json_inspect_hash_with_mixed_keys(arg); + } + if (RB_LIKELY(RBASIC_CLASS(key) == rb_cString)) { key_to_s = key; } else { - key_to_s = rb_funcall(key, i_to_s, 0); + key_to_s = convert_string_subclass(key); } break; case T_SYMBOL: + if (RB_UNLIKELY(arg->first_key_type != T_SYMBOL)) { + json_inspect_hash_with_mixed_keys(arg); + } + key_to_s = rb_sym2str(key); break; default: + if (data->state->strict) { + if (RTEST(data->state->as_json) && !as_json_called) { + key = json_call_as_json(data->state, key, Qtrue); + key_type = rb_type(key); + as_json_called = true; + goto start; + } else { + raise_generator_error(key, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(key)); + } + } + + json_inspect_hash_with_mixed_keys(arg); + key_to_s = rb_convert_type(key, T_STRING, "String", "to_s"); break; } + key_to_s = ensure_valid_encoding(data, key_to_s, as_json_called, true); + if (RB_LIKELY(RBASIC_CLASS(key_to_s) == rb_cString)) { - generate_json_string(buffer, data, state, key_to_s); + raw_generate_json_string(buffer, data, key_to_s); } else { - generate_json(buffer, data, state, key_to_s); + generate_json(buffer, data, key_to_s); } - if (RB_UNLIKELY(state->space_before)) fbuffer_append_str(buffer, state->space_before); + if (RB_UNLIKELY(state->space_before)) fbuffer_append_str(buffer, data->state->space_before); fbuffer_append_char(buffer, ':'); - if (RB_UNLIKELY(state->space)) fbuffer_append_str(buffer, state->space); - generate_json(buffer, data, state, val); + if (RB_UNLIKELY(state->space)) fbuffer_append_str(buffer, data->state->space); + generate_json(buffer, data, val); - arg->iter++; return ST_CONTINUE; } -static inline long increase_depth(JSON_Generator_State *state) +static inline long increase_depth(struct generate_json_data *data) { - long depth = ++state->depth; - if (RB_UNLIKELY(depth > state->max_nesting && state->max_nesting)) { - rb_raise(eNestingError, "nesting of %ld is too deep", --state->depth); + JSON_Generator_State *state = data->state; + if (RB_UNLIKELY(data->depth >= state->max_nesting && state->max_nesting)) { + rb_raise(eNestingError, "nesting of %ld is too deep. Did you try to serialize objects with circular references?", data->depth + 1); } - return depth; + return ++data->depth; } -static void generate_json_object(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) +static void generate_json_object(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { - int j; - long depth = increase_depth(state); + if (RB_UNLIKELY(data->state->rfc8785)) { + VALUE args[2] = {obj, data->state->strict ? data->state->as_json : Qnil}; + obj = rb_proc_call_with_block(rfc8785_sort_keys_proc, 2, args, Qnil); + Check_Type(obj, T_HASH); + } else if (RB_UNLIKELY(data->state->sort_keys)) { + obj = rb_proc_call_with_block(data->state->sort_keys, 1, &obj, Qnil); + Check_Type(obj, T_HASH); + } + + long depth = increase_depth(data); if (RHASH_SIZE(obj) == 0) { fbuffer_append(buffer, "{}", 2); - --state->depth; + --data->depth; return; } fbuffer_append_char(buffer, '{'); struct hash_foreach_arg arg = { + .hash = obj, .data = data, - .iter = 0, + .first = true, }; rb_hash_foreach(obj, json_object_i, (VALUE)&arg); - depth = --state->depth; - if (RB_UNLIKELY(state->object_nl)) { - fbuffer_append_str(buffer, state->object_nl); - if (RB_UNLIKELY(state->indent)) { - for (j = 0; j < depth; j++) { - fbuffer_append_str(buffer, state->indent); - } + depth = --data->depth; + if (RB_UNLIKELY(data->state->object_nl)) { + fbuffer_append_str(buffer, data->state->object_nl); + if (RB_UNLIKELY(data->state->indent)) { + fbuffer_append_str_repeat(buffer, data->state->indent, depth); } } fbuffer_append_char(buffer, '}'); } -static void generate_json_array(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) +static void generate_json_array(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { - int i, j; - long depth = increase_depth(state); + long depth = increase_depth(data); if (RARRAY_LEN(obj) == 0) { fbuffer_append(buffer, "[]", 2); - --state->depth; + --data->depth; return; } fbuffer_append_char(buffer, '['); - if (RB_UNLIKELY(state->array_nl)) fbuffer_append_str(buffer, state->array_nl); - for(i = 0; i < RARRAY_LEN(obj); i++) { + if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl); + for (rb_len_t i = 0; i < RARRAY_LEN(obj); i++) { if (i > 0) { fbuffer_append_char(buffer, ','); - if (RB_UNLIKELY(state->array_nl)) fbuffer_append_str(buffer, state->array_nl); - } - if (RB_UNLIKELY(state->indent)) { - for (j = 0; j < depth; j++) { - fbuffer_append_str(buffer, state->indent); - } + if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl); } - generate_json(buffer, data, state, RARRAY_AREF(obj, i)); - } - state->depth = --depth; - if (RB_UNLIKELY(state->array_nl)) { - fbuffer_append_str(buffer, state->array_nl); - if (RB_UNLIKELY(state->indent)) { - for (j = 0; j < depth; j++) { - fbuffer_append_str(buffer, state->indent); - } + if (RB_UNLIKELY(data->state->indent)) { + fbuffer_append_str_repeat(buffer, data->state->indent, depth); } + generate_json(buffer, data, RARRAY_AREF(obj, i)); } - fbuffer_append_char(buffer, ']'); -} - -static inline int enc_utf8_compatible_p(int enc_idx) -{ - if (enc_idx == usascii_encindex) return 1; - if (enc_idx == utf8_encindex) return 1; - return 0; -} - -static VALUE encode_json_string_try(VALUE str) -{ - return rb_funcall(str, i_encode, 1, Encoding_UTF_8); -} - -static VALUE encode_json_string_rescue(VALUE str, VALUE exception) -{ - raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0)); - return Qundef; -} - -static inline VALUE ensure_valid_encoding(VALUE str) -{ - int encindex = RB_ENCODING_GET(str); - VALUE utf8_string; - if (RB_UNLIKELY(!enc_utf8_compatible_p(encindex))) { - if (encindex == binary_encindex) { - utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex); - switch (rb_enc_str_coderange(utf8_string)) { - case ENC_CODERANGE_7BIT: - return utf8_string; - case ENC_CODERANGE_VALID: - // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work. - // TODO: Raise in 3.0.0 - rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0"); - return utf8_string; - break; - } + data->depth = --depth; + if (RB_UNLIKELY(data->state->array_nl)) { + fbuffer_append_str(buffer, data->state->array_nl); + if (RB_UNLIKELY(data->state->indent)) { + fbuffer_append_str_repeat(buffer, data->state->indent, depth); } - - str = rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str); - } - return str; -} - -static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) -{ - obj = ensure_valid_encoding(obj); - - fbuffer_append_char(buffer, '"'); - - long len; - search_state search; - search.buffer = buffer; - RSTRING_GETMEM(obj, search.ptr, len); - search.cursor = search.ptr; - search.end = search.ptr + len; - - switch(rb_enc_str_coderange(obj)) { - case ENC_CODERANGE_7BIT: - case ENC_CODERANGE_VALID: - if (RB_UNLIKELY(state->ascii_only)) { - convert_UTF8_to_ASCII_only_JSON(&search, state->script_safe ? script_safe_escape_table : ascii_only_escape_table); - } else if (RB_UNLIKELY(state->script_safe)) { - convert_UTF8_to_script_safe_JSON(&search); - } else { - convert_UTF8_to_JSON(&search); - } - break; - default: - raise_generator_error(obj, "source sequence is illegal/malformed utf-8"); - break; } - fbuffer_append_char(buffer, '"'); + fbuffer_append_char(buffer, ']'); } -static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) +static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { VALUE tmp; if (rb_respond_to(obj, i_to_json)) { - tmp = rb_funcall(obj, i_to_json, 1, vstate_get(data)); + tmp = json_call_to_json(data, obj); Check_Type(tmp, T_STRING); fbuffer_append_str(buffer, tmp); } else { - tmp = rb_funcall(obj, i_to_s, 0); - Check_Type(tmp, T_STRING); - generate_json_string(buffer, data, state, tmp); - } -} - -static inline void generate_json_symbol(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) -{ - if (state->strict) { - generate_json_string(buffer, data, state, rb_sym2str(obj)); - } else { - generate_json_fallback(buffer, data, state, obj); + generate_json_string(buffer, data, json_to_s(obj)); } } -static void generate_json_null(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) +static void generate_json_null(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { fbuffer_append(buffer, "null", 4); } -static void generate_json_false(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) +static void generate_json_false(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { fbuffer_append(buffer, "false", 5); } -static void generate_json_true(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) +static void generate_json_true(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { fbuffer_append(buffer, "true", 4); } -static void generate_json_fixnum(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) +static void generate_json_rfc8785_number(FBuffer *buffer, VALUE obj) { - fbuffer_append_long(buffer, FIX2LONG(obj)); + VALUE str = rb_proc_call_with_block(rfc8785_number_formatter_proc, 1, &obj, Qnil); + Check_Type(str, T_STRING); + fbuffer_append_str(buffer, str); } -static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) +static void generate_json_fixnum(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { - VALUE tmp = rb_funcall(obj, i_to_s, 0); - fbuffer_append_str(buffer, tmp); + if (RB_UNLIKELY(data->state->rfc8785)) { + generate_json_rfc8785_number(buffer, obj); + return; + } + fbuffer_append_long(buffer, FIX2LONG(obj)); } -#ifdef RUBY_INTEGER_UNIFICATION -static void generate_json_integer(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) +static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { - if (FIXNUM_P(obj)) - generate_json_fixnum(buffer, data, state, obj); - else - generate_json_bignum(buffer, data, state, obj); + if (RB_UNLIKELY(data->state->rfc8785)) { + generate_json_rfc8785_number(buffer, obj); + return; + } + fbuffer_append_str(buffer, json_to_s(obj)); } -#endif -static void generate_json_float(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) +static void generate_json_float(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { double value = RFLOAT_VALUE(obj); - char allow_nan = state->allow_nan; - if (!allow_nan) { - if (isinf(value) || isnan(value)) { - if (state->strict && state->as_json) { - VALUE casted_obj = rb_proc_call_with_block(state->as_json, 1, &obj, Qnil); + char allow_nan = data->state->allow_nan; + if (isinf(value) || isnan(value)) { + /* for NaN and Infinity values we either raise an error or rely on Float#to_s. */ + if (!allow_nan) { + if (data->state->strict && data->state->as_json) { + VALUE casted_obj = json_call_as_json(data->state, obj, Qfalse); if (casted_obj != obj) { - increase_depth(state); - generate_json(buffer, data, state, casted_obj); - state->depth--; + increase_depth(data); + generate_json(buffer, data, casted_obj); + data->depth--; return; } } - raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", rb_funcall(obj, i_to_s, 0)); + raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", json_to_s(obj)); } + + fbuffer_append_str(buffer, json_to_s(obj)); + return; + } + + if (RB_UNLIKELY(data->state->rfc8785)) { + generate_json_rfc8785_number(buffer, obj); + return; } - fbuffer_append_str(buffer, rb_funcall(obj, i_to_s, 0)); + + /* This implementation writes directly into the buffer. We reserve + * the 32 characters that fpconv_dtoa states as its maximum. + */ + fbuffer_inc_capa(buffer, 32); + char* d = buffer->ptr + buffer->len; + int len = fpconv_dtoa(value, d); + /* fpconv_dtoa converts a float to its shortest string representation, + * but it adds a ".0" if this is a plain integer. + */ + fbuffer_consumed(buffer, len); } -static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) +static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { VALUE fragment = RSTRUCT_GET(obj, 0); Check_Type(fragment, T_STRING); fbuffer_append_str(buffer, fragment); } -static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON_Generator_State *state, VALUE obj) +static inline void generate_json_general(FBuffer *buffer, struct generate_json_data *data, VALUE obj, bool fallback) { bool as_json_called = false; start: if (obj == Qnil) { - generate_json_null(buffer, data, state, obj); + generate_json_null(buffer, data, obj); } else if (obj == Qfalse) { - generate_json_false(buffer, data, state, obj); + generate_json_false(buffer, data, obj); } else if (obj == Qtrue) { - generate_json_true(buffer, data, state, obj); + generate_json_true(buffer, data, obj); } else if (RB_SPECIAL_CONST_P(obj)) { if (RB_FIXNUM_P(obj)) { - generate_json_fixnum(buffer, data, state, obj); + generate_json_fixnum(buffer, data, obj); } else if (RB_FLONUM_P(obj)) { - generate_json_float(buffer, data, state, obj); + generate_json_float(buffer, data, obj); } else if (RB_STATIC_SYM_P(obj)) { - generate_json_symbol(buffer, data, state, obj); + if (data->state->strict) { + obj = rb_sym2str(obj); + JSON_ASSERT(RBASIC_CLASS(obj) == rb_cString); + goto generate_string; + } + + generate_json_fallback(buffer, data, obj); } else { goto general; } @@ -1102,87 +1250,126 @@ static void generate_json(FBuffer *buffer, struct generate_json_data *data, JSON VALUE klass = RBASIC_CLASS(obj); switch (RB_BUILTIN_TYPE(obj)) { case T_BIGNUM: - generate_json_bignum(buffer, data, state, obj); + generate_json_bignum(buffer, data, obj); break; case T_HASH: - if (klass != rb_cHash) goto general; - generate_json_object(buffer, data, state, obj); + if (fallback && klass != rb_cHash) goto general; + generate_json_object(buffer, data, obj); break; case T_ARRAY: - if (klass != rb_cArray) goto general; - generate_json_array(buffer, data, state, obj); + if (fallback && klass != rb_cArray) goto general; + generate_json_array(buffer, data, obj); break; case T_STRING: - if (klass != rb_cString) goto general; - generate_json_string(buffer, data, state, obj); + if (fallback && klass != rb_cString) goto general; + + generate_string: + if (RB_LIKELY(valid_json_string_p(obj))) { + raw_generate_json_string(buffer, data, obj); + } else if (as_json_called) { + raise_generator_error(obj, "source sequence is illegal/malformed utf-8"); + } else { + obj = ensure_valid_encoding(data, obj, false, false); + as_json_called = true; + goto start; + } break; case T_SYMBOL: - generate_json_symbol(buffer, data, state, obj); + if (data->state->strict) { + obj = rb_sym2str(obj); + JSON_ASSERT(RBASIC_CLASS(obj) == rb_cString); + goto generate_string; + } + + generate_json_fallback(buffer, data, obj); break; case T_FLOAT: - if (klass != rb_cFloat) goto general; - generate_json_float(buffer, data, state, obj); + if (fallback && klass != rb_cFloat) goto general; + generate_json_float(buffer, data, obj); break; case T_STRUCT: if (klass != cFragment) goto general; - generate_json_fragment(buffer, data, state, obj); + generate_json_fragment(buffer, data, obj); break; default: general: - if (state->strict) { - if (RTEST(state->as_json) && !as_json_called) { - obj = rb_proc_call_with_block(state->as_json, 1, &obj, Qnil); + if (data->state->strict) { + if (RTEST(data->state->as_json) && !as_json_called) { + obj = json_call_as_json(data->state, obj, Qfalse); as_json_called = true; goto start; } else { raise_generator_error(obj, "%"PRIsVALUE" not allowed in JSON", CLASS_OF(obj)); } } else { - generate_json_fallback(buffer, data, state, obj); + generate_json_fallback(buffer, data, obj); } } } } +static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +{ + generate_json_general(buffer, data, obj, true); +} + +static void generate_json_no_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +{ + generate_json_general(buffer, data, obj, false); +} + +static void validate_rfc8785(JSON_Generator_State *state) +{ + if (!state->rfc8785) return; + + const char *option = state->indent ? "indent" : + state->space ? "space" : + state->space_before ? "space_before" : + state->object_nl ? "object_nl" : + state->array_nl ? "array_nl" : + state->ascii_only ? "ascii_only" : + state->script_safe ? "script_safe" : + state->allow_nan ? "allow_nan" : NULL; + if (option) { + rb_raise(rb_eArgError, "%s cannot be used with rfc8785", option); + } +} + static VALUE generate_json_try(VALUE d) { struct generate_json_data *data = (struct generate_json_data *)d; - data->func(data->buffer, data, data->state, data->obj); + validate_rfc8785(data->state); + data->func(data->buffer, data, data->obj); - return Qnil; + return fbuffer_finalize(data->buffer); } -static VALUE generate_json_rescue(VALUE d, VALUE exc) +static VALUE generate_json_ensure(VALUE d) { struct generate_json_data *data = (struct generate_json_data *)d; fbuffer_free(data->buffer); - rb_exc_raise(exc); - return Qundef; } -static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, VALUE io) +static inline VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, VALUE io) { GET_STATE(self); char stack_buffer[FBUFFER_STACK_SIZE]; - FBuffer buffer = { - .io = RTEST(io) ? io : Qfalse, - }; - fbuffer_stack_init(&buffer, state->buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE); + FBuffer buffer = { 0 }; + fbuffer_init(&buffer, state->buffer_initial_length, io, stack_buffer, FBUFFER_STACK_SIZE); struct generate_json_data data = { .buffer = &buffer, - .vstate = self, + .vstate = Qfalse, // don't use self as it may be frozen and its depth is mutated when calling to_json .state = state, + .depth = state->depth, .obj = obj, .func = func }; - rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data); - - return fbuffer_finalize(&buffer); + return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data); } /* call-seq: @@ -1198,10 +1385,16 @@ static VALUE cState_generate(int argc, VALUE *argv, VALUE self) rb_check_arity(argc, 1, 2); VALUE obj = argv[0]; VALUE io = argc > 1 ? argv[1] : Qnil; - VALUE result = cState_partial_generate(self, obj, generate_json, io); - GET_STATE(self); - (void)state; - return result; + return cState_partial_generate(self, obj, generate_json, io); +} + +/* :nodoc: */ +static VALUE cState_generate_no_fallback(int argc, VALUE *argv, VALUE self) +{ + rb_check_arity(argc, 1, 2); + VALUE obj = argv[0]; + VALUE io = argc > 1 ? argv[1] : Qnil; + return cState_partial_generate(self, obj, generate_json_no_fallback, io); } static VALUE cState_initialize(int argc, VALUE *argv, VALUE self) @@ -1226,12 +1419,15 @@ static VALUE cState_init_copy(VALUE obj, VALUE orig) if (!objState) rb_raise(rb_eArgError, "unallocated JSON::State"); MEMCPY(objState, origState, JSON_Generator_State, 1); - objState->indent = origState->indent; - objState->space = origState->space; - objState->space_before = origState->space_before; - objState->object_nl = origState->object_nl; - objState->array_nl = origState->array_nl; - objState->as_json = origState->as_json; + + RB_OBJ_WRITTEN(obj, Qundef, objState->indent); + RB_OBJ_WRITTEN(obj, Qundef, objState->space); + RB_OBJ_WRITTEN(obj, Qundef, objState->space_before); + RB_OBJ_WRITTEN(obj, Qundef, objState->object_nl); + RB_OBJ_WRITTEN(obj, Qundef, objState->array_nl); + RB_OBJ_WRITTEN(obj, Qundef, objState->as_json); + RB_OBJ_WRITTEN(obj, Qundef, objState->sort_keys); + return obj; } @@ -1282,6 +1478,7 @@ static VALUE string_config(VALUE config) */ static VALUE cState_indent_set(VALUE self, VALUE indent) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->indent, string_config(indent)); return Qnil; @@ -1307,6 +1504,7 @@ static VALUE cState_space(VALUE self) */ static VALUE cState_space_set(VALUE self, VALUE space) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->space, string_config(space)); return Qnil; @@ -1330,6 +1528,7 @@ static VALUE cState_space_before(VALUE self) */ static VALUE cState_space_before_set(VALUE self, VALUE space_before) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->space_before, string_config(space_before)); return Qnil; @@ -1355,6 +1554,7 @@ static VALUE cState_object_nl(VALUE self) */ static VALUE cState_object_nl_set(VALUE self, VALUE object_nl) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->object_nl, string_config(object_nl)); return Qnil; @@ -1378,6 +1578,7 @@ static VALUE cState_array_nl(VALUE self) */ static VALUE cState_array_nl_set(VALUE self, VALUE array_nl) { + rb_check_frozen(self); GET_STATE(self); RB_OBJ_WRITE(self, &state->array_nl, string_config(array_nl)); return Qnil; @@ -1386,7 +1587,7 @@ static VALUE cState_array_nl_set(VALUE self, VALUE array_nl) /* * call-seq: as_json() * - * This string is put at the end of a line that holds a JSON array. + * Returns the callback used to convert unsupported objects in strict mode. */ static VALUE cState_as_json(VALUE self) { @@ -1394,15 +1595,28 @@ static VALUE cState_as_json(VALUE self) return state->as_json; } +static VALUE as_json_config(VALUE value) +{ + if (rb_obj_is_proc(value)) { + return value; + } + VALUE proc = rb_check_funcall(value, rb_intern("to_proc"), 0, NULL); + if (proc == Qundef || !rb_obj_is_proc(proc)) { + rb_raise(rb_eTypeError, "as_json must be a Proc"); + } + return proc; +} + /* * call-seq: as_json=(as_json) * - * This string is put at the end of a line that holds a JSON array. + * Sets the Proc used to convert unsupported objects in strict mode. */ static VALUE cState_as_json_set(VALUE self, VALUE as_json) { + rb_check_frozen(self); GET_STATE(self); - RB_OBJ_WRITE(self, &state->as_json, rb_convert_type(as_json, T_DATA, "Proc", "to_proc")); + RB_OBJ_WRITE(self, &state->as_json, as_json_config(as_json)); return Qnil; } @@ -1432,7 +1646,21 @@ static VALUE cState_max_nesting(VALUE self) static long long_config(VALUE num) { - return RTEST(num) ? FIX2LONG(num) : 0; + return RTEST(num) ? NUM2LONG(num) : 0; +} + +// depth must never be negative; reject early with a clear error. +static long depth_config(VALUE num) +{ + if (!RTEST(num)) return 0; + long d = NUM2LONG(num); + if (RB_UNLIKELY(d < 0)) { + rb_raise(rb_eArgError, "depth must be >= 0 (got %ld)", d); + } + if (RB_UNLIKELY(d > INT_MAX)) { + rb_raise(rb_eArgError, "depth is too large (got %ld)", d); + } + return d; } /* @@ -1443,6 +1671,7 @@ static long long_config(VALUE num) */ static VALUE cState_max_nesting_set(VALUE self, VALUE depth) { + rb_check_frozen(self); GET_STATE(self); state->max_nesting = long_config(depth); return Qnil; @@ -1468,6 +1697,7 @@ static VALUE cState_script_safe(VALUE self) */ static VALUE cState_script_safe_set(VALUE self, VALUE enable) { + rb_check_frozen(self); GET_STATE(self); state->script_safe = RTEST(enable); return Qnil; @@ -1499,6 +1729,7 @@ static VALUE cState_strict(VALUE self) */ static VALUE cState_strict_set(VALUE self, VALUE enable) { + rb_check_frozen(self); GET_STATE(self); state->strict = RTEST(enable); return Qnil; @@ -1523,6 +1754,7 @@ static VALUE cState_allow_nan_p(VALUE self) */ static VALUE cState_allow_nan_set(VALUE self, VALUE enable) { + rb_check_frozen(self); GET_STATE(self); state->allow_nan = RTEST(enable); return Qnil; @@ -1547,11 +1779,118 @@ static VALUE cState_ascii_only_p(VALUE self) */ static VALUE cState_ascii_only_set(VALUE self, VALUE enable) { + rb_check_frozen(self); GET_STATE(self); state->ascii_only = RTEST(enable); return Qnil; } +static VALUE cState_set_default_sort_keys_proc(VALUE self, VALUE proc) +{ + if (!rb_obj_is_proc(proc)) { + rb_raise(rb_eTypeError, "sort_key_proc must be a Proc"); + } + if (default_sort_keys_proc) { + rb_raise(rb_eArgError, "sort_key_proc can only be set once"); + } + return default_sort_keys_proc = proc; +} + +static VALUE cState_set_rfc8785_number_formatter_proc(VALUE self, VALUE proc) +{ + if (!rb_obj_is_proc(proc)) { + rb_raise(rb_eTypeError, "rfc8785_number_formatter_proc must be a Proc"); + } + if (rfc8785_number_formatter_proc) { + rb_raise(rb_eArgError, "rfc8785_number_formatter_proc can only be set once"); + } + return rfc8785_number_formatter_proc = proc; +} + +static VALUE cState_set_rfc8785_sort_keys_proc(VALUE self, VALUE proc) +{ + if (!rb_obj_is_proc(proc)) { + rb_raise(rb_eTypeError, "rfc8785_sort_keys_proc must be a Proc"); + } + if (rfc8785_sort_keys_proc) { + rb_raise(rb_eArgError, "rfc8785_sort_keys_proc can only be set once"); + } + return rfc8785_sort_keys_proc = proc; +} + +static VALUE normalize_sort_keys(VALUE value) +{ + if (rb_obj_is_proc(value)) { + return value; + } else if (value == Qtrue) { + return default_sort_keys_proc; + } else if (RTEST(value)) { + rb_raise(rb_eTypeError, "The `sort_keys` argument must be a boolean or a Proc"); + } else { + return Qfalse; + } +} + +/* + * call-seq: sort_keys + * + * Get the value of sort_keys. + */ +static VALUE cState_sort_keys(VALUE self) +{ + GET_STATE(self); + return state->sort_keys; +} + +/* + * call-seq: sort_keys=(value) + * + * value is a boolean or a proc. If the value is the boolean true, object keys + * will be sorted lexicographically in ascending order. + * + * If the value is a proc, it receives the entire Hash and must return a Hash + * with its pairs in the desired order, allowing for arbitrary sorting. + */ +static VALUE cState_sort_keys_set(VALUE self, VALUE value) +{ + rb_check_frozen(self); + GET_STATE(self); + RB_OBJ_WRITE(self, &state->sort_keys, normalize_sort_keys(value)); + return state->sort_keys; +} + + +/* + * call-seq: rfc8785? + * + * Get whether RFC8785 (canonicalization) is enabled. + */ +static VALUE cState_rfc8785_p(VALUE self) +{ + GET_STATE(self); + return state->rfc8785 ? Qtrue : Qfalse; +} + +/* + * call-seq: rfc8785= + * + * Sets whether RFC8785 (canonicalization) is enabled. + */ + +static VALUE cState_rfc8785_set(VALUE self, VALUE value) +{ + rb_check_frozen(self); + GET_STATE(self); + state->rfc8785 = RTEST(value); + return state->rfc8785 ? Qtrue : Qfalse; +} + +static VALUE cState_allow_duplicate_key_p(VALUE self) +{ + GET_STATE(self); + return state->allow_duplicate_key ? Qtrue : Qfalse; +} + /* * call-seq: depth * @@ -1566,13 +1905,13 @@ static VALUE cState_depth(VALUE self) /* * call-seq: depth=(depth) * - * This sets the maximum level of data structure nesting in the generated JSON - * to the integer depth, max_nesting = 0 if no maximum should be checked. + * Sets the current depth of data structure nesting to the integer +depth+. */ static VALUE cState_depth_set(VALUE self, VALUE depth) { + rb_check_frozen(self); GET_STATE(self); - state->depth = long_config(depth); + state->depth = depth_config(depth); return Qnil; } @@ -1604,33 +1943,64 @@ static void buffer_initial_length_set(JSON_Generator_State *state, VALUE buffer_ */ static VALUE cState_buffer_initial_length_set(VALUE self, VALUE buffer_initial_length) { + rb_check_frozen(self); GET_STATE(self); buffer_initial_length_set(state, buffer_initial_length); return Qnil; } +struct configure_state_data { + JSON_Generator_State *state; + VALUE vstate; // Ruby object that owns the state, or Qfalse if stack-allocated + VALUE unknown_keywords; +}; + +static inline void state_write_value(struct configure_state_data *data, VALUE *field, VALUE value) +{ + if (RTEST(data->vstate)) { + RB_OBJ_WRITE(data->vstate, field, value); + } else { + *field = value; + } +} + static int configure_state_i(VALUE key, VALUE val, VALUE _arg) { - JSON_Generator_State *state = (JSON_Generator_State *)_arg; + struct configure_state_data *data = (struct configure_state_data *)_arg; + JSON_Generator_State *state = data->state; - if (key == sym_indent) { state->indent = string_config(val); } - else if (key == sym_space) { state->space = string_config(val); } - else if (key == sym_space_before) { state->space_before = string_config(val); } - else if (key == sym_object_nl) { state->object_nl = string_config(val); } - else if (key == sym_array_nl) { state->array_nl = string_config(val); } + if (key == sym_indent) { state_write_value(data, &state->indent, string_config(val)); } + else if (key == sym_space) { state_write_value(data, &state->space, string_config(val)); } + else if (key == sym_space_before) { state_write_value(data, &state->space_before, string_config(val)); } + else if (key == sym_object_nl) { state_write_value(data, &state->object_nl, string_config(val)); } + else if (key == sym_array_nl) { state_write_value(data, &state->array_nl, string_config(val)); } else if (key == sym_max_nesting) { state->max_nesting = long_config(val); } else if (key == sym_allow_nan) { state->allow_nan = RTEST(val); } else if (key == sym_ascii_only) { state->ascii_only = RTEST(val); } - else if (key == sym_depth) { state->depth = long_config(val); } + else if (key == sym_depth) { state->depth = depth_config(val); } else if (key == sym_buffer_initial_length) { buffer_initial_length_set(state, val); } else if (key == sym_script_safe) { state->script_safe = RTEST(val); } - else if (key == sym_escape_slash) { state->script_safe = RTEST(val); } else if (key == sym_strict) { state->strict = RTEST(val); } - else if (key == sym_as_json) { state->as_json = RTEST(val) ? rb_convert_type(val, T_DATA, "Proc", "to_proc") : Qfalse; } + else if (key == sym_allow_duplicate_key) { state->allow_duplicate_key = RTEST(val); } + else if (key == sym_rfc8785) { state->rfc8785 = RTEST(val); } + else if (key == sym_as_json) { + VALUE proc = RTEST(val) ? as_json_config(val) : Qfalse; + state->as_json_single_arg = proc && rb_proc_arity(proc) == 1; + state_write_value(data, &state->as_json, proc); + } + else if (key == sym_sort_keys) { + state_write_value(data, &state->sort_keys, normalize_sort_keys(val)); + } + else { + if (!data->unknown_keywords) { + data->unknown_keywords = rb_obj_hide(rb_ary_new()); + } + rb_ary_push(data->unknown_keywords, key); + } return ST_CONTINUE; } -static void configure_state(JSON_Generator_State *state, VALUE config) +static void configure_state(JSON_Generator_State *state, VALUE vstate, VALUE config) { if (!RTEST(config)) return; @@ -1638,45 +2008,59 @@ static void configure_state(JSON_Generator_State *state, VALUE config) if (!RHASH_SIZE(config)) return; + struct configure_state_data data = { + .state = state, + .vstate = vstate, + .unknown_keywords = Qfalse, + }; + // We assume in most cases few keys are set so it's faster to go over // the provided keys than to check all possible keys. - rb_hash_foreach(config, configure_state_i, (VALUE)state); + rb_hash_foreach(config, configure_state_i, (VALUE)&data); + + raise_argument_error_on_unknown_keywords(data.unknown_keywords); + validate_rfc8785(state); } static VALUE cState_configure(VALUE self, VALUE opts) { + rb_check_frozen(self); GET_STATE(self); - configure_state(state, opts); + configure_state(state, self, opts); return self; } -static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io) +static VALUE cState_m_do_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io, generator_func func) { JSON_Generator_State state = {0}; state_init(&state); - configure_state(&state, opts); + configure_state(&state, Qfalse, opts); char stack_buffer[FBUFFER_STACK_SIZE]; - FBuffer buffer = { - .io = RTEST(io) ? io : Qfalse, - }; - fbuffer_stack_init(&buffer, state.buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE); + FBuffer buffer = { 0 }; + fbuffer_init(&buffer, state.buffer_initial_length, io, stack_buffer, FBUFFER_STACK_SIZE); struct generate_json_data data = { .buffer = &buffer, .vstate = Qfalse, .state = &state, + .depth = state.depth, .obj = obj, - .func = generate_json, + .func = func, }; - rb_rescue(generate_json_try, (VALUE)&data, generate_json_rescue, (VALUE)&data); + return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data); +} + +static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io) +{ + return cState_m_do_generate(klass, obj, opts, io, generate_json); +} - return fbuffer_finalize(&buffer); +static VALUE cState_m_generate_no_fallback(VALUE klass, VALUE obj, VALUE opts, VALUE io) +{ + return cState_m_do_generate(klass, obj, opts, io, generate_json_no_fallback); } -/* - * - */ void Init_generator(void) { #ifdef HAVE_RB_EXT_RACTOR_SAFE @@ -1694,6 +2078,10 @@ void Init_generator(void) VALUE mExt = rb_define_module_under(mJSON, "Ext"); VALUE mGenerator = rb_define_module_under(mExt, "Generator"); + rb_global_variable(&default_sort_keys_proc); + rb_global_variable(&rfc8785_number_formatter_proc); + rb_global_variable(&rfc8785_sort_keys_proc); + rb_global_variable(&eGeneratorError); eGeneratorError = rb_path2class("JSON::GeneratorError"); @@ -1703,6 +2091,10 @@ void Init_generator(void) cState = rb_define_class_under(mGenerator, "State", rb_cObject); rb_define_alloc_func(cState, cState_s_allocate); rb_define_singleton_method(cState, "from_state", cState_from_state_s, 1); + rb_define_singleton_method(cState, "default_sort_keys_proc=", cState_set_default_sort_keys_proc, 1); + rb_define_singleton_method(cState, "rfc8785_number_formatter_proc=", cState_set_rfc8785_number_formatter_proc, 1); + rb_define_singleton_method(cState, "rfc8785_sort_keys_proc=", cState_set_rfc8785_sort_keys_proc, 1); + rb_define_method(cState, "initialize", cState_initialize, -1); rb_define_alias(cState, "initialize", "initialize"); // avoid method redefinition warnings rb_define_private_method(cState, "_configure", cState_configure, 1); @@ -1725,9 +2117,6 @@ void Init_generator(void) rb_define_method(cState, "script_safe", cState_script_safe, 0); rb_define_method(cState, "script_safe?", cState_script_safe, 0); rb_define_method(cState, "script_safe=", cState_script_safe_set, 1); - rb_define_alias(cState, "escape_slash", "script_safe"); - rb_define_alias(cState, "escape_slash?", "script_safe?"); - rb_define_alias(cState, "escape_slash=", "script_safe="); rb_define_method(cState, "strict", cState_strict, 0); rb_define_method(cState, "strict?", cState_strict, 0); rb_define_method(cState, "strict=", cState_strict_set, 1); @@ -1741,51 +2130,16 @@ void Init_generator(void) rb_define_method(cState, "buffer_initial_length", cState_buffer_initial_length, 0); rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1); rb_define_method(cState, "generate", cState_generate, -1); - rb_define_alias(cState, "generate_new", "generate"); // :nodoc: - - rb_define_singleton_method(cState, "generate", cState_m_generate, 3); - - VALUE mGeneratorMethods = rb_define_module_under(mGenerator, "GeneratorMethods"); - - VALUE mObject = rb_define_module_under(mGeneratorMethods, "Object"); - rb_define_method(mObject, "to_json", mObject_to_json, -1); - - VALUE mHash = rb_define_module_under(mGeneratorMethods, "Hash"); - rb_define_method(mHash, "to_json", mHash_to_json, -1); - - VALUE mArray = rb_define_module_under(mGeneratorMethods, "Array"); - rb_define_method(mArray, "to_json", mArray_to_json, -1); - -#ifdef RUBY_INTEGER_UNIFICATION - VALUE mInteger = rb_define_module_under(mGeneratorMethods, "Integer"); - rb_define_method(mInteger, "to_json", mInteger_to_json, -1); -#else - VALUE mFixnum = rb_define_module_under(mGeneratorMethods, "Fixnum"); - rb_define_method(mFixnum, "to_json", mFixnum_to_json, -1); - - VALUE mBignum = rb_define_module_under(mGeneratorMethods, "Bignum"); - rb_define_method(mBignum, "to_json", mBignum_to_json, -1); -#endif - VALUE mFloat = rb_define_module_under(mGeneratorMethods, "Float"); - rb_define_method(mFloat, "to_json", mFloat_to_json, -1); - - VALUE mString = rb_define_module_under(mGeneratorMethods, "String"); - rb_define_singleton_method(mString, "included", mString_included_s, 1); - rb_define_method(mString, "to_json", mString_to_json, -1); - rb_define_method(mString, "to_json_raw", mString_to_json_raw, -1); - rb_define_method(mString, "to_json_raw_object", mString_to_json_raw_object, 0); + rb_define_method(cState, "_generate_no_fallback", cState_generate_no_fallback, -1); + rb_define_method(cState, "sort_keys", cState_sort_keys, 0); + rb_define_method(cState, "sort_keys=", cState_sort_keys_set, 1); + rb_define_method(cState, "rfc8785?", cState_rfc8785_p, 0); + rb_define_method(cState, "rfc8785=", cState_rfc8785_set, 1); - mString_Extend = rb_define_module_under(mString, "Extend"); - rb_define_method(mString_Extend, "json_create", mString_Extend_json_create, 1); + rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0); - VALUE mTrueClass = rb_define_module_under(mGeneratorMethods, "TrueClass"); - rb_define_method(mTrueClass, "to_json", mTrueClass_to_json, -1); - - VALUE mFalseClass = rb_define_module_under(mGeneratorMethods, "FalseClass"); - rb_define_method(mFalseClass, "to_json", mFalseClass_to_json, -1); - - VALUE mNilClass = rb_define_module_under(mGeneratorMethods, "NilClass"); - rb_define_method(mNilClass, "to_json", mNilClass_to_json, -1); + rb_define_singleton_method(cState, "generate", cState_m_generate, 3); + rb_define_singleton_method(cState, "_generate_no_fallback", cState_m_generate_no_fallback, 3); rb_global_variable(&Encoding_UTF_8); Encoding_UTF_8 = rb_const_get(rb_path2class("Encoding"), rb_intern("UTF_8")); @@ -1793,10 +2147,6 @@ void Init_generator(void) i_to_s = rb_intern("to_s"); i_to_json = rb_intern("to_json"); i_new = rb_intern("new"); - i_pack = rb_intern("pack"); - i_unpack = rb_intern("unpack"); - i_create_id = rb_intern("create_id"); - i_extend = rb_intern("extend"); i_encode = rb_intern("encode"); sym_indent = ID2SYM(rb_intern("indent")); @@ -1810,13 +2160,16 @@ void Init_generator(void) sym_depth = ID2SYM(rb_intern("depth")); sym_buffer_initial_length = ID2SYM(rb_intern("buffer_initial_length")); sym_script_safe = ID2SYM(rb_intern("script_safe")); - sym_escape_slash = ID2SYM(rb_intern("escape_slash")); sym_strict = ID2SYM(rb_intern("strict")); sym_as_json = ID2SYM(rb_intern("as_json")); + sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key")); + sym_sort_keys = ID2SYM(rb_intern("sort_keys")); + sym_rfc8785 = ID2SYM(rb_intern("rfc8785")); usascii_encindex = rb_usascii_encindex(); utf8_encindex = rb_utf8_encindex(); - binary_encindex = rb_ascii8bit_encindex(); rb_require("json/ext/generator/state"); + + simd_impl = find_simd_implementation(); } diff --git a/ext/json/ext/json.h b/ext/json/ext/json.h new file mode 100644 index 000000000..a402182d0 --- /dev/null +++ b/ext/json/ext/json.h @@ -0,0 +1,202 @@ +#ifndef _JSON_H_ +#define _JSON_H_ + +#include "ruby.h" +#include "ruby/encoding.h" +#include + +#ifndef HAVE_RB_LEN_T +typedef long rb_len_t; +# define LEN2NUM LONG2NUM +# define PRIdLEN "ld" +#endif + +#ifndef RBIMPL_ASSERT_OR_ASSUME +# define RBIMPL_ASSERT_OR_ASSUME(x) +#endif + +#if defined(RUBY_DEBUG) && RUBY_DEBUG +# define JSON_ASSERT RUBY_ASSERT +# ifndef JSON_DEBUG +# define JSON_DEBUG 1 +# endif +#else +# ifdef JSON_DEBUG +# include +# define JSON_ASSERT(x) assert(x) +# else +# define JSON_ASSERT(x) +# endif +#endif + +#ifdef JSON_DEBUG +# define JSON_UNREACHABLE_RETURN(val) rb_bug("Unreachable") +#else +# define JSON_UNREACHABLE_RETURN UNREACHABLE_RETURN +#endif + +/* shims */ + +#ifndef RUBY_TYPED_THREAD_SAFE_FREE +#define RUBY_TYPED_THREAD_SAFE_FREE RUBY_TYPED_FREE_IMMEDIATELY +#endif + +#ifndef UNDEF_P +#define UNDEF_P(val) (val == Qundef) +#endif + +#if SIZEOF_UINT64_T == SIZEOF_LONG_LONG +# define INT64T2NUM(x) LL2NUM(x) +# define UINT64T2NUM(x) ULL2NUM(x) +#elif SIZEOF_UINT64_T == SIZEOF_LONG +# define INT64T2NUM(x) LONG2NUM(x) +# define UINT64T2NUM(x) ULONG2NUM(x) +#else +# error No uint64_t conversion +#endif + +/* This is the fallback definition from Ruby 3.4 */ +#ifndef RBIMPL_STDBOOL_H +#if defined(__cplusplus) +# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L) +# include +# endif +#elif defined(HAVE_STDBOOL_H) +# include +#elif !defined(HAVE__BOOL) +typedef unsigned char _Bool; +# define bool _Bool +# define true ((_Bool)+1) +# define false ((_Bool)+0) +# define __bool_true_false_are_defined +#endif +#endif + +#ifndef HAVE_RUBY_XFREE_SIZED +static inline void ruby_xfree_sized(void *ptr, size_t oldsize) +{ + ruby_xfree(ptr); +} + +static inline void *ruby_xrealloc2_sized(void *ptr, size_t new_elems, size_t elem_size, size_t old_elems) +{ + return ruby_xrealloc2(ptr, new_elems, elem_size); +} +#endif + +# define JSON_SIZED_REALLOC_N(v, T, m, n) \ + ((v) = (T *)ruby_xrealloc2_sized((void *)(v), (m), sizeof(T), (n))) + +# define JSON_SIZED_FREE(v) ruby_xfree_sized((void *)(v), sizeof(*(v))) +# define JSON_SIZED_FREE_N(v, n) ruby_xfree_sized((void *)(v), sizeof(*(v)) * (n)) + +#ifndef HAVE_RB_EXT_RACTOR_SAFE +# undef RUBY_TYPED_FROZEN_SHAREABLE +# define RUBY_TYPED_FROZEN_SHAREABLE 0 +#endif + +#ifdef RUBY_TYPED_EMBEDDABLE +# define HAVE_RUBY_TYPED_EMBEDDABLE 1 +#else +# ifdef HAVE_CONST_RUBY_TYPED_EMBEDDABLE +# define RUBY_TYPED_EMBEDDABLE RUBY_TYPED_EMBEDDABLE +# define HAVE_RUBY_TYPED_EMBEDDABLE 1 +# else +# define RUBY_TYPED_EMBEDDABLE 0 +# endif +#endif + +#ifndef NORETURN +#if defined(__has_attribute) && __has_attribute(noreturn) +#define NORETURN(x) __attribute__((noreturn)) x +#else +#define NORETURN(x) x +#endif +#endif + +#ifndef NOINLINE +#if defined(__has_attribute) && __has_attribute(noinline) +#define NOINLINE(x) __attribute__((noinline)) x +#else +#define NOINLINE(x) x +#endif +#endif + +#ifndef ALWAYS_INLINE +#if defined(__has_attribute) && __has_attribute(always_inline) +#define ALWAYS_INLINE(x) inline __attribute__((always_inline)) x +#else +#define ALWAYS_INLINE(x) inline x +#endif +#endif + +#ifndef RB_UNLIKELY +#define RB_UNLIKELY(expr) expr +#endif + +#ifndef RB_LIKELY +#define RB_LIKELY(expr) expr +#endif + +#ifndef MAYBE_UNUSED +# define MAYBE_UNUSED(x) x +#endif + +#ifdef RUBY_DEBUG +#ifndef JSON_DEBUG +#define JSON_DEBUG RUBY_DEBUG +#endif +#endif + +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ && INTPTR_MAX == INT64_MAX +#define JSON_CPU_LITTLE_ENDIAN_64BITS 1 +#else +#define JSON_CPU_LITTLE_ENDIAN_64BITS 0 +#endif + +#ifdef JSON_TRUFFLERUBY_RB_CATCH_BUG + +#undef RB_BLOCK_CALL_FUNC_ARGLIST +#define RB_BLOCK_CALL_FUNC_ARGLIST(yielded_arg, func_args) VALUE func_args + +NORETURN(static inline) void json_rb_throw_obj(VALUE tag, VALUE obj) +{ + VALUE exc = rb_exc_new_str(rb_eException, rb_utf8_str_new_cstr("throw_workaround")); + rb_ivar_set(exc, rb_intern("@throw_tag"), tag); + rb_ivar_set(exc, rb_intern("@throw_obj"), obj); + rb_exc_raise(exc); +} +#define rb_throw_obj json_rb_throw_obj + +static inline VALUE json_rb_catch_obj(VALUE tag, VALUE (*func)(VALUE args), VALUE func_args) +{ + int status; + VALUE result = rb_protect(func, func_args, &status); + if (status) { + VALUE exc = rb_errinfo(); + if (tag == rb_ivar_get(exc, rb_intern("@throw_tag"))) { + rb_set_errinfo(Qnil); + return rb_ivar_get(exc, rb_intern("@throw_obj")); + } + rb_jump_tag(status); + } + return result; +} +#define rb_catch_obj json_rb_catch_obj + +#endif // JSON_TRUFFLERUBY_RB_CATCH_BUG + +static inline void raise_argument_error_on_unknown_keywords(VALUE unknown_keywords) +{ + if (RB_UNLIKELY(unknown_keywords)) { + if (RARRAY_LEN(unknown_keywords) == 1) { + rb_raise(rb_eArgError, "unknown keyword: %" PRIsVALUE, RARRAY_AREF(unknown_keywords, 0)); + } + else { + VALUE keywords = rb_ary_join(unknown_keywords, rb_utf8_str_new_cstr(", ")); + rb_raise(rb_eArgError, "unknown keywords: %" PRIsVALUE, keywords); + } + } +} + +#endif // _JSON_H_ diff --git a/ext/json/ext/parser/depend b/ext/json/ext/parser/depend index c051a244f..c46071561 100644 --- a/ext/json/ext/parser/depend +++ b/ext/json/ext/parser/depend @@ -1 +1,6 @@ -parser.o: parser.c $(srcdir)/../fbuffer/fbuffer.h +# AUTOGENERATED DEPENDENCIES START +parser.o: $(srcdir)/../json.h +parser.o: $(srcdir)/../simd/simd.h +parser.o: $(srcdir)/../vendor/fast_float_parser.h +parser.o: parser.c +# AUTOGENERATED DEPENDENCIES END diff --git a/ext/json/ext/parser/extconf.rb b/ext/json/ext/parser/extconf.rb index a8e21aed4..18aa546a9 100644 --- a/ext/json/ext/parser/extconf.rb +++ b/ext/json/ext/parser/extconf.rb @@ -1,12 +1,43 @@ # frozen_string_literal: true require 'mkmf' -have_func("rb_enc_interned_str", "ruby.h") # RUBY_VERSION >= 3.0 +$defs << "-DJSON_DEBUG" if ENV.fetch("JSON_DEBUG", "0") != "0" + +if RUBY_ENGINE == 'truffleruby' && RUBY_ENGINE_VERSION < '40.0' + # Ref: https://github.com/truffleruby/truffleruby/issues/4329 + # Ref: https://github.com/truffleruby/truffleruby/pull/4333 + $defs << "-DJSON_TRUFFLERUBY_RB_CATCH_BUG" +end + +have_func("rb_enc_interned_str", "ruby/encoding.h") # RUBY_VERSION >= 3.0 +have_func("rb_str_to_interned_str", "ruby.h") # RUBY_VERSION >= 3.0 have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2 have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby -have_func("rb_category_warn", "ruby.h") # Missing on TruffleRuby -have_func("strnlen", "string.h") # Missing on Solaris 10 +have_func("ruby_xfree_sized", "ruby.h") # RUBY_VERSION >= 4.1 + +if have_header("x86intrin.h") + have_func("_lzcnt_u64", "x86intrin.h") +end + +if have_header("intrin.h") + have_func("__lzcnt64", "intrin.h") + have_func("_BitScanReverse64", "intrin.h") +end + +if RUBY_ENGINE == "ruby" + have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") # RUBY_VERSION >= 3.3 +end append_cflags("-std=c99") +# Disable function outlining on clang to prevent some of the repeated instructions +# in json_eat_whitespace being outlined into function calls. +# Note: This verifies '-mno-outline' is accepted as a valid compiler flag +# and will not pass it if unsupported. +append_cflags("-mno-outline") + +if enable_config('parser-use-simd', default=!ENV["JSON_DISABLE_SIMD"]) + load __dir__ + "/../simd/conf.rb" +end + create_makefile 'json/ext/parser' diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c index 776eb916f..b456a4e23 100644 --- a/ext/json/ext/parser/parser.c +++ b/ext/json/ext/parser/parser.c @@ -1,64 +1,28 @@ -#include "ruby.h" -#include "ruby/encoding.h" +#include "../json.h" +#include "../vendor/fast_float_parser.h" +#include "../simd/simd.h" -/* shims */ -/* This is the fallback definition from Ruby 3.4 */ +static VALUE mJSON, eNestingError, eParserError, Encoding_UTF_8; +static VALUE CNaN, CInfinity, CMinusInfinity, JSON_empty_string; -#ifndef RBIMPL_STDBOOL_H -#if defined(__cplusplus) -# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L) -# include -# endif -#elif defined(HAVE_STDBOOL_H) -# include -#elif !defined(HAVE__BOOL) -typedef unsigned char _Bool; -# define bool _Bool -# define true ((_Bool)+1) -# define false ((_Bool)+0) -# define __bool_true_false_are_defined -#endif -#endif - -#ifndef RB_UNLIKELY -#define RB_UNLIKELY(expr) expr +static ID i_new, i_try_convert, i_encode, i_at_line, i_at_column, i_at_json_path; +#ifndef HAVE_RB_STR_TO_INTERNED_STR +static ID i_uminus; #endif -#ifndef RB_LIKELY -#define RB_LIKELY(expr) expr -#endif - -static VALUE mJSON, eNestingError, Encoding_UTF_8; -static VALUE CNaN, CInfinity, CMinusInfinity; - -static ID i_json_creatable_p, i_json_create, i_create_id, - i_chr, i_deep_const_get, i_match, i_aset, i_aref, - i_leftshift, i_new, i_try_convert, i_uminus, i_encode; - -static VALUE sym_max_nesting, sym_allow_nan, sym_allow_trailing_comma, sym_symbolize_names, sym_freeze, - sym_create_additions, sym_create_id, sym_object_class, sym_array_class, - sym_decimal_class, sym_match_string; +static VALUE sym_max_nesting, sym_allow_nan, sym_allow_trailing_comma, sym_allow_comments, + sym_allow_control_characters, sym_allow_invalid_escape, sym_symbolize_names, + sym_freeze, sym_decimal_class, sym_on_load, sym_allow_duplicate_key; static int binary_encindex; static int utf8_encindex; -#ifdef HAVE_RB_CATEGORY_WARN -# define json_deprecated(message) rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, message) -#else -# define json_deprecated(message) rb_warn(message) -#endif - -static const char deprecated_create_additions_warning[] = - "JSON.load implicit support for `create_additions: true` is deprecated " - "and will be removed in 3.0, use JSON.unsafe_load or explicitly " - "pass `create_additions: true`"; - #ifndef HAVE_RB_HASH_BULK_INSERT // For TruffleRuby -void -rb_hash_bulk_insert(long count, const VALUE *pairs, VALUE hash) +static void +rb_hash_bulk_insert(rb_len_t count, const VALUE *pairs, VALUE hash) { - long index = 0; + rb_len_t index = 0; while (index < count) { VALUE name = pairs[index++]; VALUE value = pairs[index++]; @@ -72,6 +36,12 @@ rb_hash_bulk_insert(long count, const VALUE *pairs, VALUE hash) #define rb_hash_new_capa(n) rb_hash_new() #endif +#ifndef HAVE_RB_STR_TO_INTERNED_STR +static VALUE rb_str_to_interned_str(VALUE str) +{ + return rb_funcall(rb_str_freeze(str), i_uminus, 0); +} +#endif /* name cache */ @@ -91,11 +61,25 @@ typedef struct rvalue_cache_struct { VALUE entries[JSON_RVALUE_CACHE_CAPA]; } rvalue_cache; +static void rvalue_cache_mark(rvalue_cache *cache) +{ + for (int index = 0; index < cache->length; index++) { + rb_gc_mark_movable(cache->entries[index]); + } +} + +static void rvalue_cache_compact(rvalue_cache *cache) +{ + for (int index = 0; index < cache->length; index++) { + cache->entries[index] = rb_gc_location(cache->entries[index]); + } +} + static rb_encoding *enc_utf8; #define JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH 55 -static inline VALUE build_interned_string(const char *str, const long length) +static inline VALUE build_interned_string(const char *str, const rb_len_t length) { # ifdef HAVE_RB_ENC_INTERNED_STR return rb_enc_interned_str(str, length, enc_utf8); @@ -105,7 +89,7 @@ static inline VALUE build_interned_string(const char *str, const long length) # endif } -static inline VALUE build_symbol(const char *str, const long length) +static inline VALUE build_symbol(const char *str, const rb_len_t length) { return rb_str_intern(build_interned_string(str, length)); } @@ -117,116 +101,104 @@ static void rvalue_cache_insert_at(rvalue_cache *cache, int index, VALUE rstring cache->entries[index] = rstring; } -static inline int rstring_cache_cmp(const char *str, const long length, VALUE rstring) +#define rstring_cache_memcmp memcmp + +#if JSON_CPU_LITTLE_ENDIAN_64BITS +#if __has_builtin(__builtin_bswap64) +#undef rstring_cache_memcmp +ALWAYS_INLINE(static) int rstring_cache_memcmp(const char *str, const char *rptr, const long length) { - long rstring_length = RSTRING_LEN(rstring); - if (length == rstring_length) { - return memcmp(str, RSTRING_PTR(rstring), length); - } else { - return (int)(length - rstring_length); + // The libc memcmp has numerous complex optimizations, but in this particular case, + // we know the string is small (JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH), so being able to + // inline a simpler memcmp outperforms calling the libc version. + long i = 0; + + for (; i + 8 <= length; i += 8) { + uint64_t a, b; + memcpy(&a, str + i, 8); + memcpy(&b, rptr + i, 8); + if (a != b) { + a = __builtin_bswap64(a); + b = __builtin_bswap64(b); + return (a < b) ? -1 : 1; + } + } + + for (; i < length; i++) { + if (str[i] != rptr[i]) { + return (str[i] < rptr[i]) ? -1 : 1; + } } + + return 0; } +#endif +#endif -static VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const long length) +ALWAYS_INLINE(static) int rstring_cache_cmp(const char *str, const long length, VALUE rstring) { - if (RB_UNLIKELY(length > JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH)) { - // Common names aren't likely to be very long. So we just don't - // cache names above an arbitrary threshold. - return Qfalse; - } + const char *rstring_ptr; + rb_len_t rstring_length; - if (RB_UNLIKELY(!isalpha((unsigned char)str[0]))) { - // Simple heuristic, if the first character isn't a letter, - // we're much less likely to see this string again. - // We mostly want to cache strings that are likely to be repeated. - return Qfalse; + RSTRING_GETMEM(rstring, rstring_ptr, rstring_length); + + if (length == rstring_length) { + return rstring_cache_memcmp(str, rstring_ptr, length); + } else { + return (int)(length - rstring_length); } +} +ALWAYS_INLINE(static) VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const long length) +{ int low = 0; int high = cache->length - 1; - int mid = 0; - int last_cmp = 0; while (low <= high) { - mid = (high + low) >> 1; + int mid = (high + low) >> 1; VALUE entry = cache->entries[mid]; - last_cmp = rstring_cache_cmp(str, length, entry); + int cmp = rstring_cache_cmp(str, length, entry); - if (last_cmp == 0) { + if (cmp == 0) { return entry; - } else if (last_cmp > 0) { + } else if (cmp > 0) { low = mid + 1; } else { high = mid - 1; } } - if (RB_UNLIKELY(memchr(str, '\\', length))) { - // We assume the overwhelming majority of names don't need to be escaped. - // But if they do, we have to fallback to the slow path. - return Qfalse; - } - VALUE rstring = build_interned_string(str, length); if (cache->length < JSON_RVALUE_CACHE_CAPA) { - if (last_cmp > 0) { - mid += 1; - } - - rvalue_cache_insert_at(cache, mid, rstring); + rvalue_cache_insert_at(cache, low, rstring); } return rstring; } static VALUE rsymbol_cache_fetch(rvalue_cache *cache, const char *str, const long length) { - if (RB_UNLIKELY(length > JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH)) { - // Common names aren't likely to be very long. So we just don't - // cache names above an arbitrary threshold. - return Qfalse; - } - - if (RB_UNLIKELY(!isalpha((unsigned char)str[0]))) { - // Simple heuristic, if the first character isn't a letter, - // we're much less likely to see this string again. - // We mostly want to cache strings that are likely to be repeated. - return Qfalse; - } - int low = 0; int high = cache->length - 1; - int mid = 0; - int last_cmp = 0; while (low <= high) { - mid = (high + low) >> 1; + int mid = (high + low) >> 1; VALUE entry = cache->entries[mid]; - last_cmp = rstring_cache_cmp(str, length, rb_sym2str(entry)); + int cmp = rstring_cache_cmp(str, length, rb_sym2str(entry)); - if (last_cmp == 0) { + if (cmp == 0) { return entry; - } else if (last_cmp > 0) { + } else if (cmp > 0) { low = mid + 1; } else { high = mid - 1; } } - if (RB_UNLIKELY(memchr(str, '\\', length))) { - // We assume the overwhelming majority of names don't need to be escaped. - // But if they do, we have to fallback to the slow path. - return Qfalse; - } - VALUE rsymbol = build_symbol(str, length); if (cache->length < JSON_RVALUE_CACHE_CAPA) { - if (last_cmp > 0) { - mid += 1; - } - - rvalue_cache_insert_at(cache, mid, rsymbol); + rvalue_cache_insert_at(cache, low, rsymbol); } return rsymbol; } @@ -251,12 +223,12 @@ static rvalue_stack *rvalue_stack_spill(rvalue_stack *old_stack, VALUE *handle, static rvalue_stack *rvalue_stack_grow(rvalue_stack *stack, VALUE *handle, rvalue_stack **stack_ref) { - long required = stack->capa * 2; + long required = stack->capa ? stack->capa * 2 : RVALUE_STACK_INITIAL_CAPA; if (stack->type == RVALUE_STACK_STACK_ALLOCATED) { stack = rvalue_stack_spill(stack, handle, stack_ref); } else { - REALLOC_N(stack->ptr, VALUE, required); + JSON_SIZED_REALLOC_N(stack->ptr, VALUE, required, stack->capa); stack->capa = required; } return stack; @@ -264,11 +236,15 @@ static rvalue_stack *rvalue_stack_grow(rvalue_stack *stack, VALUE *handle, rvalu static VALUE rvalue_stack_push(rvalue_stack *stack, VALUE value, VALUE *handle, rvalue_stack **stack_ref) { + JSON_ASSERT(stack->type != RVALUE_STACK_STACK_ALLOCATED || handle); + if (RB_UNLIKELY(stack->head >= stack->capa)) { stack = rvalue_stack_grow(stack, handle, stack_ref); } + stack->ptr[stack->head] = value; stack->head++; + return value; } @@ -286,48 +262,78 @@ static void rvalue_stack_mark(void *ptr) { rvalue_stack *stack = (rvalue_stack *)ptr; long index; - for (index = 0; index < stack->head; index++) { - rb_gc_mark(stack->ptr[index]); + if (stack && stack->ptr) { + for (index = 0; index < stack->head; index++) { + rb_gc_mark_movable(stack->ptr[index]); + } } } +static void rvalue_stack_free_buffer(rvalue_stack *stack) +{ + JSON_SIZED_FREE_N(stack->ptr, stack->capa); + stack->ptr = NULL; +} + static void rvalue_stack_free(void *ptr) { rvalue_stack *stack = (rvalue_stack *)ptr; if (stack) { - ruby_xfree(stack->ptr); - ruby_xfree(stack); + rvalue_stack_free_buffer(stack); +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE + JSON_SIZED_FREE(stack); +#endif } } static size_t rvalue_stack_memsize(const void *ptr) { const rvalue_stack *stack = (const rvalue_stack *)ptr; - return sizeof(rvalue_stack) + sizeof(VALUE) * stack->capa; + size_t memsize = sizeof(VALUE) * stack->capa; +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE + memsize += sizeof(rvalue_stack); +#endif + return memsize; +} + +static void rvalue_stack_compact(void *ptr) +{ + rvalue_stack *stack = (rvalue_stack *)ptr; + long index; + if (stack && stack->ptr) { + for (index = 0; index < stack->head; index++) { + stack->ptr[index] = rb_gc_location(stack->ptr[index]); + } + } } static const rb_data_type_t JSON_Parser_rvalue_stack_type = { - "JSON::Ext::Parser/rvalue_stack", - { + .wrap_struct_name = "JSON::Ext::Parser/rvalue_stack", + .function = { .dmark = rvalue_stack_mark, .dfree = rvalue_stack_free, .dsize = rvalue_stack_memsize, + .dcompact = rvalue_stack_compact, }, - 0, 0, - RUBY_TYPED_FREE_IMMEDIATELY, + // We deliberately don't declare rvalue_stack as RUBY_TYPED_WB_PROTECTED + // because it churns a lot of values so trigering write barriers every time is very costly. + .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_EMBEDDABLE, }; static rvalue_stack *rvalue_stack_spill(rvalue_stack *old_stack, VALUE *handle, rvalue_stack **stack_ref) { rvalue_stack *stack; - *handle = TypedData_Make_Struct(0, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack); - *stack_ref = stack; + VALUE stack_handle = TypedData_Make_Struct(0, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack); MEMCPY(stack, old_stack, rvalue_stack, 1); stack->capa = old_stack->capa << 1; stack->ptr = ALLOC_N(VALUE, stack->capa); stack->type = RVALUE_STACK_HEAP_ALLOCATED; MEMCPY(stack->ptr, old_stack->ptr, VALUE, old_stack->head); + + *handle = stack_handle; + *stack_ref = stack; + return stack; } @@ -336,49 +342,203 @@ static void rvalue_stack_eagerly_release(VALUE handle) if (handle) { rvalue_stack *stack; TypedData_Get_Struct(handle, rvalue_stack, &JSON_Parser_rvalue_stack_type, stack); - RTYPEDDATA_DATA(handle) = NULL; +#ifdef HAVE_RUBY_TYPED_EMBEDDABLE + rvalue_stack_free_buffer(stack); +#else rvalue_stack_free(stack); + RTYPEDDATA_DATA(handle) = NULL; +#endif } } -/* unicode */ +/* frame stack */ + +// Iterative (non-recursive) parsing keeps an explicit stack of the containers +// currently being built, instead of relying on the C call stack. Each frame +// only needs enough bookkeeping to close its container: which kind it is, the +// rvalue_stack position where its children start (so we know how many to pop), +// and the cursor at its opening brace (used to rewind for duplicate key +// errors). Frames hold no VALUEs, so this stack needs no GC marking; it reuses +// the same stack-allocated-with-heap-spill strategy as the rvalue_stack so that +// it's freed even if parsing raises. +// +// The lifecycle helpers below (grow/push/peek/pop/spill/free/eagerly_release +// and the rb_data_type_t) deliberately mirror their rvalue_stack counterparts +// -- the element type and the absence of a mark function are the only real +// differences. Keep the two in sync: a fix to the spill/release or +// HAVE_RUBY_TYPED_EMBEDDABLE handling in one almost certainly belongs in the +// other. +#define JSON_FRAME_STACK_INITIAL_CAPA 32 + +enum json_frame_type { + JSON_FRAME_ROOT, // == JSON_PHASE_DONE + JSON_FRAME_ARRAY, // == JSON_PHASE_ARRAY_COMMA + JSON_FRAME_OBJECT, // = JSON_PHASE_OBJECT_COMMA +}; -static const signed char digit_values[256] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, - -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1 +// Where a frame is within its container's grammar. This is the entirety of the +// parser's "what to do next" state: json_parse_any dispatches on the top +// frame's phase and holds no resume state in C locals, so a parse can stop at +// any value boundary and be resumed purely from the (persistable) frame stack. +// +// The first three phases are deliberately equal to the corresponding json_frame_type +// to simplify the transition of phase in json_value_completed. +enum json_frame_phase { + JSON_PHASE_DONE = JSON_FRAME_ROOT, // root only: the document value has been parsed + JSON_PHASE_ARRAY_COMMA = JSON_FRAME_ARRAY, // after a value: expecting ',' or the closing ']' + JSON_PHASE_OBJECT_COMMA = JSON_FRAME_OBJECT, // after a value: expecting ',' or the closing '}' + JSON_PHASE_VALUE, // expecting a value (document root, array element, or object value after ':') + JSON_PHASE_OBJECT_KEY, // expecting a '"' key (after '{' or ',') + JSON_PHASE_OBJECT_COLON, // object only: after a key, expecting ':' }; -static uint32_t unescape_unicode(const unsigned char *p) -{ - const uint32_t replacement_char = 0xFFFD; - - signed char b; - uint32_t result = 0; - b = digit_values[p[0]]; - if (b < 0) return replacement_char; - result = (result << 4) | (unsigned char)b; - b = digit_values[p[1]]; - if (b < 0) return replacement_char; - result = (result << 4) | (unsigned char)b; - b = digit_values[p[2]]; - if (b < 0) return replacement_char; - result = (result << 4) | (unsigned char)b; - b = digit_values[p[3]]; - if (b < 0) return replacement_char; - result = (result << 4) | (unsigned char)b; - return result; +typedef struct json_frame_struct { + enum json_frame_type type; + enum json_frame_phase phase; + long value_stack_head; // rvalue_stack->head when this container opened + size_t start_offset; // object frames only (the '{'); NULL otherwise +} json_frame; + +typedef struct json_frame_stack_struct { + enum rvalue_stack_type type; // shared with rvalue_stack: is ptr stack- or heap-allocated + long capa; + long head; + json_frame *ptr; +} json_frame_stack; + +typedef struct JSON_ParserStruct { + VALUE on_load_proc; + VALUE decimal_class; + ID decimal_method_id; + int max_nesting; + bool allow_comments; + bool allow_duplicate_key; + bool allow_nan; + bool allow_trailing_comma; + bool allow_control_characters; + bool allow_invalid_escape; + bool symbolize_names; + bool freeze; +} JSON_ParserConfig; + +typedef struct JSON_ParserStateStruct { + VALUE *value_stack_handle; + VALUE *frame_stack_handle; + const char *start; + const char *cursor; + const char *end; + rvalue_stack *value_stack; + json_frame_stack *frames; + rvalue_cache name_cache; + int in_array; + int current_nesting; + VALUE parser; +} JSON_ParserState; + +static json_frame_stack *json_frame_stack_spill(json_frame_stack *old_stack, VALUE *handle, json_frame_stack **stack_ref); + +static json_frame_stack *json_frame_stack_grow(json_frame_stack *stack, VALUE *handle, json_frame_stack **stack_ref) +{ + long required = stack->capa ? stack->capa * 2 : JSON_FRAME_STACK_INITIAL_CAPA; + + if (stack->type == RVALUE_STACK_STACK_ALLOCATED) { + stack = json_frame_stack_spill(stack, handle, stack_ref); + } else { + JSON_SIZED_REALLOC_N(stack->ptr, json_frame, required, stack->capa); + stack->capa = required; + } + return stack; +} + +static json_frame *json_frame_stack_push(JSON_ParserState *state, json_frame frame) +{ + json_frame_stack *stack = state->frames; + + JSON_ASSERT(stack->type != RVALUE_STACK_STACK_ALLOCATED || state->frame_stack_handle); + + if (RB_UNLIKELY(stack->head >= stack->capa)) { + stack = json_frame_stack_grow(stack, state->frame_stack_handle, &state->frames); + } + + json_frame *frame_ptr = &stack->ptr[stack->head++]; + *frame_ptr = frame; + return frame_ptr; +} + +static inline json_frame *json_frame_stack_peek(json_frame_stack *stack) +{ + return &stack->ptr[stack->head - 1]; +} + +static inline void json_frame_stack_pop(json_frame_stack *stack) +{ + stack->head--; +} + +static void json_frame_stack_free_buffer(json_frame_stack *stack) +{ + JSON_SIZED_FREE_N(stack->ptr, stack->capa); + stack->ptr = NULL; +} + +static void json_frame_stack_free(void *ptr) +{ + json_frame_stack *stack = (json_frame_stack *)ptr; + if (stack) { + json_frame_stack_free_buffer(stack); +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE + JSON_SIZED_FREE(stack); +#endif + } +} + +static size_t json_frame_stack_memsize(const void *ptr) +{ + const json_frame_stack *stack = (const json_frame_stack *)ptr; + + size_t memsize = sizeof(json_frame) * stack->capa; +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE + memsize += sizeof(json_frame_stack); +#endif + return memsize; +} + +static const rb_data_type_t JSON_Parser_frame_stack_type = { + .wrap_struct_name = "JSON::Ext::Parser/frame_stack", + .function = { + .dmark = NULL, + .dfree = json_frame_stack_free, + .dsize = json_frame_stack_memsize, + }, + .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE, +}; + +static json_frame_stack *json_frame_stack_spill(json_frame_stack *old_stack, VALUE *handle, json_frame_stack **stack_ref) +{ + json_frame_stack *stack; + *handle = TypedData_Make_Struct(0, json_frame_stack, &JSON_Parser_frame_stack_type, stack); + *stack_ref = stack; + MEMCPY(stack, old_stack, json_frame_stack, 1); + + stack->capa = old_stack->capa << 1; + stack->ptr = ALLOC_N(json_frame, stack->capa); + stack->type = RVALUE_STACK_HEAP_ALLOCATED; + MEMCPY(stack->ptr, old_stack->ptr, json_frame, old_stack->head); + return stack; +} + +static void json_frame_stack_eagerly_release(VALUE handle) +{ + if (handle) { + json_frame_stack *stack; + TypedData_Get_Struct(handle, json_frame_stack, &JSON_Parser_frame_stack_type, stack); +#ifdef HAVE_RUBY_TYPED_EMBEDDABLE + json_frame_stack_free_buffer(stack); +#else + json_frame_stack_free(stack); + RTYPEDDATA_DATA(handle) = NULL; +#endif + } } static int convert_UTF32_to_UTF8(char *buf, uint32_t ch) @@ -407,191 +567,366 @@ static int convert_UTF32_to_UTF8(char *buf, uint32_t ch) return len; } -typedef struct JSON_ParserStruct { - VALUE create_id; - VALUE object_class; - VALUE array_class; - VALUE decimal_class; - ID decimal_method_id; - VALUE match_string; - int max_nesting; - bool allow_nan; - bool allow_trailing_comma; - bool parsing_name; - bool symbolize_names; - bool freeze; - bool create_additions; - bool deprecated_create_additions; -} JSON_ParserConfig; - -typedef struct JSON_ParserStateStruct { - VALUE stack_handle; - const char *cursor; - const char *end; - rvalue_stack *stack; - rvalue_cache name_cache; - int in_array; - int current_nesting; -} JSON_ParserState; - -#define GET_PARSER_CONFIG \ - JSON_ParserConfig *config; \ - TypedData_Get_Struct(self, JSON_ParserConfig, &JSON_ParserConfig_type, config) +static inline size_t rest(JSON_ParserState *state) { + return state->end - state->cursor; +} -static const rb_data_type_t JSON_ParserConfig_type; +static inline bool eos(JSON_ParserState *state) { + return state->cursor >= state->end; +} -#ifndef HAVE_STRNLEN -static size_t strnlen(const char *s, size_t maxlen) +static inline char peek(JSON_ParserState *state) { - char *p; - return ((p = memchr(s, '\0', maxlen)) ? p - s : maxlen); + if (RB_UNLIKELY(eos(state))) { + return 0; + } + return *state->cursor; } -#endif -#define PARSE_ERROR_FRAGMENT_LEN 32 -#ifdef RBIMPL_ATTR_NORETURN -RBIMPL_ATTR_NORETURN() -#endif -static void raise_parse_error(const char *format, const char *start) +static void cursor_position(JSON_ParserState *state, long *line_out, rb_len_t *column_out) { - unsigned char buffer[PARSE_ERROR_FRAGMENT_LEN + 1]; + JSON_ASSERT(!state->parser); + JSON_ASSERT(state->cursor); + JSON_ASSERT(state->cursor <= state->end); - size_t len = start ? strnlen(start, PARSE_ERROR_FRAGMENT_LEN) : 0; - const char *ptr = start; + // Redundant but helpful for hardening + if (RB_UNLIKELY(state->cursor > state->end)) { + state->cursor = state->end; + } - if (len == PARSE_ERROR_FRAGMENT_LEN) { - MEMCPY(buffer, start, char, PARSE_ERROR_FRAGMENT_LEN); + const char *cursor = state->cursor; + long line = 1; - while (buffer[len - 1] >= 0x80 && buffer[len - 1] < 0xC0) { // Is continuation byte - len--; - } + while (cursor > state->start && cursor[-1] != '\n') { + cursor--; + } + rb_len_t column = rb_enc_strlen(cursor, state->cursor, enc_utf8) + 1; - if (buffer[len - 1] >= 0xC0) { // multibyte character start - len--; + while (cursor > state->start) { + if (*--cursor == '\n') { + line++; } - - buffer[len] = '\0'; - ptr = (const char *)buffer; } - - rb_enc_raise(enc_utf8, rb_path2class("JSON::ParserError"), format, ptr); + *line_out = line; + *column_out = column; } -static const bool whitespace[256] = { - [' '] = 1, - ['\t'] = 1, - ['\n'] = 1, - ['\r'] = 1, - ['/'] = 1, -}; +#define PARSE_ERROR_FRAGMENT_LEN 32 -static void -json_eat_comments(JSON_ParserState *state) -{ - if (state->cursor + 1 < state->end) { - switch(state->cursor[1]) { - case '/': { - state->cursor = memchr(state->cursor, '\n', state->end - state->cursor); - if (!state->cursor) { - state->cursor = state->end; - } else { - state->cursor++; - } +static VALUE build_parse_error_message(const char *format, JSON_ParserState *state) +{ + unsigned char buffer[PARSE_ERROR_FRAGMENT_LEN + 3]; + + const char *ptr = "EOF"; + if (state->cursor && state->cursor < state->end) { + ptr = state->cursor; + size_t len = 0; + while (len < PARSE_ERROR_FRAGMENT_LEN) { + char ch = ptr[len]; + if (!ch || ch == '\n' || ch == ' ' || ch == '\t' || ch == '\r') { break; } - case '*': { - state->cursor += 2; - while (true) { - state->cursor = memchr(state->cursor, '*', state->end - state->cursor); - if (!state->cursor) { - state->cursor = state->end; - raise_parse_error("unexpected end of input, expected closing '*/'", state->cursor); - } else { - state->cursor++; - if (state->cursor < state->end && *state->cursor == '/') { - state->cursor++; - break; - } - } + len++; + } + + if (len) { + if (len == PARSE_ERROR_FRAGMENT_LEN) { + // Only trim when the byte limit splits a multibyte character. + while (len && (unsigned char)ptr[len] >= 0x80 && (unsigned char)ptr[len] < 0xC0) { + len--; } - break; } - default: - raise_parse_error("unexpected token at '%s'", state->cursor); - break; + + buffer[0] = '\''; + MEMCPY(buffer + 1, ptr, char, len); + buffer[len + 1] = '\''; + buffer[len + 2] = '\0'; + ptr = (const char *)buffer; } - } else { - raise_parse_error("unexpected token at '%s'", state->cursor); } + + return rb_enc_sprintf(enc_utf8, format, ptr); } -static inline void -json_eat_whitespace(JSON_ParserState *state) +static VALUE json_path_new(JSON_ParserState *state, VALUE duplicate_key) { - while (state->cursor < state->end && RB_UNLIKELY(whitespace[(unsigned char)*state->cursor])) { - if (RB_LIKELY(*state->cursor != '/')) { - state->cursor++; + VALUE path = rb_ary_new_capa(state->current_nesting); + + json_frame_stack *frames = state->frames; + rvalue_stack *values = state->value_stack; + + for (long depth = 1; depth < frames->head; depth++) { + json_frame *frame = &frames->ptr[depth]; + + bool innermost = depth == frames->head - 1; + long child_head = innermost ? values->head : frames->ptr[depth + 1].value_stack_head; + long count = child_head - frame->value_stack_head; + + if (frame->type == JSON_FRAME_ARRAY) { + rb_ary_push(path, LONG2NUM(frame->phase == JSON_PHASE_ARRAY_COMMA ? count - 1 : count)); + } else if (innermost && !UNDEF_P(duplicate_key)) { + rb_ary_push(path, duplicate_key); + } else if (count & 1) { + rb_ary_push(path, values->ptr[child_head - 1]); + } else if (frame->phase == JSON_PHASE_OBJECT_COMMA && count >= 2) { + rb_ary_push(path, values->ptr[child_head - 2]); } else { - json_eat_comments(state); + break; } } + + return path; } -static inline VALUE build_string(const char *start, const char *end, bool intern, bool symbolize) +static VALUE parse_error_new(JSON_ParserState *state, VALUE message, long line, rb_len_t column, bool eos) { - if (symbolize) { - intern = true; - } - VALUE result; -# ifdef HAVE_RB_ENC_INTERNED_STR - if (intern) { - result = rb_enc_interned_str(start, (long)(end - start), enc_utf8); - } else { - result = rb_utf8_str_new(start, (long)(end - start)); - } -# else - result = rb_utf8_str_new(start, (long)(end - start)); - if (intern) { - result = rb_funcall(rb_str_freeze(result), i_uminus, 0); - } -# endif - - if (symbolize) { - result = rb_str_intern(result); - } - - return result; + VALUE exc = rb_exc_new_str(eParserError, message); + rb_ivar_set(exc, i_at_line, LONG2NUM(line)); + rb_ivar_set(exc, i_at_column, LEN2NUM(column)); + rb_ivar_set(exc, i_at_json_path, json_path_new(state, Qundef)); + return exc; } -static inline VALUE json_string_fastpath(JSON_ParserState *state, const char *string, const char *stringEnd, bool is_name, bool intern, bool symbolize) +NORETURN(static) void raise_parse_error(const char *format, JSON_ParserState *state, bool eos) { - size_t bufferSize = stringEnd - string; - - if (is_name && state->in_array) { - VALUE cached_key; - if (RB_UNLIKELY(symbolize)) { - cached_key = rsymbol_cache_fetch(&state->name_cache, string, bufferSize); + if (state->parser) { + if (eos) { + // the error will be swallowed by ResumableParser#parse, so no + // point building a message or backtrace. + rb_throw_obj(state->parser, state->parser); } else { - cached_key = rstring_cache_fetch(&state->name_cache, string, bufferSize); + // line and columns can't be accurate in resumable + rb_exc_raise(parse_error_new(state, build_parse_error_message(format, state), 0, 0, eos)); } + } else { + VALUE message = build_parse_error_message(format, state); + long line; + rb_len_t column; + cursor_position(state, &line, &column); + rb_str_catf(message, " at line %ld column %"PRIdLEN, line, column); + rb_exc_raise(parse_error_new(state, message, line, column, eos)); + } +} - if (RB_LIKELY(cached_key)) { - return cached_key; +NORETURN(static) void raise_eos_error(const char *format, JSON_ParserState *state) +{ + raise_parse_error(format, state, true); +} + +NORETURN(static) void raise_syntax_error(const char *format, JSON_ParserState *state) +{ + raise_parse_error(format, state, false); +} + +NORETURN(static) void raise_parse_error_at(const char *format, JSON_ParserState *state, const char *at, bool eos) +{ + state->cursor = at; + raise_parse_error(format, state, eos); +} + +NORETURN(static) void raise_eos_error_at(const char *format, JSON_ParserState *state, const char *at) +{ + raise_parse_error_at(format, state, at, true); +} + +NORETURN(static) void raise_syntax_error_at(const char *format, JSON_ParserState *state, const char *at) +{ + raise_parse_error_at(format, state, at, false); +} + +/* unicode */ + +static const signed char digit_values[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, + -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1 +}; + +static uint32_t unescape_unicode(JSON_ParserState *state, const char *sp, const char *spe) +{ + if (RB_UNLIKELY(sp > spe - 4)) { + raise_syntax_error_at("incomplete unicode character escape sequence at %s", state, sp - 2); + } + + const unsigned char *p = (const unsigned char *)sp; + + const signed char b0 = digit_values[p[0]]; + const signed char b1 = digit_values[p[1]]; + const signed char b2 = digit_values[p[2]]; + const signed char b3 = digit_values[p[3]]; + + if (RB_UNLIKELY((signed char)(b0 | b1 | b2 | b3) < 0)) { + raise_syntax_error_at("incomplete unicode character escape sequence at %s", state, sp - 2); + } + + return ((uint32_t)b0 << 12) | ((uint32_t)b1 << 8) | ((uint32_t)b2 << 4) | (uint32_t)b3; +} + +#define GET_PARSER_CONFIG \ + JSON_ParserConfig *config; \ + TypedData_Get_Struct(self, JSON_ParserConfig, &JSON_ParserConfig_type, config) + +static const rb_data_type_t JSON_ParserConfig_type; + +NOINLINE(static) void +json_eat_comments(JSON_ParserState *state, JSON_ParserConfig *config, const char *resume_pos) +{ + if (!config->allow_comments) { + raise_syntax_error("unexpected token %s", state); + } + + const char *start = state->cursor; + // An incomplete comment suspends a resumable parse by rewinding the cursor + // and throwing. Callers that already consumed a token not yet committed to + // the frame stack pass resume_pos so the rewind re-reads that token too. + // Non-resumable error positions keep pointing at the comment either way. + const char *rewind_pos = (state->parser && resume_pos) ? resume_pos : start; + state->cursor++; + + switch (peek(state)) { + case '/': { + const char *newline = memchr(state->cursor, '\n', state->end - state->cursor); + if (!newline) { + // state->parser marks resumable mode, where the buffer end is only a + // chunk boundary: the terminating newline may still arrive, so leave + // the comment unterminated instead of consuming to end as a one-shot + // parse would. + if (state->parser) { + raise_eos_error_at("unterminated comment, expected end of line", state, rewind_pos); + } + state->cursor = state->end; + } else { + state->cursor = newline + 1; + } + break; } + case '*': { + state->cursor++; + + while (true) { + const char *next_match = memchr(state->cursor, '*', state->end - state->cursor); + if (!next_match) { + raise_eos_error_at("unterminated comment, expected closing '*/'", state, rewind_pos); + } + + state->cursor = next_match + 1; + if (peek(state) == '/') { + state->cursor++; + break; + } + } + break; + } + default: + raise_parse_error_at("unexpected token %s", state, eos(state) ? rewind_pos : start, eos(state)); + break; } +} - return build_string(string, stringEnd, intern, symbolize); +ALWAYS_INLINE(static) void +json_eat_whitespace_resume_at(JSON_ParserState *state, JSON_ParserConfig *config, bool include_comments, const char *resume_pos) +{ + while (true) { + switch (peek(state)) { + case ' ': + state->cursor++; + break; + case '\n': + state->cursor++; + + // Heuristic: if we see a newline, there is likely consecutive spaces after it. +#if JSON_CPU_LITTLE_ENDIAN_64BITS + while (rest(state) > 8) { + uint64_t chunk; + memcpy(&chunk, state->cursor, sizeof(uint64_t)); + if (chunk == 0x2020202020202020) { + state->cursor += 8; + continue; + } + + uint32_t consecutive_spaces = trailing_zeros64(chunk ^ 0x2020202020202020) / CHAR_BIT; + state->cursor += consecutive_spaces; + break; + } +#endif + break; + case '\t': + case '\r': + state->cursor++; + break; + case '/': + if (!include_comments) { + return; + } + + json_eat_comments(state, config, resume_pos); + break; + + default: + return; + } + } +} + +ALWAYS_INLINE(static) void +json_eat_whitespace(JSON_ParserState *state, JSON_ParserConfig *config, bool include_comments) +{ + json_eat_whitespace_resume_at(state, config, include_comments, NULL); +} + +static inline VALUE build_string(const char *start, const char *end, bool intern, bool symbolize) +{ + if (symbolize) { + intern = true; + } + VALUE result; +# ifdef HAVE_RB_ENC_INTERNED_STR + if (intern) { + result = rb_enc_interned_str(start, (rb_len_t)(end - start), enc_utf8); + } else { + result = rb_utf8_str_new(start, (rb_len_t)(end - start)); + } +# else + result = rb_utf8_str_new(start, (rb_len_t)(end - start)); + if (intern) { + result = rb_funcall(rb_str_freeze(result), i_uminus, 0); + } +# endif + + if (symbolize) { + result = rb_str_intern(result); + } + + return result; +} + +static inline bool json_string_cacheable_p(const char *string, size_t length) +{ + // We mostly want to cache strings that are likely to be repeated. + // Simple heuristics: + // - Common names aren't likely to be very long. So we just don't cache names above an arbitrary threshold. + // - If the first character isn't a letter, we're much less likely to see this string again. + return length <= JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH && rb_isalpha(string[0]); } -static VALUE json_string_unescape(JSON_ParserState *state, const char *string, const char *stringEnd, bool is_name, bool intern, bool symbolize) +static inline VALUE json_string_fastpath(JSON_ParserState *state, JSON_ParserConfig *config, const char *string, const char *stringEnd, bool is_name) { + bool intern = is_name || config->freeze; + bool symbolize = is_name && config->symbolize_names; size_t bufferSize = stringEnd - string; - const char *p = string, *pe = string, *unescape, *bufferStart; - char *buffer; - int unescape_len; - char buf[4]; - if (is_name && state->in_array) { + if (is_name && state->in_array && RB_LIKELY(json_string_cacheable_p(string, bufferSize))) { VALUE cached_key; if (RB_UNLIKELY(symbolize)) { cached_key = rsymbol_cache_fetch(&state->name_cache, string, bufferSize); @@ -604,83 +939,137 @@ static VALUE json_string_unescape(JSON_ParserState *state, const char *string, c } } + return build_string(string, stringEnd, intern, symbolize); +} + +#define JSON_MAX_UNESCAPE_POSITIONS 16 +typedef struct _json_unescape_positions { + long size; + const char **positions; + unsigned long additional_backslashes; +} JSON_UnescapePositions; + +static inline const char *json_next_backslash(const char *pe, const char *stringEnd, JSON_UnescapePositions *positions) +{ + while (positions->size) { + positions->size--; + const char *next_position = positions->positions[0]; + positions->positions++; + if (next_position >= pe) { + return next_position; + } + } + + if (positions->additional_backslashes) { + positions->additional_backslashes--; + return memchr(pe, '\\', stringEnd - pe); + } + + return NULL; +} + +NOINLINE(static) VALUE json_string_unescape(JSON_ParserState *state, JSON_ParserConfig *config, const char *string, const char *stringEnd, bool is_name, JSON_UnescapePositions *positions) +{ + bool intern = is_name || config->freeze; + bool symbolize = is_name && config->symbolize_names; + size_t bufferSize = stringEnd - string; + const char *p = string, *pe = string, *bufferStart; + char *buffer; + VALUE result = rb_str_buf_new(bufferSize); rb_enc_associate_index(result, utf8_encindex); buffer = RSTRING_PTR(result); bufferStart = buffer; - while ((pe = memchr(pe, '\\', stringEnd - pe))) { - unescape = (char *) "?"; - unescape_len = 1; +#define APPEND_CHAR(chr) *buffer++ = chr; p = ++pe; + + while (pe < stringEnd && (pe = json_next_backslash(pe, stringEnd, positions))) { if (pe > p) { MEMCPY(buffer, p, char, pe - p); buffer += pe - p; } switch (*++pe) { + case '"': + case '/': + p = pe; // nothing to unescape just need to skip the backslash + break; + case '\\': + APPEND_CHAR('\\'); + break; case 'n': - unescape = (char *) "\n"; + APPEND_CHAR('\n'); break; case 'r': - unescape = (char *) "\r"; + APPEND_CHAR('\r'); break; case 't': - unescape = (char *) "\t"; - break; - case '"': - unescape = (char *) "\""; - break; - case '\\': - unescape = (char *) "\\"; + APPEND_CHAR('\t'); break; case 'b': - unescape = (char *) "\b"; + APPEND_CHAR('\b'); break; case 'f': - unescape = (char *) "\f"; + APPEND_CHAR('\f'); break; - case 'u': - if (pe > stringEnd - 5) { - raise_parse_error("incomplete unicode character escape sequence at '%s'", p); - } else { - uint32_t ch = unescape_unicode((unsigned char *) ++pe); - pe += 3; - /* To handle values above U+FFFF, we take a sequence of - * \uXXXX escapes in the U+D800..U+DBFF then - * U+DC00..U+DFFF ranges, take the low 10 bits from each - * to make a 20-bit number, then add 0x10000 to get the - * final codepoint. - * - * See Unicode 15: 3.8 "Surrogates", 5.3 "Handling - * Surrogate Pairs in UTF-16", and 23.6 "Surrogates - * Area". - */ - if ((ch & 0xFC00) == 0xD800) { - pe++; - if (pe > stringEnd - 6) { - raise_parse_error("incomplete surrogate pair at '%s'", p); - } - if (pe[0] == '\\' && pe[1] == 'u') { - uint32_t sur = unescape_unicode((unsigned char *) pe + 2); - ch = (((ch & 0x3F) << 10) | ((((ch >> 6) & 0xF) + 1) << 16) - | (sur & 0x3FF)); - pe += 5; - } else { - unescape = (char *) "?"; - break; + case 'u': { + uint32_t ch = unescape_unicode(state, ++pe, stringEnd); + pe += 3; + /* To handle values above U+FFFF, we take a sequence of + * \uXXXX escapes in the U+D800..U+DBFF then + * U+DC00..U+DFFF ranges, take the low 10 bits from each + * to make a 20-bit number, then add 0x10000 to get the + * final codepoint. + * + * See Unicode 15: 3.8 "Surrogates", 5.3 "Handling + * Surrogate Pairs in UTF-16", and 23.6 "Surrogates + * Area". + */ + if ((ch & 0xFC00) == 0xD800) { + pe++; + if (RB_LIKELY((pe <= stringEnd - 6) && memcmp(pe, "\\u", 2) == 0)) { + uint32_t sur = unescape_unicode(state, pe + 2, stringEnd); + + if (RB_UNLIKELY((sur & 0xFC00) != 0xDC00)) { + raise_syntax_error_at("invalid surrogate pair at %s", state, p); } + + ch = (((ch & 0x3F) << 10) | ((((ch >> 6) & 0xF) + 1) << 16) | (sur & 0x3FF)); + pe += 5; + } else { + raise_syntax_error_at("incomplete surrogate pair at %s", state, p); + break; } - unescape_len = convert_UTF32_to_UTF8(buf, ch); - unescape = buf; + } else if ((ch & 0xFC00) == 0xDC00) { + raise_syntax_error_at("unpaired trailing surrogate at %s", state, p); + break; } + + int unescape_len = convert_UTF32_to_UTF8(buffer, ch); + buffer += unescape_len; + p = ++pe; break; + } default: - p = pe; - continue; + if ((unsigned char)*pe < 0x20) { + if (!config->allow_control_characters) { + if (*pe == '\n') { + raise_syntax_error_at("Invalid unescaped newline character (\\n) in string: %s", state, pe - 1); + } else if (*pe == '\0') { + raise_syntax_error_at("Invalid unescaped NULL character (\\0) in string: %s", state, pe - 1); + } + raise_syntax_error_at("invalid ASCII control character in string: %s", state, pe - 1); + } + } + + if (config->allow_invalid_escape) { + APPEND_CHAR(*pe); + } else { + raise_syntax_error_at("invalid escape character in string: %s", state, pe - 1); + } + break; } - MEMCPY(buffer, unescape, char, unescape_len); - buffer += unescape_len; - p = ++pe; } +#undef APPEND_CHAR if (stringEnd > p) { MEMCPY(buffer, p, char, stringEnd - p); @@ -691,98 +1080,124 @@ static VALUE json_string_unescape(JSON_ParserState *state, const char *string, c if (symbolize) { result = rb_str_intern(result); } else if (intern) { - result = rb_funcall(rb_str_freeze(result), i_uminus, 0); + result = rb_str_to_interned_str(result); } return result; } -#define MAX_FAST_INTEGER_SIZE 18 -static inline VALUE fast_decode_integer(const char *p, const char *pe) -{ - bool negative = false; - if (*p == '-') { - negative = true; - p++; - } +#define MAX_FAST_INTEGER_SIZE 19 +#define MAX_FAST_UINT64_SIZE 20 +#define MAX_NUMBER_STACK_BUFFER 128 - long long memo = 0; - while (p < pe) { - memo *= 10; - memo += *p - '0'; - p++; - } +typedef VALUE (*json_number_decode_func_t)(const char *ptr); - if (negative) { - memo = -memo; +static inline VALUE json_decode_large_number(const char *start, rb_len_t len, json_number_decode_func_t func) +{ + if (RB_LIKELY(len < MAX_NUMBER_STACK_BUFFER)) { + char buffer[MAX_NUMBER_STACK_BUFFER]; + MEMCPY(buffer, start, char, len); + buffer[len] = '\0'; + return func(buffer); + } else { + VALUE buffer_v = rb_str_tmp_new(len); + char *buffer = RSTRING_PTR(buffer_v); + MEMCPY(buffer, start, char, len); + buffer[len] = '\0'; + VALUE number = func(buffer); + RB_GC_GUARD(buffer_v); + return number; } - return LL2NUM(memo); } -static VALUE json_decode_large_integer(const char *start, long len) +static VALUE json_decode_inum(const char *buffer) +{ + return rb_cstr2inum(buffer, 10); +} + +NOINLINE(static) VALUE json_decode_large_integer(const char *start, rb_len_t len) { - VALUE buffer_v; - char *buffer = RB_ALLOCV_N(char, buffer_v, len + 1); - MEMCPY(buffer, start, char, len); - buffer[len] = '\0'; - VALUE number = rb_cstr2inum(buffer, 10); - RB_ALLOCV_END(buffer_v); - return number; + return json_decode_large_number(start, len, json_decode_inum); } -static inline VALUE -json_decode_integer(const char *start, const char *end) +static inline VALUE json_decode_integer(uint64_t mantissa, int mantissa_digits, bool negative, const char *start, const char *end) { - long len = end - start; - if (RB_LIKELY(len < MAX_FAST_INTEGER_SIZE)) { - return fast_decode_integer(start, end); + if (RB_LIKELY(mantissa_digits <= MAX_FAST_INTEGER_SIZE)) { + if (RB_LIKELY(!negative)) { + return UINT64T2NUM(mantissa); } - return json_decode_large_integer(start, len); + + // For a negative number 19 digits in length, we only get half of the range, + // so ensure this negative number is less than INT64_MAX. + // + // Note: This does miss INT64_MIN as it's value is one past INT64_MAX + // when converted to a uint64_t. It will still be parsed correctly by + // falling through to json_decode_large_integer. + if (RB_LIKELY(mantissa <= (uint64_t)INT64_MAX)) { + return INT64T2NUM(-((int64_t)mantissa)); + } + } + + if (!negative && mantissa_digits == MAX_FAST_UINT64_SIZE) { + // Not all 20 digit integers can be safely represented by a uint64_t but + // some can. The memcmp with uint64_max is safe as we've rejected leading + // zeros and we have guaranteed we're comparing it with a 20 digit number. + static const char uint64_max[] = "18446744073709551615"; + if (memcmp(end - MAX_FAST_UINT64_SIZE, uint64_max, MAX_FAST_UINT64_SIZE) <= 0) { + return UINT64T2NUM(mantissa); + } + } + + return json_decode_large_integer(start, end - start); } -static VALUE json_decode_large_float(const char *start, long len) +static VALUE json_decode_dnum(const char *buffer) { - VALUE buffer_v; - char *buffer = RB_ALLOCV_N(char, buffer_v, len + 1); - MEMCPY(buffer, start, char, len); - buffer[len] = '\0'; - VALUE number = DBL2NUM(rb_cstr_to_dbl(buffer, 1)); - RB_ALLOCV_END(buffer_v); - return number; + return DBL2NUM(rb_cstr_to_dbl(buffer, 1)); } -static VALUE json_decode_float(JSON_ParserConfig *config, const char *start, const char *end) +NOINLINE(static) VALUE json_decode_large_float(const char *start, rb_len_t len) { - long len = end - start; + return json_decode_large_number(start, len, json_decode_dnum); +} +/* Ruby JSON optimized float decoder using vendored Ryu algorithm + * Accepts pre-extracted mantissa and exponent from first-pass validation + */ +static inline VALUE json_decode_float(JSON_ParserConfig *config, uint64_t mantissa, int mantissa_digits, int64_t exponent, bool negative, + const char *start, const char *end) +{ if (RB_UNLIKELY(config->decimal_class)) { - VALUE text = rb_str_new(start, len); + VALUE text = rb_str_new(start, end - start); return rb_funcallv(config->decimal_class, config->decimal_method_id, 1, &text); - } else if (RB_LIKELY(len < 64)) { - char buffer[64]; - MEMCPY(buffer, start, char, len); - buffer[len] = '\0'; - return DBL2NUM(rb_cstr_to_dbl(buffer, 1)); - } else { - return json_decode_large_float(start, len); } -} -static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig *config, long count) -{ - VALUE array; - if (RB_UNLIKELY(config->array_class)) { - array = rb_class_new_instance(0, 0, config->array_class); - VALUE *items = rvalue_stack_peek(state->stack, count); - long index; - for (index = 0; index < count; index++) { - rb_funcall(array, i_leftshift, 1, items[index]); + if (RB_UNLIKELY(exponent >= (int64_t)INT32_MAX)) { + return negative ? CMinusInfinity : CInfinity; + } + + if (RB_UNLIKELY(exponent <= INT32_MIN)) { + return rb_float_new(negative ? -0.0 : 0.0); + } + + if (RB_UNLIKELY(mantissa_digits > 18 || mantissa_digits + exponent < -307)) { + // If the value is so small that it definitely underflows to 0.0, return early + // to avoid triggering a "Float out of range" warning from rb_cstr_to_dbl. + // When mantissa_digits + exponent < -324, value < 10^(-324) < DBL_TRUE_MIN/2, + // so it rounds to 0 in IEEE 754 round-to-nearest. + if (RB_UNLIKELY(mantissa_digits + exponent < -324)) { + return rb_float_new(negative ? -0.0 : 0.0); } - } else { - array = rb_ary_new_from_values(count, rvalue_stack_peek(state->stack, count)); + return json_decode_large_float(start, end - start); } - rvalue_stack_pop(state->stack, count); + return DBL2NUM(ffp_s2d(exponent, mantissa, negative)); +} + +static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig *config, long count) +{ + VALUE array = rb_ary_new_from_values(count, rvalue_stack_peek(state->value_stack, count)); + rvalue_stack_pop(state->value_stack, count); if (config->freeze) { RB_OBJ_FREEZE(array); @@ -791,89 +1206,82 @@ static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig return array; } -static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, long count) +static VALUE json_find_duplicated_key(size_t count, const VALUE *pairs) { - VALUE object; - if (RB_UNLIKELY(config->object_class)) { - object = rb_class_new_instance(0, 0, config->object_class); - long index = 0; - VALUE *items = rvalue_stack_peek(state->stack, count); - while (index < count) { - VALUE name = items[index++]; - VALUE value = items[index++]; - rb_funcall(object, i_aset, 2, name, value); - } - } else { - object = rb_hash_new_capa(count); - rb_hash_bulk_insert(count, rvalue_stack_peek(state->stack, count), object); - } - - rvalue_stack_pop(state->stack, count); - - if (RB_UNLIKELY(config->create_additions)) { - VALUE klassname; - if (config->object_class) { - klassname = rb_funcall(object, i_aref, 1, config->create_id); - } else { - klassname = rb_hash_aref(object, config->create_id); - } - if (!NIL_P(klassname)) { - VALUE klass = rb_funcall(mJSON, i_deep_const_get, 1, klassname); - if (RTEST(rb_funcall(klass, i_json_creatable_p, 0))) { - if (config->deprecated_create_additions) { - json_deprecated(deprecated_create_additions_warning); - } - object = rb_funcall(klass, i_json_create, 1, object); + VALUE set = rb_hash_new_capa(count / 2); + for (size_t index = 0; index < count; index += 2) { + size_t before = RHASH_SIZE(set); + VALUE key = pairs[index]; + rb_hash_aset(set, key, Qtrue); + if (RHASH_SIZE(set) == before) { + if (RB_SYMBOL_P(key)) { + return rb_sym2str(key); } + return key; } } + return Qfalse; +} - if (config->freeze) { - RB_OBJ_FREEZE(object); +NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE duplicate_key) +{ + VALUE message = rb_sprintf( + "duplicate key %"PRIsVALUE, + rb_inspect(duplicate_key) + ); + + rb_str_concat(message, build_parse_error_message("", state)); + VALUE exc; + if (state->parser) { // line and columns can't be accurate in resumable + exc = parse_error_new(state, message, 0, 0, false); + } else { + long line; + rb_len_t column; + cursor_position(state, &line, &column); + rb_str_catf(message, " at line %ld column %"PRIdLEN, line, column); + exc = parse_error_new(state, message, line, column, false); } - - return object; + rb_ivar_set(exc, i_at_json_path, json_path_new(state, duplicate_key)); + rb_exc_raise(exc); } -static int match_i(VALUE regexp, VALUE klass, VALUE memo) +NOINLINE(static) void json_on_duplicate_key(JSON_ParserState *state, JSON_ParserConfig *config, size_t count, const VALUE *pairs) { - if (regexp == Qundef) return ST_STOP; - if (RTEST(rb_funcall(klass, i_json_creatable_p, 0)) && - RTEST(rb_funcall(regexp, i_match, 1, rb_ary_entry(memo, 0)))) { - rb_ary_push(memo, klass); - return ST_STOP; + if (!config->allow_duplicate_key) { + raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs)); } - return ST_CONTINUE; } -static inline VALUE json_decode_string(JSON_ParserState *state, JSON_ParserConfig *config, const char *start, const char *end, bool escaped, bool is_name) +static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count) { - VALUE string; - bool intern = is_name || config->freeze; - bool symbolize = is_name && config->symbolize_names; - if (escaped) { - string = json_string_unescape(state, start, end, is_name, intern, symbolize); - } else { - string = json_string_fastpath(state, start, end, is_name, intern, symbolize); + size_t entries_count = count / 2; + VALUE object = rb_hash_new_capa(entries_count); + const VALUE *pairs = rvalue_stack_peek(state->value_stack, count); + rb_hash_bulk_insert(count, pairs, object); + + if (RB_UNLIKELY(RHASH_SIZE(object) < entries_count)) { + json_on_duplicate_key(state, config, count, pairs); } - if (RB_UNLIKELY(config->create_additions && RTEST(config->match_string))) { - VALUE klass; - VALUE memo = rb_ary_new2(2); - rb_ary_push(memo, string); - rb_hash_foreach(config->match_string, match_i, memo); - klass = rb_ary_entry(memo, 1); - if (RTEST(klass)) { - string = rb_funcall(klass, i_json_create, 1, string); - } + rvalue_stack_pop(state->value_stack, count); + + if (config->freeze) { + RB_OBJ_FREEZE(object); } - return string; + return object; } -#define PUSH(result) rvalue_stack_push(state->stack, result, &state->stack_handle, &state->stack) +static inline VALUE json_push_value(JSON_ParserState *state, JSON_ParserConfig *config, VALUE value) +{ + if (RB_UNLIKELY(config->on_load_proc)) { + value = rb_proc_call_with_block(config->on_load_proc, 1, &value, Qnil); + } + rvalue_stack_push(state->value_stack, value, state->value_stack_handle, &state->value_stack); + return value; +} -static const bool string_scan[256] = { +static const bool string_scan_table[256] = { // ASCII Control Characters 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -886,292 +1294,671 @@ static const bool string_scan[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; -static inline VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name) +#ifdef HAVE_SIMD +static SIMD_Implementation simd_impl = SIMD_NONE; +#endif /* HAVE_SIMD */ + +ALWAYS_INLINE(static) bool string_scan(JSON_ParserState *state) { - state->cursor++; - const char *start = state->cursor; - bool escaped = false; +#ifdef HAVE_SIMD +#if defined(HAVE_SIMD_NEON) - while (state->cursor < state->end) { - if (RB_UNLIKELY(string_scan[(unsigned char)*state->cursor])) { - switch (*state->cursor) { - case '"': { - VALUE string = json_decode_string(state, config, start, state->cursor, escaped, is_name); - state->cursor++; - return PUSH(string); - } - case '\\': { - state->cursor++; - escaped = true; - if ((unsigned char)*state->cursor < 0x20) { - raise_parse_error("invalid ASCII control character in string: %s", state->cursor); - } - break; + uint64_t mask = 0; + if (string_scan_simd_neon(&state->cursor, state->end, &mask)) { + state->cursor += trailing_zeros64(mask) >> 2; + return true; + } + +#elif defined(HAVE_SIMD_SSE2) + if (simd_impl == SIMD_SSE2) { + int mask = 0; + if (string_scan_simd_sse2(&state->cursor, state->end, &mask)) { + state->cursor += trailing_zeros(mask); + return true; + } + } +#endif /* HAVE_SIMD_NEON or HAVE_SIMD_SSE2 */ +#endif /* HAVE_SIMD */ + + while (!eos(state)) { + if (RB_UNLIKELY(string_scan_table[(unsigned char)*state->cursor])) { + return true; + } + state->cursor++; + } + + // If the string ended with an unterminated escape sequence, we might + // have gone past the end. + if (RB_UNLIKELY(state->cursor > state->end)) { + state->cursor = state->end; + } + + return false; +} + +static VALUE json_parse_escaped_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name, const char *start) +{ + const char *backslashes[JSON_MAX_UNESCAPE_POSITIONS]; + JSON_UnescapePositions positions = { + .size = 0, + .positions = backslashes, + .additional_backslashes = 0, + }; + + do { + switch (*state->cursor) { + case '"': { + VALUE string = json_string_unescape(state, config, start, state->cursor, is_name, &positions); + state->cursor++; + return string; + } + case '\\': { + if (RB_LIKELY(positions.size < JSON_MAX_UNESCAPE_POSITIONS)) { + backslashes[positions.size] = state->cursor; + positions.size++; + } else { + positions.additional_backslashes++; } - default: - raise_parse_error("invalid ASCII control character in string: %s", state->cursor); - break; + state->cursor++; + break; } + default: + if (!config->allow_control_characters) { + raise_syntax_error("invalid ASCII control character in string: %s", state); + } + break; } state->cursor++; + } while (string_scan(state)); + + return Qundef; +} + +ALWAYS_INLINE(static) VALUE json_parse_string(JSON_ParserState *state, JSON_ParserConfig *config, bool is_name) +{ + state->cursor++; + const char *start = state->cursor; + + if (RB_UNLIKELY(!string_scan(state))) { + return Qundef; } - raise_parse_error("unexpected end of input, expected closing \"", state->cursor); - return Qfalse; + VALUE string; + if (RB_LIKELY(*state->cursor == '"')) { + string = json_string_fastpath(state, config, start, state->cursor, is_name); + state->cursor++; + } + else { + string = json_parse_escaped_string(state, config, is_name, start); + } + + return string; } -static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config) +#if JSON_CPU_LITTLE_ENDIAN_64BITS +// From: https://lemire.me/blog/2022/01/21/swar-explained-parsing-eight-digits/ +// Additional References: +// https://johnnylee-sde.github.io/Fast-numeric-string-to-int/ +// http://0x80.pl/notesen/2014-10-12-parsing-decimal-numbers-part-1-swar.html +static inline uint64_t decode_8digits_unrolled(uint64_t val) { + const uint64_t mask = 0x000000FF000000FF; + const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32) + const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32) + val -= 0x3030303030303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32; + return val; +} + +static inline uint64_t decode_4digits_unrolled(uint32_t val) { + const uint32_t mask = 0x000000FF; + const uint32_t mul1 = 100; + val -= 0x30303030; + val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8; + val = ((val & mask) * mul1) + (((val >> 16) & mask)); + return val; +} +#endif + +static inline int json_parse_digits(JSON_ParserState *state, uint64_t *accumulator) { - json_eat_whitespace(state); - if (state->cursor >= state->end) { - raise_parse_error("unexpected end of input", state->cursor); + const char *start = state->cursor; + +#if JSON_CPU_LITTLE_ENDIAN_64BITS + while (rest(state) >= sizeof(uint64_t)) { + uint64_t next_8bytes; + memcpy(&next_8bytes, state->cursor, sizeof(uint64_t)); + + // From: https://github.com/simdjson/simdjson/blob/32b301893c13d058095a07d9868edaaa42ee07aa/include/simdjson/generic/numberparsing.h#L333 + // Branchless version of: http://0x80.pl/articles/swar-digits-validate.html + uint64_t match = (next_8bytes & 0xF0F0F0F0F0F0F0F0) | (((next_8bytes + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0) >> 4); + + if (match == 0x3333333333333333) { // 8 consecutive digits + *accumulator = (*accumulator * 100000000) + decode_8digits_unrolled(next_8bytes); + state->cursor += 8; + continue; + } + + uint32_t consecutive_digits = trailing_zeros64(match ^ 0x3333333333333333) / CHAR_BIT; + + if (consecutive_digits >= 4) { + *accumulator = (*accumulator * 10000) + decode_4digits_unrolled((uint32_t)next_8bytes); + state->cursor += 4; + consecutive_digits -= 4; + } + + while (consecutive_digits) { + *accumulator = *accumulator * 10 + (*state->cursor - '0'); + consecutive_digits--; + state->cursor++; + } + + return (int)(state->cursor - start); } +#endif - switch (*state->cursor) { - case 'n': - if ((state->end - state->cursor >= 4) && (memcmp(state->cursor, "null", 4) == 0)) { - state->cursor += 4; - return PUSH(Qnil); - } + char next_char; + while (rb_isdigit(next_char = peek(state))) { + *accumulator = *accumulator * 10 + (next_char - '0'); + state->cursor++; + } + return (int)(state->cursor - start); +} - raise_parse_error("unexpected token at '%s'", state->cursor); - break; - case 't': - if ((state->end - state->cursor >= 4) && (memcmp(state->cursor, "true", 4) == 0)) { - state->cursor += 4; - return PUSH(Qtrue); - } +static inline VALUE json_parse_number(JSON_ParserState *state, JSON_ParserConfig *config, bool negative, const char *start, bool resumable) +{ + bool integer = true; + const char first_digit = *state->cursor; - raise_parse_error("unexpected token at '%s'", state->cursor); - break; - case 'f': - // Note: memcmp with a small power of two compile to an integer comparison - if ((state->end - state->cursor >= 5) && (memcmp(state->cursor + 1, "alse", 4) == 0)) { - state->cursor += 5; - return PUSH(Qfalse); - } + // Variables for Ryu optimization - extract digits during parsing + int64_t exponent = 0; + int decimal_point_pos = -1; + uint64_t mantissa = 0; - raise_parse_error("unexpected token at '%s'", state->cursor); - break; - case 'N': - // Note: memcmp with a small power of two compile to an integer comparison - if (config->allow_nan && (state->end - state->cursor >= 3) && (memcmp(state->cursor + 1, "aN", 2) == 0)) { - state->cursor += 3; - return PUSH(CNaN); - } + // Parse integer part and extract mantissa digits + int mantissa_digits = json_parse_digits(state, &mantissa); - raise_parse_error("unexpected token at '%s'", state->cursor); - break; - case 'I': - if (config->allow_nan && (state->end - state->cursor >= 8) && (memcmp(state->cursor, "Infinity", 8) == 0)) { - state->cursor += 8; - return PUSH(CInfinity); - } + if (RB_UNLIKELY((first_digit == '0' && mantissa_digits > 1) || (negative && mantissa_digits == 0))) { + return Qundef; + } - raise_parse_error("unexpected token at '%s'", state->cursor); - break; - case '-': - // Note: memcmp with a small power of two compile to an integer comparison - if ((state->end - state->cursor >= 9) && (memcmp(state->cursor + 1, "Infinity", 8) == 0)) { - if (config->allow_nan) { - state->cursor += 9; - return PUSH(CMinusInfinity); - } else { - raise_parse_error("unexpected token at '%s'", state->cursor); - } - } - // Fallthrough - case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { - bool integer = true; + // Parse fractional part + if (peek(state) == '.') { + integer = false; + decimal_point_pos = mantissa_digits; // Remember position of decimal point + state->cursor++; - // /\A-?(0|[1-9]\d*)(\.\d+)?([Ee][-+]?\d+)?/ - const char *start = state->cursor; + int fractional_digits = json_parse_digits(state, &mantissa); + mantissa_digits += fractional_digits; + + if (RB_UNLIKELY(!fractional_digits)) { + return Qundef; + } + } + + // Parse exponent + if (rb_tolower(peek(state)) == 'e') { + integer = false; + state->cursor++; + + bool negative_exponent = false; + const char next_char = peek(state); + if (next_char == '-' || next_char == '+') { + negative_exponent = next_char == '-'; state->cursor++; + } - while ((state->cursor < state->end) && (*state->cursor >= '0') && (*state->cursor <= '9')) { - state->cursor++; - } + uint64_t abs_exponent = 0; + int exponent_digits = json_parse_digits(state, &abs_exponent); - long integer_length = state->cursor - start; + if (RB_UNLIKELY(!exponent_digits)) { + return Qundef; + } - if (RB_UNLIKELY(start[0] == '0' && integer_length > 1)) { - raise_parse_error("invalid number: %s", start); - } else if (RB_UNLIKELY(integer_length > 2 && start[0] == '-' && start[1] == '0')) { - raise_parse_error("invalid number: %s", start); - } else if (RB_UNLIKELY(integer_length == 1 && start[0] == '-')) { - raise_parse_error("invalid number: %s", start); - } + if (RB_UNLIKELY(exponent_digits >= 20 || abs_exponent > (uint64_t)INT64_MAX)) { + exponent = negative_exponent ? INT32_MIN : INT32_MAX; + } else { + exponent = negative_exponent ? -(int64_t)abs_exponent : (int64_t)abs_exponent; + } + } - if ((state->cursor < state->end) && (*state->cursor == '.')) { - integer = false; - state->cursor++; + // A number touching the end of the buffer may still grow in a later chunk, + // so the caller will rewind and wait. Decoding it now would build a value + // -- for a long run of digits, an expensive bignum -- only to discard it, + // and repeating that on every resumed chunk is quadratic in the number's + // length. The digit scan above already advanced the cursor, which is all + // the caller needs to detect the incomplete number. + if (RB_UNLIKELY(resumable && eos(state))) { + return Qundef; + } + + if (integer) { + return json_decode_integer(mantissa, mantissa_digits, negative, start, state->cursor); + } + + // Adjust exponent based on decimal point position + if (decimal_point_pos >= 0) { + exponent -= (mantissa_digits - decimal_point_pos); + } + + return json_decode_float(config, mantissa, mantissa_digits, exponent, negative, start, state->cursor); +} + +// How many values (array elements, or interleaved object keys+values) have been +// pushed onto the rvalue stack since this container opened. Used to size the +// bulk decode on close, and to tell the first key/colon from later ones. +static inline long json_frame_entry_count(const json_frame *frame, const rvalue_stack *value_stack) +{ + return value_stack->head - frame->value_stack_head; +} + +// A complete value now sits on top of the rvalue stack. Advance the frame that +// was waiting for it: the root document is done, or the enclosing container +// moves on to expecting a ',' or its closing bracket. The caller passes the +// frame it already has in hand -- the one that was expecting the value -- which +// after a container close is the freshly re-exposed parent. +static inline enum json_frame_phase json_value_completed(json_frame *frame) +{ + JSON_ASSERT((int)JSON_PHASE_DONE == (int)JSON_FRAME_ROOT); + JSON_ASSERT((int)JSON_PHASE_ARRAY_COMMA == (int)JSON_FRAME_ARRAY); + JSON_ASSERT((int)JSON_PHASE_OBJECT_COMMA == (int)JSON_FRAME_OBJECT); + + return frame->phase = (enum json_frame_phase) frame->type; +} + +ALWAYS_INLINE(static) void json_match_keyword(JSON_ParserState *state, const char *keyword, size_t offset) +{ + // It is assumed that since `keyword` is always a literal, the compiler is able to constantize this + // `strlen` and several other computations in that routine. + + size_t len = strlen(keyword); + + // Note: memcmp with a small power of two and a literal string compile to an integer comparison / + // That's why we sometime compare starting from the first byte and sometimes from the second. + if (rest(state) >= len && (memcmp(state->cursor + offset, keyword + offset, len - offset) == 0)) { + state->cursor += len; + return; + } + + bool eos = rest(state) < len && memcmp(state->cursor, keyword, rest(state)) == 0; + raise_parse_error("unexpected token %s", state, eos); +} + +// Parse an arbitrary JSON value iteratively. This is a state machine driven +// entirely by the top frame's phase so it can stop at any value boundary and +// resume purely from the frame stack. A JSON_FRAME_ROOT frame sits at the +// bottom of the stack, so the stack is never empty mid-parse and the document +// itself is just another frame whose value, once parsed, leaves its phase DONE. +// When invoked in resumable mode, it returns true after parsing a complete document. +// If reaching EOS without having parsed a complete document, either returns false +// of raise a JSON::ParserError tagged with `@eos=true`. +ALWAYS_INLINE(static) bool json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config, bool resumable) +{ + json_frame *frame = json_frame_stack_peek(state->frames); + + switch (frame->phase) { + case JSON_PHASE_DONE: JSON_UNREACHABLE_RETURN(false); + case JSON_PHASE_ARRAY_COMMA: goto JSON_PHASE_ARRAY_COMMA; + case JSON_PHASE_OBJECT_COMMA: goto JSON_PHASE_OBJECT_COMMA; + case JSON_PHASE_VALUE: goto JSON_PHASE_VALUE; + case JSON_PHASE_OBJECT_KEY: goto JSON_PHASE_OBJECT_KEY; + case JSON_PHASE_OBJECT_COLON: goto JSON_PHASE_OBJECT_COLON; + } + JSON_UNREACHABLE_RETURN(false); + + JSON_PHASE_VALUE: { + json_eat_whitespace(state, config, true); + + // A trailing comma lands us here expecting an element but finding the + // closing bracket; hand off to ARRAY_COMMA to close. An empty array + // closes inline at '[', so this position is only reached after a ','. + if (config->allow_trailing_comma && frame->type == JSON_FRAME_ARRAY && peek(state) == ']') { + goto JSON_PHASE_ARRAY_COMMA; + } + + VALUE value; + const char *value_start = state->cursor; - if (state->cursor == state->end || *state->cursor < '0' || *state->cursor > '9') { - raise_parse_error("invalid number: %s", state->cursor); + switch (peek(state)) { + case 'n': + json_match_keyword(state, "null", 0); + value = Qnil; + break; + + case 't': + json_match_keyword(state, "true", 0); + value = Qtrue; + break; + + case 'f': + json_match_keyword(state, "false", 1); + value = Qfalse; + break; + + case 'N': + if (!config->allow_nan) { + raise_syntax_error("unexpected token %s", state); } - while ((state->cursor < state->end) && (*state->cursor >= '0') && (*state->cursor <= '9')) { - state->cursor++; + json_match_keyword(state, "NaN", 1); + value = CNaN; + break; + + case 'I': + if (!config->allow_nan) { + raise_syntax_error("unexpected token %s", state); } - } - if ((state->cursor < state->end) && ((*state->cursor == 'e') || (*state->cursor == 'E'))) { - integer = false; + json_match_keyword(state, "Infinity", 0); + value = CInfinity; + break; + + case '-': { state->cursor++; - if ((state->cursor < state->end) && ((*state->cursor == '+') || (*state->cursor == '-'))) { - state->cursor++; + + value = json_parse_number(state, config, true, value_start, resumable); + + if (RB_UNLIKELY(UNDEF_P(value) && config->allow_nan && peek(state) == 'I')) { + state->cursor = value_start; + json_match_keyword(state, "-Infinity", 1); + value = CMinusInfinity; + break; } - if (state->cursor == state->end || *state->cursor < '0' || *state->cursor > '9') { - raise_parse_error("invalid number: %s", state->cursor); + // Top level numbers are ambiguous when parsing streams, we can't + // know if we parsed all the digits if we hit EOS. + if (RB_UNLIKELY(resumable && eos(state))) { + state->cursor = value_start; + return false; } - while ((state->cursor < state->end) && (*state->cursor >= '0') && (*state->cursor <= '9')) { - state->cursor++; + if (RB_UNLIKELY(UNDEF_P(value))) { + raise_syntax_error_at("invalid number: %s", state, value_start); } + break; } - if (integer) { - return PUSH(json_decode_integer(start, state->cursor)); - } - return PUSH(json_decode_float(config, start, state->cursor)); - } - case '"': { - // %r{\A"[^"\\\t\n\x00]*(?:\\[bfnrtu\\/"][^"\\]*)*"} - return json_parse_string(state, config, false); - break; - } - case '[': { - state->cursor++; - json_eat_whitespace(state); - long stack_head = state->stack->head; + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { + value = json_parse_number(state, config, false, value_start, resumable); - if ((state->cursor < state->end) && (*state->cursor == ']')) { - state->cursor++; - return PUSH(json_decode_array(state, config, 0)); - } else { - state->current_nesting++; - if (RB_UNLIKELY(config->max_nesting && (config->max_nesting < state->current_nesting))) { - rb_raise(eNestingError, "nesting of %d is too deep", state->current_nesting); + // Top level numbers are ambiguous when parsing streams, we can't + // know if we parsed all the digits if we hit EOS. + if (RB_UNLIKELY(resumable && eos(state))) { + state->cursor = value_start; + return false; } - state->in_array++; - json_parse_any(state, config); + + if (RB_UNLIKELY(UNDEF_P(value))) { + raise_syntax_error_at("invalid number: %s", state, value_start); + } + break; } - while (true) { - json_eat_whitespace(state); - - if (state->cursor < state->end) { - if (*state->cursor == ']') { - state->cursor++; - long count = state->stack->head - stack_head; - state->current_nesting--; - state->in_array--; - return PUSH(json_decode_array(state, config, count)); - } + case '"': { + // %r{\A"[^"\\\t\n\x00]*(?:\\[bfnrtu\\/"][^"\\]*)*"} + value = json_parse_string(state, config, false); - if (*state->cursor == ',') { - state->cursor++; - if (config->allow_trailing_comma) { - json_eat_whitespace(state); - if ((state->cursor < state->end) && (*state->cursor == ']')) { - continue; - } - } - json_parse_any(state, config); - continue; + if (RB_UNLIKELY(UNDEF_P(value))) { + bool is_eos = eos(state); + if (resumable && is_eos) { + state->cursor = value_start; + return false; } + raise_parse_error("unexpected end of input, expected closing \"", state, is_eos); } - - raise_parse_error("expected ',' or ']' after array value", state->cursor); + break; } - break; - } - case '{': { - state->cursor++; - json_eat_whitespace(state); - long stack_head = state->stack->head; - if ((state->cursor < state->end) && (*state->cursor == '}')) { + case '[': { state->cursor++; - return PUSH(json_decode_object(state, config, 0)); - } else { + // The '[' is consumed but its frame is only pushed below, so a + // comment suspending here must resume from the bracket. + json_eat_whitespace_resume_at(state, config, true, value_start); + + const char next = peek(state); + if (next == ']') { + state->cursor++; + value = json_decode_array(state, config, 0); + break; + } else if (resumable && eos(state)) { + state->cursor = value_start; + return false; + } + state->current_nesting++; if (RB_UNLIKELY(config->max_nesting && (config->max_nesting < state->current_nesting))) { rb_raise(eNestingError, "nesting of %d is too deep", state->current_nesting); } + state->in_array++; + + // Phase stays VALUE: the next iteration reads the first element. + frame = json_frame_stack_push(state, (json_frame){ + .type = JSON_FRAME_ARRAY, + .phase = JSON_PHASE_VALUE, + .value_stack_head = state->value_stack->head, + }); + goto JSON_PHASE_VALUE; + } - if (*state->cursor != '"') { - raise_parse_error("expected object key, got '%s", state->cursor); + case '{': { + state->cursor++; + // Same as '[': the frame is only pushed below. + json_eat_whitespace_resume_at(state, config, true, value_start); + + if (peek(state) == '}') { + state->cursor++; + value = json_decode_object(state, config, 0); + break; + } else if (resumable && eos(state)) { + state->cursor = value_start; + return false; } - json_parse_string(state, config, true); - json_eat_whitespace(state); - if ((state->cursor >= state->end) || (*state->cursor != ':')) { - raise_parse_error("expected ':' after object key", state->cursor); + state->current_nesting++; + if (RB_UNLIKELY(config->max_nesting && (config->max_nesting < state->current_nesting))) { + rb_raise(eNestingError, "nesting of %d is too deep", state->current_nesting); } - state->cursor++; - json_parse_any(state, config); + // Phase KEY: the next iteration reads the first key. + frame = json_frame_stack_push(state, (json_frame){ + .type = JSON_FRAME_OBJECT, + .phase = JSON_PHASE_OBJECT_KEY, + .value_stack_head = state->value_stack->head, + .start_offset = value_start - state->start, + }); + goto JSON_PHASE_OBJECT_KEY; } - while (true) { - json_eat_whitespace(state); - - if (state->cursor < state->end) { - if (*state->cursor == '}') { - state->cursor++; - state->current_nesting--; - long count = state->stack->head - stack_head; - return PUSH(json_decode_object(state, config, count)); - } + case 0: + // peek() returns 0 both at end-of-stream and for a literal NUL byte in the + // buffer. Only a genuine EOS means "feed me more"; a NUL byte that is not at + // EOS is just an invalid character. + if (eos(state)) { + return false; + } else { + raise_syntax_error("unexpected NULL byte: %s", state); + } + default: + raise_syntax_error("unexpected character: %s", state); + } - if (*state->cursor == ',') { - state->cursor++; - json_eat_whitespace(state); + json_push_value(state, config, value); + json_value_completed(frame); - if (config->allow_trailing_comma) { - if ((state->cursor < state->end) && (*state->cursor == '}')) { - continue; - } - } + switch (frame->phase) { + case JSON_PHASE_DONE: return true; + case JSON_PHASE_ARRAY_COMMA: goto JSON_PHASE_ARRAY_COMMA; + case JSON_PHASE_OBJECT_COMMA: goto JSON_PHASE_OBJECT_COMMA; + case JSON_PHASE_VALUE: goto JSON_PHASE_VALUE; + case JSON_PHASE_OBJECT_KEY: JSON_UNREACHABLE_RETURN(false); + case JSON_PHASE_OBJECT_COLON: goto JSON_PHASE_OBJECT_COLON; + } + JSON_UNREACHABLE_RETURN(false); + } - if (*state->cursor != '"') { - raise_parse_error("expected object key, got: '%s'", state->cursor); - } - json_parse_string(state, config, true); + JSON_PHASE_OBJECT_KEY: { + JSON_ASSERT(frame->type == JSON_FRAME_OBJECT); - json_eat_whitespace(state); - if ((state->cursor >= state->end) || (*state->cursor != ':')) { - raise_parse_error("expected ':' after object key, got: '%s", state->cursor); - } - state->cursor++; + json_eat_whitespace(state, config, true); + + // A trailing comma lands us here expecting a key but finding the closing + // brace; hand off to OBJECT_COMMA to close. An empty object closes inline + // at '{', so this position is only reached after a ','. + if (config->allow_trailing_comma && peek(state) == '}') { + goto JSON_PHASE_OBJECT_COMMA; + } - json_parse_any(state, config); + const char *start = state->cursor; - continue; - } + if (RB_LIKELY(peek(state) == '"')) { + VALUE string = json_parse_string(state, config, true); + if (UNDEF_P(string)) { + if (resumable && eos(state)) { + state->cursor = start; + return false; + } else { + raise_syntax_error("unexpected end of input, expected closing \"", state); } + } + json_push_value(state, config, string); + frame->phase = JSON_PHASE_OBJECT_COLON; + goto JSON_PHASE_OBJECT_COLON; + } else if (resumable && eos(state)) { + return false; + } else { + // The message differs for the first key vs. a key after a + // ',': the first is the only one reached with nothing pushed + // for this object yet. + if (json_frame_entry_count(frame, state->value_stack) == 0) { + raise_syntax_error("expected object key, got %s", state); + } else { + raise_syntax_error("expected object key, got: %s", state); + } + } + JSON_UNREACHABLE_RETURN(false); + } + + JSON_PHASE_OBJECT_COLON: { + JSON_ASSERT(frame->type == JSON_FRAME_OBJECT); - raise_parse_error("expected ',' or '}' after object value, got: '%s'", state->cursor); + json_eat_whitespace(state, config, true); + + if (RB_LIKELY(peek(state) == ':')) { + state->cursor++; + frame->phase = JSON_PHASE_VALUE; + goto JSON_PHASE_VALUE; + } else if (resumable && eos(state)) { + return false; + } else { + // First colon (only the first pair's key is pushed, nothing + // else) vs. a later one. + if (json_frame_entry_count(frame, state->value_stack) == 1) { + raise_syntax_error("expected ':' after object key", state); + } else { + raise_syntax_error("expected ':' after object key, got: %s", state); } - break; } + JSON_UNREACHABLE_RETURN(false); + } - default: - raise_parse_error("unexpected character: '%s'", state->cursor); - break; + JSON_PHASE_ARRAY_COMMA: { + JSON_ASSERT(frame->type == JSON_FRAME_ARRAY); + + json_eat_whitespace(state, config, true); + + const char next_char = peek(state); + + if (RB_LIKELY(next_char == ',')) { + state->cursor++; + // Commit the phase before eating the whitespace that follows: an + // incomplete comment there would suspend the parse, and a phase not + // yet advanced past the ',' would drop it on resume. A trailing comma + // is recognized in JSON_PHASE_VALUE once the ']' is in the buffer. + frame->phase = JSON_PHASE_VALUE; + goto JSON_PHASE_VALUE; + } else if (next_char == ']') { + state->cursor++; + long count = json_frame_entry_count(frame, state->value_stack); + state->current_nesting--; + state->in_array--; + + json_push_value(state, config, json_decode_array(state, config, count)); + json_frame_stack_pop(state->frames); + frame = json_frame_stack_peek(state->frames); + + json_value_completed(frame); + + switch (frame->phase) { + case JSON_PHASE_DONE: return true; + case JSON_PHASE_ARRAY_COMMA: goto JSON_PHASE_ARRAY_COMMA; + case JSON_PHASE_OBJECT_COMMA: goto JSON_PHASE_OBJECT_COMMA; + case JSON_PHASE_VALUE: goto JSON_PHASE_VALUE; + case JSON_PHASE_OBJECT_KEY: JSON_UNREACHABLE_RETURN(false); + case JSON_PHASE_OBJECT_COLON: goto JSON_PHASE_OBJECT_COLON; + } + } else if (resumable && eos(state)) { + return false; + } else { + raise_syntax_error("expected ',' or ']' after array value", state); + } + JSON_UNREACHABLE_RETURN(false); } - raise_parse_error("unreacheable: '%s'", state->cursor); + JSON_PHASE_OBJECT_COMMA: { + JSON_ASSERT(frame->type == JSON_FRAME_OBJECT); + + json_eat_whitespace(state, config, true); + const char next_char = peek(state); + + if (RB_LIKELY(next_char == ',')) { + state->cursor++; + // Commit the phase before eating the whitespace that follows: an + // incomplete comment there would suspend the parse, and a phase not + // yet advanced past the ',' would drop it on resume. A trailing comma + // is recognized in JSON_PHASE_OBJECT_KEY once the '}' is in the buffer. + frame->phase = JSON_PHASE_OBJECT_KEY; + goto JSON_PHASE_OBJECT_KEY; + } else if (next_char == '}') { + state->cursor++; + state->current_nesting--; + size_t count = json_frame_entry_count(frame, state->value_stack); + + // Temporary rewind cursor in case an error is raised + const char *final_cursor = state->cursor; + state->cursor = state->start + frame->start_offset; + VALUE object = json_decode_object(state, config, count); + state->cursor = final_cursor; + + json_push_value(state, config, object); + json_frame_stack_pop(state->frames); + frame = json_frame_stack_peek(state->frames); + json_value_completed(frame); + + switch (frame->phase) { + case JSON_PHASE_DONE: return true; + case JSON_PHASE_ARRAY_COMMA: goto JSON_PHASE_ARRAY_COMMA; + case JSON_PHASE_OBJECT_COMMA: goto JSON_PHASE_OBJECT_COMMA; + case JSON_PHASE_VALUE: goto JSON_PHASE_VALUE; + case JSON_PHASE_OBJECT_KEY: JSON_UNREACHABLE_RETURN(false); + case JSON_PHASE_OBJECT_COLON: goto JSON_PHASE_OBJECT_COLON; + } + } else if (resumable && eos(state)) { + return false; + } else { + raise_syntax_error("expected ',' or '}' after object value, got: %s", state); + } + JSON_UNREACHABLE_RETURN(false); + } + + JSON_UNREACHABLE_RETURN(false); } -static void json_ensure_eof(JSON_ParserState *state) +static void json_ensure_eof(JSON_ParserState *state, JSON_ParserConfig *config) { - json_eat_whitespace(state); - if (state->cursor != state->end) { - raise_parse_error("unexpected token at end of stream '%s'", state->cursor); + json_eat_whitespace(state, config, true); + if (!eos(state)) { + raise_syntax_error("unexpected token at end of stream %s", state); } } @@ -1189,40 +1976,67 @@ static void json_ensure_eof(JSON_ParserState *state) static VALUE convert_encoding(VALUE source) { - int encindex = RB_ENCODING_GET(source); + StringValue(source); + int encindex = RB_ENCODING_GET(source); + + if (RB_LIKELY(encindex == utf8_encindex)) { + return source; + } - if (RB_LIKELY(encindex == utf8_encindex)) { + if (encindex == binary_encindex) { + // For historical reason, we silently reinterpret binary strings as UTF-8 + return rb_enc_associate_index(rb_str_dup(source), utf8_encindex); + } + + source = rb_funcall(source, i_encode, 1, Encoding_UTF_8); + StringValue(source); return source; - } +} - if (encindex == binary_encindex) { - // For historical reason, we silently reinterpret binary strings as UTF-8 - return rb_enc_associate_index(rb_str_dup(source), utf8_encindex); - } +struct parser_config_init_args { + JSON_ParserConfig *config; + VALUE self; + VALUE unknown_keywords; + bool strict; +}; - return rb_funcall(source, i_encode, 1, Encoding_UTF_8); +static void parser_config_wb_write(VALUE self, VALUE *dest, VALUE val) +{ + *dest = val; + if (self) RB_OBJ_WRITTEN(self, Qundef, val); } static int parser_config_init_i(VALUE key, VALUE val, VALUE data) { - JSON_ParserConfig *config = (JSON_ParserConfig *)data; - - if (key == sym_max_nesting) { config->max_nesting = RTEST(val) ? FIX2INT(val) : 0; } - else if (key == sym_allow_nan) { config->allow_nan = RTEST(val); } - else if (key == sym_allow_trailing_comma) { config->allow_trailing_comma = RTEST(val); } - else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); } - else if (key == sym_freeze) { config->freeze = RTEST(val); } - else if (key == sym_create_id) { config->create_id = RTEST(val) ? val : Qfalse; } - else if (key == sym_object_class) { config->object_class = RTEST(val) ? val : Qfalse; } - else if (key == sym_array_class) { config->array_class = RTEST(val) ? val : Qfalse; } - else if (key == sym_match_string) { config->match_string = RTEST(val) ? val : Qfalse; } - else if (key == sym_decimal_class) { + struct parser_config_init_args *args = (struct parser_config_init_args *)data; + JSON_ParserConfig *config = args->config; + VALUE self = args->self; + + if (key == sym_max_nesting) { config->max_nesting = RTEST(val) ? FIX2INT(val) : 0; } + else if (key == sym_allow_nan) { config->allow_nan = RTEST(val); } + else if (key == sym_allow_trailing_comma) { config->allow_trailing_comma = RTEST(val); } + else if (key == sym_allow_comments) { config->allow_comments = RTEST(val); } + else if (key == sym_allow_control_characters) { config->allow_control_characters = RTEST(val); } + else if (key == sym_allow_invalid_escape) { config->allow_invalid_escape = RTEST(val); } + else if (key == sym_symbolize_names) { config->symbolize_names = RTEST(val); } + else if (key == sym_freeze) { config->freeze = RTEST(val); } + else if (key == sym_on_load) { + if (RTEST(val) && !rb_obj_is_proc(val)) { + val = rb_check_funcall(val, rb_intern("to_proc"), 0, NULL); + if (val == Qundef || !rb_obj_is_proc(val)) { + rb_raise(rb_eTypeError, "on_load must be a Proc"); + } + } + parser_config_wb_write(self, &config->on_load_proc, RTEST(val) ? val : Qfalse); + } + else if (key == sym_allow_duplicate_key) { config->allow_duplicate_key = RTEST(val); } + else if (key == sym_decimal_class) { if (RTEST(val)) { if (rb_respond_to(val, i_try_convert)) { - config->decimal_class = val; + parser_config_wb_write(self, &config->decimal_class, val); config->decimal_method_id = i_try_convert; } else if (rb_respond_to(val, i_new)) { - config->decimal_class = val; + parser_config_wb_write(self, &config->decimal_class, val); config->decimal_method_id = i_new; } else if (RB_TYPE_P(val, T_CLASS)) { VALUE name = rb_class_name(val); @@ -1231,56 +2045,49 @@ static int parser_config_init_i(VALUE key, VALUE val, VALUE data) if (last_colon) { const char *mod_path_end = last_colon - 1; VALUE mod_path = rb_str_substr(name, 0, mod_path_end - name_cstr); - config->decimal_class = rb_path_to_class(mod_path); + parser_config_wb_write(self, &config->decimal_class, rb_path_to_class(mod_path)); const char *method_name_beg = last_colon + 1; - long before_len = method_name_beg - name_cstr; - long len = RSTRING_LEN(name) - before_len; + rb_len_t before_len = method_name_beg - name_cstr; + rb_len_t len = RSTRING_LEN(name) - before_len; VALUE method_name = rb_str_substr(name, before_len, len); config->decimal_method_id = SYM2ID(rb_str_intern(method_name)); } else { - config->decimal_class = rb_mKernel; + parser_config_wb_write(self, &config->decimal_class, rb_mKernel); config->decimal_method_id = SYM2ID(rb_str_intern(name)); } } } } - else if (key == sym_create_additions) { - if (NIL_P(val)) { - config->create_additions = true; - config->deprecated_create_additions = true; - } else { - config->create_additions = RTEST(val); - config->deprecated_create_additions = false; + else { + if (!args->unknown_keywords) { + args->unknown_keywords = rb_obj_hide(rb_ary_new()); } + rb_ary_push(args->unknown_keywords, key); } return ST_CONTINUE; } -static void parser_config_init(JSON_ParserConfig *config, VALUE opts) +static void parser_config_init(JSON_ParserConfig *config, VALUE opts, VALUE self, bool strict) { config->max_nesting = 100; - if (!NIL_P(opts)) { - Check_Type(opts, T_HASH); - if (RHASH_SIZE(opts) > 0) { - // We assume in most cases few keys are set so it's faster to go over - // the provided keys than to check all possible keys. - rb_hash_foreach(opts, parser_config_init_i, (VALUE)config); - - if (config->symbolize_names && config->create_additions) { - rb_raise(rb_eArgError, - "options :symbolize_names and :create_additions cannot be " - " used in conjunction"); - } + struct parser_config_init_args args = { + .config = config, + .self = self, + .strict = strict, + }; - if (config->create_additions && !config->create_id) { - config->create_id = rb_funcall(mJSON, i_create_id, 0); - } - } + if (NIL_P(opts)) return; + Check_Type(opts, T_HASH); + if (RHASH_SIZE(opts) == 0) return; - } + // We assume in most cases few keys are set so it's faster to go over + // the provided keys than to check all possible keys. + rb_hash_foreach(opts, parser_config_init_i, (VALUE)&args); + + raise_argument_error_on_unknown_keywords(args.unknown_keywords); } /* @@ -1288,74 +2095,88 @@ static void parser_config_init(JSON_ParserConfig *config, VALUE opts) * * Creates a new JSON::Ext::ParserConfig instance. * - * It will be configured by the _opts_ hash. _opts_ can have the following - * keys: + * Argument +opts+, if given, contains a \Hash of options for the parsing. + * See {Parsing Options}[#module-JSON-label-Parsing+Options]. * - * _opts_ can have the following keys: - * * *max_nesting*: The maximum depth of nesting allowed in the parsed data - * structures. Disable depth checking with :max_nesting => false|nil|0, it - * defaults to 100. - * * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in - * defiance of RFC 4627 to be parsed by the Parser. This option defaults to - * false. - * * *symbolize_names*: If set to true, returns symbols for the names - * (keys) in a JSON object. Otherwise strings are returned, which is - * also the default. It's not possible to use this option in - * conjunction with the *create_additions* option. - * * *create_additions*: If set to false, the Parser doesn't create - * additions even if a matching class and create_id was found. This option - * defaults to false. - * * *object_class*: Defaults to Hash. If another type is provided, it will be used - * instead of Hash to represent JSON objects. The type must respond to - * +new+ without arguments, and return an object that respond to +[]=+. - * * *array_class*: Defaults to Array If another type is provided, it will be used - * instead of Hash to represent JSON arrays. The type must respond to - * +new+ without arguments, and return an object that respond to +<<+. - * * *decimal_class*: Specifies which class to use instead of the default - * (Float) when parsing decimal numbers. This class must accept a single - * string argument in its constructor. */ static VALUE cParserConfig_initialize(VALUE self, VALUE opts) { + rb_check_frozen(self); GET_PARSER_CONFIG; - parser_config_init(config, opts); - - RB_OBJ_WRITTEN(self, Qundef, config->create_id); - RB_OBJ_WRITTEN(self, Qundef, config->object_class); - RB_OBJ_WRITTEN(self, Qundef, config->array_class); - RB_OBJ_WRITTEN(self, Qundef, config->decimal_class); - RB_OBJ_WRITTEN(self, Qundef, config->match_string); + parser_config_init(config, opts, self, false); return self; } -static VALUE cParser_parse(JSON_ParserConfig *config, VALUE Vsource) +static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src) { - Vsource = convert_encoding(StringValue(Vsource)); - StringValue(Vsource); + VALUE Vsource = convert_encoding(src); + + // Ensure the string isn't mutated under us. + // The classic API to use is `rb_str_locktmp`, but then we'd + // need to use `rb_protect` to make sure we always unlock. + Vsource = rb_str_new_frozen(Vsource); VALUE rvalue_stack_buffer[RVALUE_STACK_INITIAL_CAPA]; - rvalue_stack stack = { + rvalue_stack value_stack = { .type = RVALUE_STACK_STACK_ALLOCATED, .ptr = rvalue_stack_buffer, .capa = RVALUE_STACK_INITIAL_CAPA, }; + // Seed the frame stack with the root frame, establishing the invariant that + // json_parse_any always has a top frame to dispatch on (so the stack is never + // empty mid-parse). + json_frame frame_stack_buffer[JSON_FRAME_STACK_INITIAL_CAPA]; + frame_stack_buffer[0] = (json_frame){ + .type = JSON_FRAME_ROOT, + .phase = JSON_PHASE_VALUE, + }; + json_frame_stack frames = { + .type = RVALUE_STACK_STACK_ALLOCATED, + .ptr = frame_stack_buffer, + .capa = JSON_FRAME_STACK_INITIAL_CAPA, + .head = 1, + }; + + rb_len_t len; + const char *start; + + RSTRING_GETMEM(Vsource, start, len); + + VALUE value_stack_handle = 0; + VALUE frame_stack_handle = 0; JSON_ParserState _state = { - .cursor = RSTRING_PTR(Vsource), - .end = RSTRING_END(Vsource), - .stack = &stack, + .start = start, + .cursor = start, + .end = start + len, + .value_stack = &value_stack, + .value_stack_handle = &value_stack_handle, + .frames = &frames, + .frame_stack_handle = &frame_stack_handle, }; JSON_ParserState *state = &_state; - VALUE result = json_parse_any(state, config); + bool complete = json_parse_any(state, config, false); + + // The root document value is parsed; it is the lone survivor on + // the rvalue stack. + VALUE result = complete ? *rvalue_stack_peek(state->value_stack, 1) : Qundef; + + if (complete) { + json_ensure_eof(state, config); + } else { + raise_eos_error("unexpected end of input", state); + } // This may be skipped in case of exception, but // it won't cause a leak. - rvalue_stack_eagerly_release(state->stack_handle); - - json_ensure_eof(state); + rvalue_stack_eagerly_release(value_stack_handle); + json_frame_stack_eagerly_release(frame_stack_handle); + RB_GC_GUARD(value_stack_handle); + RB_GC_GUARD(frame_stack_handle); + RB_GC_GUARD(Vsource); return result; } @@ -1375,12 +2196,9 @@ static VALUE cParserConfig_parse(VALUE self, VALUE Vsource) static VALUE cParser_m_parse(VALUE klass, VALUE Vsource, VALUE opts) { - Vsource = convert_encoding(StringValue(Vsource)); - StringValue(Vsource); - JSON_ParserConfig _config = {0}; JSON_ParserConfig *config = &_config; - parser_config_init(config, opts); + parser_config_init(config, opts, Qfalse, false); return cParser_parse(config, Vsource); } @@ -1388,33 +2206,35 @@ static VALUE cParser_m_parse(VALUE klass, VALUE Vsource, VALUE opts) static void JSON_ParserConfig_mark(void *ptr) { JSON_ParserConfig *config = ptr; - rb_gc_mark(config->create_id); - rb_gc_mark(config->object_class); - rb_gc_mark(config->array_class); - rb_gc_mark(config->decimal_class); - rb_gc_mark(config->match_string); + rb_gc_mark_movable(config->on_load_proc); + rb_gc_mark_movable(config->decimal_class); } -static void JSON_ParserConfig_free(void *ptr) +static size_t JSON_ParserConfig_memsize(const void *ptr) { - JSON_ParserConfig *config = ptr; - ruby_xfree(config); +#ifdef HAVE_RUBY_TYPED_EMBEDDABLE + return 0; +#else + return sizeof(JSON_ParserConfig); +#endif } -static size_t JSON_ParserConfig_memsize(const void *ptr) +static void JSON_ParserConfig_compact(void *ptr) { - return sizeof(JSON_ParserConfig); + JSON_ParserConfig *config = ptr; + config->on_load_proc = rb_gc_location(config->on_load_proc); + config->decimal_class = rb_gc_location(config->decimal_class); } static const rb_data_type_t JSON_ParserConfig_type = { - "JSON::Ext::Parser/ParserConfig", - { - JSON_ParserConfig_mark, - JSON_ParserConfig_free, - JSON_ParserConfig_memsize, + .wrap_struct_name = "JSON::Ext::Parser/ParserConfig", + .function = { + .dmark = JSON_ParserConfig_mark, + .dfree = RUBY_DEFAULT_FREE, + .dsize = JSON_ParserConfig_memsize, + .dcompact = JSON_ParserConfig_compact, }, - 0, 0, - RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, + .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE, }; static VALUE cJSON_parser_s_allocate(VALUE klass) @@ -1423,6 +2243,602 @@ static VALUE cJSON_parser_s_allocate(VALUE klass) return TypedData_Make_Struct(klass, JSON_ParserConfig, &JSON_ParserConfig_type, config); } +static void json_str_clear(VALUE str) +{ + if (RB_OBJ_FROZEN_RAW(str)) { + return; + } + rb_str_replace(str, JSON_empty_string); +} + +typedef struct JSON_ResumableParserStruct { + JSON_ParserConfig config; + JSON_ParserState state; + rvalue_stack value_stack; + json_frame_stack frames; + VALUE buffer; + size_t parsed_bytes; + size_t incomplete_bytes; + bool complete; + bool in_use; +} JSON_ResumableParser; + +static void JSON_ResumableParser_mark(void *ptr) +{ + JSON_ResumableParser *parser = (JSON_ResumableParser *)ptr; + JSON_ParserConfig_mark(&parser->config); + rvalue_stack_mark(&parser->value_stack); + rvalue_cache_mark(&parser->state.name_cache); + rb_gc_mark(parser->buffer); // pin the buffer + rb_gc_mark_movable(parser->state.parser); +} + +static void JSON_ResumableParser_free(void *ptr) +{ + JSON_ResumableParser *parser = (JSON_ResumableParser *)ptr; + rvalue_stack_free_buffer(&parser->value_stack); + json_frame_stack_free_buffer(&parser->frames); +} + +static size_t JSON_ResumableParser_memsize(const void *ptr) +{ + const JSON_ResumableParser *parser = (const JSON_ResumableParser *)ptr; + size_t memsize = JSON_ParserConfig_memsize(&parser->config); + memsize += rvalue_stack_memsize(&parser->value_stack); + memsize += json_frame_stack_memsize(&parser->frames); +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE + memsize += ( + sizeof(JSON_ResumableParser) + - sizeof(JSON_ParserState) + - sizeof(JSON_ParserConfig) + - sizeof(rvalue_stack) + - sizeof(json_frame_stack) + ); +#endif + return memsize; +} + +static void JSON_ResumableParser_compact(void *ptr) +{ + JSON_ResumableParser *parser = (JSON_ResumableParser *)ptr; + JSON_ParserConfig_compact(&parser->config); + rvalue_stack_compact(&parser->value_stack); + rvalue_cache_compact(&parser->state.name_cache); + parser->buffer = rb_gc_location(parser->buffer); + parser->state.parser = rb_gc_location(parser->state.parser); +} + +static const rb_data_type_t JSON_ResumableParser_type = { + .wrap_struct_name = "JSON::Ext::ResumableParser", + .function = { + JSON_ResumableParser_mark, + JSON_ResumableParser_free, + JSON_ResumableParser_memsize, + JSON_ResumableParser_compact, + }, + // RUBY_TYPED_WB_PROTECTED is deliberately not declared because + // this is a superset of JSON_Parser_rvalue_stack_type, so we'd need + // to trigger a lot of write barriers. + .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_EMBEDDABLE, +}; + +static VALUE cResumableParser_allocate(VALUE klass) +{ + JSON_ResumableParser *parser; + VALUE obj = TypedData_Make_Struct(klass, JSON_ResumableParser, &JSON_ResumableParser_type, parser); + parser->state.in_array++; + parser->state.parser = obj; + return obj; +} + +static inline JSON_ResumableParser *cResumableParser_get(VALUE self) +{ + JSON_ResumableParser *parser; + TypedData_Get_Struct(self, JSON_ResumableParser, &JSON_ResumableParser_type, parser); + return parser; +} + +/* + * call-seq: new(opts => {}) + * + * Creates a new JSON::ResumableParser instance. + * + * Argument +opts+, if given, contains a \Hash of options for the parsing. + * See {Parsing Options}[#module-JSON-label-Parsing+Options]. + * + * A ResumableParser is able to parse partial documents and resume parsing later + * when more of the document is provided: + * + * parser = JSON::ResumableParser.new + * parser << '{"user": "george", "role": "ad' + * parser.parse # => false + * parser.eos? # => true + * parser.partial_value # => { "user" => "george", "role" => nil } + * parser.rest # => '"ad' + * + * parser << 'min" }[1, 2, 3]' + * parser.parse # => true + * parser.value # => { "user" => "george", "role" => "admin" } + * + * parser.parse # => true + * parser.value # => [1, 2, 3] + * + * === Limitations + * + * While ResumableParser is able to parse streams of documents without any + * explicit separators between them, it is highly recommended to separate documents + * by either spaces or newlines, as otherwise the \JSON syntax for numbers may be ambiguous. + * When parsing a number, ResumableParser will not consider the number complete until something follows: + * + * parser << '123' + * parser.parse # => false + * parser << ' ' + * parser.parse # => true + * parser.value # => 123 + * + * === Security + * + * An incomplete document is buffered in full and there is no size limit, so when reading + * from an untrusted source the caller is responsible for bounding how much data is fed. + * For example: + * + * loop do + * if parser.parsed_bytes > DOCUMENT_MAX_SIZE + * raise "document too large" + * end + * + * parser << read_chunk + * while parser.parse + * process(parser.value) + * end + * end + */ +static VALUE cResumableParser_initialize(int argc, VALUE *argv, VALUE self) +{ + rb_check_frozen(self); + + VALUE opts = Qfalse; + rb_scan_args_kw(RB_SCAN_ARGS_LAST_HASH_KEYWORDS, argc, argv, "0:", &opts); + JSON_ResumableParser *parser = cResumableParser_get(self); + + opts = argc > 0 ? argv[0] : Qnil; + parser_config_init(&parser->config, opts, self, true); + + return self; +} + +static JSON_ResumableParser *ResumableParser_acquire(VALUE self, bool lock); + +/* + * call-seq: self << string -> self + * + * Appends the given string to the parser's buffer. + */ +static VALUE cResumableParser_feed(VALUE self, VALUE str) +{ + rb_check_frozen(self); + + JSON_ResumableParser *parser = ResumableParser_acquire(self, false); + + str = convert_encoding(str); + if (!RSTRING_LEN(str)) { + return self; + } + + size_t offset = parser->state.cursor - parser->state.start; + const size_t remaining = parser->state.end - parser->state.cursor; + + if (!remaining) { + if (parser->buffer) { + json_str_clear(parser->buffer); + } + parser->buffer = RB_OBJ_FROZEN_RAW(str) ? str : rb_obj_hide(rb_str_new_shared(str)); + offset = 0; + } else { + JSON_ASSERT(parser->buffer); + + const size_t size = parser->state.end - parser->state.start; + const size_t consumed = size - remaining; + + if (RB_OBJ_FROZEN_RAW(parser->buffer)) { + VALUE new_buffer = rb_obj_hide(rb_str_buf_new(remaining + RSTRING_LEN(str))); + rb_enc_associate_index(new_buffer, utf8_encindex); + + char *old_ptr = RSTRING_PTR(parser->buffer); + memcpy(RSTRING_PTR(new_buffer), old_ptr + consumed, remaining); + rb_str_set_len(new_buffer, remaining); + offset = 0; + parser->buffer = new_buffer; + } else if (consumed > (size / 2) && size >= 512) { + rb_str_modify(parser->buffer); + char *old_ptr = RSTRING_PTR(parser->buffer); + memmove(old_ptr, old_ptr + consumed, remaining); + rb_str_set_len(parser->buffer, remaining); + offset = 0; + } + rb_str_append(parser->buffer, str); + } + + rb_len_t len; + const char *start; + RSTRING_GETMEM(parser->buffer, start, len); + parser->state.start = start; + parser->state.end = start + len; + parser->state.cursor = parser->state.start + offset; + + return self; +} + +struct json_parse_any_args { + JSON_ParserState *state; + JSON_ParserConfig *config; + VALUE parser; +}; + +static VALUE json_parse_any_resumable_safe0(RB_BLOCK_CALL_FUNC_ARGLIST(yielded_arg, _args)) +{ + struct json_parse_any_args *args = (struct json_parse_any_args *)_args; + return (VALUE)json_parse_any(args->state, args->config, true); +} + +static VALUE json_parse_any_resumable_safe(VALUE _args) +{ + struct json_parse_any_args *args = (struct json_parse_any_args *)_args; + VALUE result = rb_catch_obj(args->parser, json_parse_any_resumable_safe0, _args); + return result == args->parser ? Qfalse : result; +} + +static JSON_ResumableParser *ResumableParser_acquire(VALUE self, bool lock) +{ + JSON_ResumableParser *parser = cResumableParser_get(self); + + if (parser->in_use) { + rb_raise(rb_eArgError, "ResumableParser can't be used recursively"); + } + + if (lock) { + parser->in_use = true; + } + + // self may have moved, so we need to update all pointers + // Investigate: We might be better off keeping JSON_ParserState on the stack + // and only persist what we need. + parser->state.value_stack = &parser->value_stack; + parser->state.frames = &parser->frames; + + return parser; +} + +/* + * call-seq: parse -> true or false + * + * Attemps to parse a JSON document from the internal buffer. + * Returns whether a complete document could be parsed. + * + * It does raise +JSON::ParserError+ when encountering invalid \JSON syntax. + * + * The parsed object can be retrieved by calling #value + */ +static VALUE cResumableParser_parse(VALUE self) +{ + JSON_ResumableParser *parser = ResumableParser_acquire(self, true); + + if (parser->complete) { + parser->parsed_bytes = 0; + parser->incomplete_bytes = 0; + parser->complete = false; + } + + if (!parser->buffer) { + parser->in_use = false; + return Qfalse; + } + + if (parser->frames.head == 0) { + json_frame_stack_push(&parser->state, (json_frame){ + .type = JSON_FRAME_ROOT, + .phase = JSON_PHASE_VALUE, + }); + } + + VALUE Vsource = parser->buffer; // Prevent compaction + + json_frame *frame = json_frame_stack_peek(&parser->frames); + + if (frame->phase == JSON_PHASE_DONE) { + JSON_ASSERT(parser->value_stack.head == 1); + JSON_ASSERT(parser->frames.head == 1); + + frame->phase = JSON_PHASE_VALUE; + rvalue_stack_pop(parser->state.value_stack, 1); + } + + struct json_parse_any_args args = { + .state = &parser->state, + .config = &parser->config, + .parser = self, + }; + int status; + const char *initial_cursor = parser->state.cursor; + parser->complete = rb_protect(json_parse_any_resumable_safe, (VALUE)&args, &status); + + if (status) { + parser->complete = true; // a parse error is considered complete + } + + parser->parsed_bytes += parser->state.cursor - initial_cursor; + parser->incomplete_bytes = parser->complete ? 0 : parser->state.end - parser->state.cursor; + + json_eat_whitespace(&parser->state, &parser->config, false); + if (eos(&parser->state)) { + json_str_clear(parser->buffer); + parser->buffer = Qfalse; + parser->state.start = parser->state.cursor = parser->state.end = 0; + } + parser->in_use = false; + + if (status) { + rb_jump_tag(status); // reraise + } + RB_GC_GUARD(Vsource); + return parser->complete ? Qtrue : Qfalse; +} + +/* + * call-seq: value? -> true or false + * + * Returns whether a parsed value is available. + */ +static VALUE cResumableParser_value_p(VALUE self) +{ + JSON_ResumableParser *parser = ResumableParser_acquire(self, false); + + if (parser->value_stack.head > 0) { + json_frame *frame = json_frame_stack_peek(&parser->frames); + if (frame->phase == JSON_PHASE_DONE) { + return Qtrue; + } + } + return Qfalse; +} + +/* + * call-seq: value -> object + * + * Returns and consume the last parsed value. + * Raises ArgumentError if there is no parsed value or if it was already retrieved: + * parser << '[1][2]' + * parser.value # ArgumentError no ready value + * parser.parse # => true + * parser.value # => [1] + * parser.value # ArgumentError no ready value + */ +static VALUE cResumableParser_value(VALUE self) +{ + JSON_ResumableParser *parser = ResumableParser_acquire(self, false); + + if (parser->frames.head > 0) { + json_frame *frame = json_frame_stack_peek(&parser->frames); + + if (frame->phase == JSON_PHASE_DONE) { + VALUE result = *rvalue_stack_peek(parser->state.value_stack, 1); + rvalue_stack_pop(parser->state.value_stack, 1); + json_frame_stack_pop(parser->state.frames); + return result; + } + } + rb_raise(rb_eArgError, "no ready value"); +} + +/* + * call-seq: clear -> self + * + * Entirely reset the parser state and buffer. + */ +static VALUE cResumableParser_clear(VALUE self) +{ + JSON_ResumableParser *parser = ResumableParser_acquire(self, false); + parser->buffer = 0; + parser->complete = true; + parser->parsed_bytes = 0; + parser->incomplete_bytes = 0; + parser->frames.head = 0; + parser->value_stack.head = 0; + parser->state.name_cache.length = 0; + parser->state.current_nesting = 0; + parser->state.in_array = 1; + parser->state.start = parser->state.cursor = parser->state.end = NULL; + return self; +} + +static VALUE cResumableParser_partial_value_body(VALUE self) +{ + JSON_ResumableParser *original_parser = cResumableParser_get(self); + JSON_ResumableParser parser = *original_parser; + + parser.state.frames = &parser.frames; + parser.state.value_stack = &parser.value_stack; + + if (parser.value_stack.head == 0) { + return Qnil; + } + + json_frame *frame = json_frame_stack_peek(parser.state.frames); + long missing_object_value = 0; + if (frame->type == JSON_FRAME_OBJECT && (frame->phase == JSON_PHASE_VALUE || frame->phase == JSON_PHASE_OBJECT_COLON)) { + missing_object_value = 1; + } + + // Copy the value stack as we need to mutate it. The collapse loop folds each + // open container by popping its entries and pushing the single result, so a + // parent always reclaims its child's slot; head exceeds its live size by at + // most one, either for the missing-value placeholder pushed below or for the + // result of folding an empty innermost container. That one spare slot keeps + // rvalue_stack_push from growing (reallocating) this ALLOCV buffer. + long capa = parser.value_stack.head; + parser.value_stack.capa = capa + 1; + VALUE tmpbuf, *value_stack_buffer = ALLOCV_N(VALUE, tmpbuf, parser.value_stack.capa); + MEMCPY(value_stack_buffer, parser.value_stack.ptr, VALUE, capa); + parser.value_stack.ptr = value_stack_buffer; + + JSON_ParserState *state = &parser.state; + JSON_ParserConfig *config = &parser.config; + + if (missing_object_value) { + rvalue_stack_push(state->value_stack, Qnil, NULL, &state->value_stack); + } + + VALUE partial_result = Qundef; + + while (UNDEF_P(partial_result)) { + frame = json_frame_stack_peek(state->frames); + + switch (frame->type) { + case JSON_FRAME_ROOT: { + partial_result = *rvalue_stack_peek(state->value_stack, 1); + break; + } + + case JSON_FRAME_ARRAY: { + long count = json_frame_entry_count(frame, state->value_stack); + json_push_value(state, config, json_decode_array(state, config, count)); + json_frame_stack_pop(state->frames); + + break; + } + + case JSON_FRAME_OBJECT: { + long count = json_frame_entry_count(frame, state->value_stack); + json_push_value(state, config, json_decode_object(state, config, count)); + json_frame_stack_pop(state->frames); + break; + } + + default: { + JSON_UNREACHABLE_RETURN(Qundef); + break; + } + } + } + + ALLOCV_END(tmpbuf); + return partial_result; +} + +/* + * call-seq: partial_value -> object + * + * Returns the Ruby objects parsed up to this point: + * parser << '[1, [2, 3,' + * parser.parse # => false + * parser.value # ArgumentError no ready value + * parser.partial_value # => [1, [2, 3]] + */ +static VALUE cResumableParser_partial_value(VALUE self) +{ + JSON_ResumableParser *parser = ResumableParser_acquire(self, true); + + int status; + VALUE result = rb_protect(cResumableParser_partial_value_body, self, &status); + parser->in_use = false; + if (status) { + rb_jump_tag(status); + } + return result; +} + +/* + * call-seq: rest -> string + * + * Returns a string containing what remains to be parsed in the buffer + * parser << '{ "message": "unterminated message' + * parser.parse # => false + * parser.rest # => '"unterminated message"' + */ +static VALUE cResumableParser_rest(VALUE self) +{ + JSON_ResumableParser *parser = cResumableParser_get(self); + + if (!parser->buffer) { + return rb_utf8_str_new("", 0); + } + + size_t offset = parser->state.cursor - parser->state.start; + const char *ptr; + rb_len_t len; + RSTRING_GETMEM(parser->buffer, ptr, len); + return rb_utf8_str_new(ptr + offset, len - offset); +} + +/* + * call-seq: eos? -> true or false + * + * Returns whether the internal buffer has been entirely consumed. + */ +static VALUE cResumableParser_eos_p(VALUE self) +{ + JSON_ResumableParser *parser = cResumableParser_get(self); + return eos(&parser->state) ? Qtrue : Qfalse; +} + +/* + * call-seq: partial_value? -> true or false + * + * Returns whether a document is currently under construction: an unclosed + * container, a key awaiting its value, etc. + * + * It answers the same question as !partial_value.nil?, but as a + * cheap predicate on the parser's internal state, without materializing the + * partially parsed Ruby objects: + * parser << '{"a":1,' + * parser.parse # => false + * parser.partial_value? # => true + * + * A fully parsed document whose value hasn't been retrieved yet is not under + * construction: #value? returns true and #partial_value? returns false. + */ +static VALUE cResumableParser_partial_value_p(VALUE self) +{ + JSON_ResumableParser *parser = cResumableParser_get(self); + + // Mirror of #value?: values on the stack while the document isn't DONE + // belong to a partially built document. A container whose first key or + // element hasn't been parsed yet has no frame nor value registered (the + // tokenizer rewinds to the container start on EOS), so that state is + // observable through the buffer (#eos?/#rest) instead, keeping this + // predicate consistent with #partial_value returning nil. + if (parser->value_stack.head > 0) { + json_frame *frame = json_frame_stack_peek(&parser->frames); + if (frame->phase != JSON_PHASE_DONE) { + return Qtrue; + } + } + return Qfalse; +} + +/* + * call-seq: parsed_bytes -> integer + * + * Returns the number of bytes parsed since the start of the current partial value. + * This is intended to be used for securing against untrusted input: + * + * loop do + * if parser.parsed_bytes > DOCUMENT_MAX_SIZE + * raise "document too large" + * end + * + * parser << read_chunk + * while parser.parse + * process(parser.value) + * end + * end + */ +static VALUE cResumableParser_parsed_bytes(VALUE self) +{ + JSON_ResumableParser *parser = cResumableParser_get(self); + return ULL2NUM(parser->parsed_bytes + parser->incomplete_bytes); +} + void Init_parser(void) { #ifdef HAVE_RB_EXT_RACTOR_SAFE @@ -1434,54 +2850,76 @@ void Init_parser(void) mJSON = rb_define_module("JSON"); VALUE mExt = rb_define_module_under(mJSON, "Ext"); VALUE cParserConfig = rb_define_class_under(mExt, "ParserConfig", rb_cObject); + + rb_global_variable(&eParserError); + eParserError = rb_path2class("JSON::ParserError"); + + rb_global_variable(&eNestingError); eNestingError = rb_path2class("JSON::NestingError"); - rb_gc_register_mark_object(eNestingError); + rb_define_alloc_func(cParserConfig, cJSON_parser_s_allocate); - rb_define_method(cParserConfig, "initialize", cParserConfig_initialize, 1); + rb_define_private_method(cParserConfig, "initialize", cParserConfig_initialize, 1); rb_define_method(cParserConfig, "parse", cParserConfig_parse, 1); VALUE cParser = rb_define_class_under(mExt, "Parser", rb_cObject); rb_define_singleton_method(cParser, "parse", cParser_m_parse, 2); + VALUE cResumableParser = rb_define_class_under(mJSON, "ResumableParser", rb_cObject); + rb_define_alloc_func(cResumableParser, cResumableParser_allocate); + rb_define_private_method(cResumableParser, "initialize", cResumableParser_initialize, -1); + rb_define_method(cResumableParser, "<<", cResumableParser_feed, 1); + rb_define_method(cResumableParser, "parse", cResumableParser_parse, 0); + rb_define_method(cResumableParser, "value", cResumableParser_value, 0); + rb_define_method(cResumableParser, "value?", cResumableParser_value_p, 0); + rb_define_method(cResumableParser, "partial_value", cResumableParser_partial_value, 0); + rb_define_method(cResumableParser, "partial_value?", cResumableParser_partial_value_p, 0); + rb_define_method(cResumableParser, "clear", cResumableParser_clear, 0); + rb_define_method(cResumableParser, "rest", cResumableParser_rest, 0); + rb_define_method(cResumableParser, "eos?", cResumableParser_eos_p, 0); + rb_define_method(cResumableParser, "parsed_bytes", cResumableParser_parsed_bytes, 0); + + rb_global_variable(&CNaN); CNaN = rb_const_get(mJSON, rb_intern("NaN")); - rb_gc_register_mark_object(CNaN); + rb_global_variable(&CInfinity); CInfinity = rb_const_get(mJSON, rb_intern("Infinity")); - rb_gc_register_mark_object(CInfinity); + rb_global_variable(&CMinusInfinity); CMinusInfinity = rb_const_get(mJSON, rb_intern("MinusInfinity")); - rb_gc_register_mark_object(CMinusInfinity); rb_global_variable(&Encoding_UTF_8); Encoding_UTF_8 = rb_const_get(rb_path2class("Encoding"), rb_intern("UTF_8")); + rb_global_variable(&JSON_empty_string); + JSON_empty_string = rb_obj_hide(rb_utf8_str_new("", 0)); + sym_max_nesting = ID2SYM(rb_intern("max_nesting")); sym_allow_nan = ID2SYM(rb_intern("allow_nan")); sym_allow_trailing_comma = ID2SYM(rb_intern("allow_trailing_comma")); + sym_allow_comments = ID2SYM(rb_intern("allow_comments")); + sym_allow_control_characters = ID2SYM(rb_intern("allow_control_characters")); + sym_allow_invalid_escape = ID2SYM(rb_intern("allow_invalid_escape")); sym_symbolize_names = ID2SYM(rb_intern("symbolize_names")); sym_freeze = ID2SYM(rb_intern("freeze")); - sym_create_additions = ID2SYM(rb_intern("create_additions")); - sym_create_id = ID2SYM(rb_intern("create_id")); - sym_object_class = ID2SYM(rb_intern("object_class")); - sym_array_class = ID2SYM(rb_intern("array_class")); + sym_on_load = ID2SYM(rb_intern("on_load")); sym_decimal_class = ID2SYM(rb_intern("decimal_class")); - sym_match_string = ID2SYM(rb_intern("match_string")); - - i_create_id = rb_intern("create_id"); - i_json_creatable_p = rb_intern("json_creatable?"); - i_json_create = rb_intern("json_create"); - i_chr = rb_intern("chr"); - i_match = rb_intern("match"); - i_deep_const_get = rb_intern("deep_const_get"); - i_aset = rb_intern("[]="); - i_aref = rb_intern("[]"); - i_leftshift = rb_intern("<<"); + sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key")); + i_new = rb_intern("new"); i_try_convert = rb_intern("try_convert"); +#ifndef HAVE_RB_STR_TO_INTERNED_STR i_uminus = rb_intern("-@"); +#endif i_encode = rb_intern("encode"); + i_at_line = rb_intern("@line"); + i_at_column = rb_intern("@column"); + i_at_json_path = rb_intern("@json_path"); binary_encindex = rb_ascii8bit_encindex(); utf8_encindex = rb_utf8_encindex(); enc_utf8 = rb_utf8_encoding(); + +#ifdef HAVE_SIMD + simd_impl = find_simd_implementation(); +#endif } diff --git a/ext/json/ext/simd/conf.rb b/ext/json/ext/simd/conf.rb new file mode 100644 index 000000000..9b87473d5 --- /dev/null +++ b/ext/json/ext/simd/conf.rb @@ -0,0 +1,21 @@ +case RbConfig::CONFIG['host_cpu'] +when /^(arm|aarch64)/ + # Try to compile a small program using NEON instructions + header, type, init = 'arm_neon.h', 'uint8x16_t', 'vdupq_n_u8(32)' +when /^(x86_64|x64)/ + header, type, init = 'x86intrin.h', '__m128i', '_mm_set1_epi8(32)' +end +if header + if have_header(header) && try_compile(<<~SRC, '-Werror=implicit-function-declaration') + #{cpp_include(header)} + int main(int argc, char **argv) { + #{type} test = #{init}; + if (argc > 100000) printf("%p", &test); + return 0; + } + SRC + $defs.push("-DJSON_ENABLE_SIMD") + else + puts "Disable SIMD" + end +end diff --git a/ext/json/ext/simd/simd.h b/ext/json/ext/simd/simd.h new file mode 100644 index 000000000..2f3f838a9 --- /dev/null +++ b/ext/json/ext/simd/simd.h @@ -0,0 +1,200 @@ +#include "../json.h" + +typedef enum { + SIMD_NONE, + SIMD_NEON, + SIMD_SSE2 +} SIMD_Implementation; + +#ifndef __has_builtin // Optional of course. + #define __has_builtin(x) 0 // Compatibility with non-clang compilers. +#endif + +#ifdef __clang__ +# if __has_builtin(__builtin_ctzll) +# define HAVE_BUILTIN_CTZLL 1 +# else +# define HAVE_BUILTIN_CTZLL 0 +# endif +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) +# define HAVE_BUILTIN_CTZLL 1 +#else +# define HAVE_BUILTIN_CTZLL 0 +#endif + +static inline uint32_t trailing_zeros64(uint64_t input) +{ + JSON_ASSERT(input > 0); // __builtin_ctz(0) is undefined behavior + +#if HAVE_BUILTIN_CTZLL + return __builtin_ctzll(input); +#else + uint32_t trailing_zeros = 0; + uint64_t temp = input; + while ((temp & 1) == 0 && temp > 0) { + trailing_zeros++; + temp >>= 1; + } + return trailing_zeros; +#endif +} + +static inline int trailing_zeros(int input) +{ + JSON_ASSERT(input > 0); // __builtin_ctz(0) is undefined behavior + +#if HAVE_BUILTIN_CTZLL + return __builtin_ctz(input); +#else + int trailing_zeros = 0; + int temp = input; + while ((temp & 1) == 0 && temp > 0) { + trailing_zeros++; + temp >>= 1; + } + return trailing_zeros; +#endif +} + +#ifdef JSON_ENABLE_SIMD + +#define SIMD_MINIMUM_THRESHOLD 4 + +ALWAYS_INLINE(static) void json_fast_memcpy16(char *dst, const char *src, size_t len) +{ + RBIMPL_ASSERT_OR_ASSUME(len < 16); + RBIMPL_ASSERT_OR_ASSUME(len >= SIMD_MINIMUM_THRESHOLD); // 4 +#if defined(__has_builtin) && __has_builtin(__builtin_memcpy) + // If __builtin_memcpy is available, use it to copy between SIMD_MINIMUM_THRESHOLD (4) and vec_len-1 (15) bytes. + // These copies overlap. The first copy will copy the first 8 (or 4) bytes. The second copy will copy + // the last 8 (or 4) bytes but overlap with the first copy. The overlapping bytes will be in the correct + // position in both copies. + + // Please do not attempt to replace __builtin_memcpy with memcpy without profiling and/or looking at the + // generated assembly. On clang-specifically (tested on Apple clang version 17.0.0 (clang-1700.0.13.3)), + // when using memcpy, the compiler will notice the only difference is a 4 or 8 and generate a conditional + // select instruction instead of direct loads and stores with a branch. This ends up slower than the branch + // plus two loads and stores generated when using __builtin_memcpy. + if (len >= 8) { + __builtin_memcpy(dst, src, 8); + __builtin_memcpy(dst + len - 8, src + len - 8, 8); + } else { + __builtin_memcpy(dst, src, 4); + __builtin_memcpy(dst + len - 4, src + len - 4, 4); + } +#else + MEMCPY(dst, src, char, len); +#endif +} + +#if defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(__aarch64__) || defined(_M_ARM64) +#include + +#define FIND_SIMD_IMPLEMENTATION_DEFINED 1 +static inline SIMD_Implementation find_simd_implementation(void) +{ + return SIMD_NEON; +} + +#define HAVE_SIMD 1 +#define HAVE_SIMD_NEON 1 + +// See: https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon +ALWAYS_INLINE(static) uint64_t neon_match_mask(uint8x16_t matches) +{ + const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(matches), 4); + const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0); + return mask & 0x8888888888888888ull; +} + +ALWAYS_INLINE(static) uint64_t compute_chunk_mask_neon(const char *ptr) +{ + uint8x16_t chunk = vld1q_u8((const unsigned char *)ptr); + + // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33 + // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/ + const uint8x16_t too_low_or_dbl_quote = vcltq_u8(veorq_u8(chunk, vdupq_n_u8(2)), vdupq_n_u8(33)); + + uint8x16_t has_backslash = vceqq_u8(chunk, vdupq_n_u8('\\')); + uint8x16_t needs_escape = vorrq_u8(too_low_or_dbl_quote, has_backslash); + return neon_match_mask(needs_escape); +} + +ALWAYS_INLINE(static) int string_scan_simd_neon(const char **ptr, const char *end, uint64_t *mask) +{ + while (*ptr + sizeof(uint8x16_t) <= end) { + uint64_t chunk_mask = compute_chunk_mask_neon(*ptr); + if (chunk_mask) { + *mask = chunk_mask; + return 1; + } + *ptr += sizeof(uint8x16_t); + } + return 0; +} + +#endif /* ARM Neon Support.*/ + +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) + +#ifdef HAVE_X86INTRIN_H +#include + +#define HAVE_SIMD 1 +#define HAVE_SIMD_SSE2 1 + +#define FIND_SIMD_IMPLEMENTATION_DEFINED 1 + +#if defined(__clang__) || defined(__GNUC__) +#define TARGET_SSE2 __attribute__((target("sse2"))) +#else +#define TARGET_SSE2 +#endif + +#define _mm_cmpge_epu8(a, b) _mm_cmpeq_epi8(_mm_max_epu8(a, b), a) +#define _mm_cmple_epu8(a, b) _mm_cmpge_epu8(b, a) +#define _mm_cmpgt_epu8(a, b) _mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1)) +#define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a) + +ALWAYS_INLINE(static) TARGET_SSE2 int compute_chunk_mask_sse2(const char *ptr) +{ + __m128i chunk = _mm_loadu_si128((__m128i const*)ptr); + // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33 + // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/ + __m128i too_low_or_dbl_quote = _mm_cmplt_epu8(_mm_xor_si128(chunk, _mm_set1_epi8(2)), _mm_set1_epi8(33)); + __m128i has_backslash = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\\')); + __m128i needs_escape = _mm_or_si128(too_low_or_dbl_quote, has_backslash); + return _mm_movemask_epi8(needs_escape); +} + +ALWAYS_INLINE(static) TARGET_SSE2 int string_scan_simd_sse2(const char **ptr, const char *end, int *mask) +{ + while (*ptr + sizeof(__m128i) <= end) { + int chunk_mask = compute_chunk_mask_sse2(*ptr); + if (chunk_mask) { + *mask = chunk_mask; + return 1; + } + *ptr += sizeof(__m128i); + } + + return 0; +} + +static inline SIMD_Implementation find_simd_implementation(void) +{ + // SSE2 is part of the x86-64 baseline. + return SIMD_SSE2; +} + +#endif /* HAVE_X86INTRIN_H */ +#endif /* X86_64 Support */ + +#endif /* JSON_ENABLE_SIMD */ + +#ifndef FIND_SIMD_IMPLEMENTATION_DEFINED +static inline SIMD_Implementation find_simd_implementation(void) +{ + return SIMD_NONE; +} +#endif diff --git a/ext/json/ext/vendor/fast_float_parser.h b/ext/json/ext/vendor/fast_float_parser.h new file mode 100644 index 000000000..6e43b254c --- /dev/null +++ b/ext/json/ext/vendor/fast_float_parser.h @@ -0,0 +1,814 @@ +/* Eisel-Lemire decimal->double, ported from fast_float: + * include/fast_float/decimal_to_binary.h, the compute_float> + * + compute_product_approximation routines. + * + * Algorithm authors: Michael Eisel (original approach) and Daniel Lemire + * (formalization, proof, and the fast_float implementation) — hence "Eisel-Lemire". + * + * Copyright (c) 2021 The fast_float authors. Tri-licensed Apache-2.0 / MIT / BSL-1.0; + * used here under MIT — see LICENSE-fast_float-MIT in this directory. + * + * C99 Port by Tilo Sloboda. + */ + +#ifndef FFP_H +#define FFP_H +#include +#include +#include + +#define FFP_SMALLEST_POWER_OF_FIVE (-342) +#define FFP_LARGEST_POWER_OF_FIVE (308) +static const uint64_t ffp_power_of_five_128[2 * (308 - (-342) + 1)] = { + 0xeef453d6923bd65a, 0x113faa2906a13b3f, + 0x9558b4661b6565f8, 0x4ac7ca59a424c507, + 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, + 0xe95a99df8ace6f53, 0xf4d82c2c107973dc, + 0x91d8a02bb6c10594, 0x79071b9b8a4be869, + 0xb64ec836a47146f9, 0x9748e2826cdee284, + 0xe3e27a444d8d98b7, 0xfd1b1b2308169b25, + 0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7, + 0xb208ef855c969f4f, 0xbdbd2d335e51a935, + 0xde8b2b66b3bc4723, 0xad2c788035e61382, + 0x8b16fb203055ac76, 0x4c3bcb5021afcc31, + 0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d, + 0xd953e8624b85dd78, 0xd71d6dad34a2af0d, + 0x87d4713d6f33aa6b, 0x8672648c40e5ad68, + 0xa9c98d8ccb009506, 0x680efdaf511f18c2, + 0xd43bf0effdc0ba48, 0x212bd1b2566def2, + 0x84a57695fe98746d, 0x14bb630f7604b57, + 0xa5ced43b7e3e9188, 0x419ea3bd35385e2d, + 0xcf42894a5dce35ea, 0x52064cac828675b9, + 0x818995ce7aa0e1b2, 0x7343efebd1940993, + 0xa1ebfb4219491a1f, 0x1014ebe6c5f90bf8, + 0xca66fa129f9b60a6, 0xd41a26e077774ef6, + 0xfd00b897478238d0, 0x8920b098955522b4, + 0x9e20735e8cb16382, 0x55b46e5f5d5535b0, + 0xc5a890362fddbc62, 0xeb2189f734aa831d, + 0xf712b443bbd52b7b, 0xa5e9ec7501d523e4, + 0x9a6bb0aa55653b2d, 0x47b233c92125366e, + 0xc1069cd4eabe89f8, 0x999ec0bb696e840a, + 0xf148440a256e2c76, 0xc00670ea43ca250d, + 0x96cd2a865764dbca, 0x380406926a5e5728, + 0xbc807527ed3e12bc, 0xc605083704f5ecf2, + 0xeba09271e88d976b, 0xf7864a44c633682e, + 0x93445b8731587ea3, 0x7ab3ee6afbe0211d, + 0xb8157268fdae9e4c, 0x5960ea05bad82964, + 0xe61acf033d1a45df, 0x6fb92487298e33bd, + 0x8fd0c16206306bab, 0xa5d3b6d479f8e056, + 0xb3c4f1ba87bc8696, 0x8f48a4899877186c, + 0xe0b62e2929aba83c, 0x331acdabfe94de87, + 0x8c71dcd9ba0b4925, 0x9ff0c08b7f1d0b14, + 0xaf8e5410288e1b6f, 0x7ecf0ae5ee44dd9, + 0xdb71e91432b1a24a, 0xc9e82cd9f69d6150, + 0x892731ac9faf056e, 0xbe311c083a225cd2, + 0xab70fe17c79ac6ca, 0x6dbd630a48aaf406, + 0xd64d3d9db981787d, 0x92cbbccdad5b108, + 0x85f0468293f0eb4e, 0x25bbf56008c58ea5, + 0xa76c582338ed2621, 0xaf2af2b80af6f24e, + 0xd1476e2c07286faa, 0x1af5af660db4aee1, + 0x82cca4db847945ca, 0x50d98d9fc890ed4d, + 0xa37fce126597973c, 0xe50ff107bab528a0, + 0xcc5fc196fefd7d0c, 0x1e53ed49a96272c8, + 0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7a, + 0x9faacf3df73609b1, 0x77b191618c54e9ac, + 0xc795830d75038c1d, 0xd59df5b9ef6a2417, + 0xf97ae3d0d2446f25, 0x4b0573286b44ad1d, + 0x9becce62836ac577, 0x4ee367f9430aec32, + 0xc2e801fb244576d5, 0x229c41f793cda73f, + 0xf3a20279ed56d48a, 0x6b43527578c1110f, + 0x9845418c345644d6, 0x830a13896b78aaa9, + 0xbe5691ef416bd60c, 0x23cc986bc656d553, + 0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa8, + 0x94b3a202eb1c3f39, 0x7bf7d71432f3d6a9, + 0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc53, + 0xe858ad248f5c22c9, 0xd1b3400f8f9cff68, + 0x91376c36d99995be, 0x23100809b9c21fa1, + 0xb58547448ffffb2d, 0xabd40a0c2832a78a, + 0xe2e69915b3fff9f9, 0x16c90c8f323f516c, + 0x8dd01fad907ffc3b, 0xae3da7d97f6792e3, + 0xb1442798f49ffb4a, 0x99cd11cfdf41779c, + 0xdd95317f31c7fa1d, 0x40405643d711d583, + 0x8a7d3eef7f1cfc52, 0x482835ea666b2572, + 0xad1c8eab5ee43b66, 0xda3243650005eecf, + 0xd863b256369d4a40, 0x90bed43e40076a82, + 0x873e4f75e2224e68, 0x5a7744a6e804a291, + 0xa90de3535aaae202, 0x711515d0a205cb36, + 0xd3515c2831559a83, 0xd5a5b44ca873e03, + 0x8412d9991ed58091, 0xe858790afe9486c2, + 0xa5178fff668ae0b6, 0x626e974dbe39a872, + 0xce5d73ff402d98e3, 0xfb0a3d212dc8128f, + 0x80fa687f881c7f8e, 0x7ce66634bc9d0b99, + 0xa139029f6a239f72, 0x1c1fffc1ebc44e80, + 0xc987434744ac874e, 0xa327ffb266b56220, + 0xfbe9141915d7a922, 0x4bf1ff9f0062baa8, + 0x9d71ac8fada6c9b5, 0x6f773fc3603db4a9, + 0xc4ce17b399107c22, 0xcb550fb4384d21d3, + 0xf6019da07f549b2b, 0x7e2a53a146606a48, + 0x99c102844f94e0fb, 0x2eda7444cbfc426d, + 0xc0314325637a1939, 0xfa911155fefb5308, + 0xf03d93eebc589f88, 0x793555ab7eba27ca, + 0x96267c7535b763b5, 0x4bc1558b2f3458de, + 0xbbb01b9283253ca2, 0x9eb1aaedfb016f16, + 0xea9c227723ee8bcb, 0x465e15a979c1cadc, + 0x92a1958a7675175f, 0xbfacd89ec191ec9, + 0xb749faed14125d36, 0xcef980ec671f667b, + 0xe51c79a85916f484, 0x82b7e12780e7401a, + 0x8f31cc0937ae58d2, 0xd1b2ecb8b0908810, + 0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa15, + 0xdfbdcece67006ac9, 0x67a791e093e1d49a, + 0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e0, + 0xaecc49914078536d, 0x58fae9f773886e18, + 0xda7f5bf590966848, 0xaf39a475506a899e, + 0x888f99797a5e012d, 0x6d8406c952429603, + 0xaab37fd7d8f58178, 0xc8e5087ba6d33b83, + 0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a64, + 0x855c3be0a17fcd26, 0x5cf2eea09a55067f, + 0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481e, + 0xd0601d8efc57b08b, 0xf13b94daf124da26, + 0x823c12795db6ce57, 0x76c53d08d6b70858, + 0xa2cb1717b52481ed, 0x54768c4b0c64ca6e, + 0xcb7ddcdda26da268, 0xa9942f5dcf7dfd09, + 0xfe5d54150b090b02, 0xd3f93b35435d7c4c, + 0x9efa548d26e5a6e1, 0xc47bc5014a1a6daf, + 0xc6b8e9b0709f109a, 0x359ab6419ca1091b, + 0xf867241c8cc6d4c0, 0xc30163d203c94b62, + 0x9b407691d7fc44f8, 0x79e0de63425dcf1d, + 0xc21094364dfb5636, 0x985915fc12f542e4, + 0xf294b943e17a2bc4, 0x3e6f5b7b17b2939d, + 0x979cf3ca6cec5b5a, 0xa705992ceecf9c42, + 0xbd8430bd08277231, 0x50c6ff782a838353, + 0xece53cec4a314ebd, 0xa4f8bf5635246428, + 0x940f4613ae5ed136, 0x871b7795e136be99, + 0xb913179899f68584, 0x28e2557b59846e3f, + 0xe757dd7ec07426e5, 0x331aeada2fe589cf, + 0x9096ea6f3848984f, 0x3ff0d2c85def7621, + 0xb4bca50b065abe63, 0xfed077a756b53a9, + 0xe1ebce4dc7f16dfb, 0xd3e8495912c62894, + 0x8d3360f09cf6e4bd, 0x64712dd7abbbd95c, + 0xb080392cc4349dec, 0xbd8d794d96aacfb3, + 0xdca04777f541c567, 0xecf0d7a0fc5583a0, + 0x89e42caaf9491b60, 0xf41686c49db57244, + 0xac5d37d5b79b6239, 0x311c2875c522ced5, + 0xd77485cb25823ac7, 0x7d633293366b828b, + 0x86a8d39ef77164bc, 0xae5dff9c02033197, + 0xa8530886b54dbdeb, 0xd9f57f830283fdfc, + 0xd267caa862a12d66, 0xd072df63c324fd7b, + 0x8380dea93da4bc60, 0x4247cb9e59f71e6d, + 0xa46116538d0deb78, 0x52d9be85f074e608, + 0xcd795be870516656, 0x67902e276c921f8b, + 0x806bd9714632dff6, 0xba1cd8a3db53b6, + 0xa086cfcd97bf97f3, 0x80e8a40eccd228a4, + 0xc8a883c0fdaf7df0, 0x6122cd128006b2cd, + 0xfad2a4b13d1b5d6c, 0x796b805720085f81, + 0x9cc3a6eec6311a63, 0xcbe3303674053bb0, + 0xc3f490aa77bd60fc, 0xbedbfc4411068a9c, + 0xf4f1b4d515acb93b, 0xee92fb5515482d44, + 0x991711052d8bf3c5, 0x751bdd152d4d1c4a, + 0xbf5cd54678eef0b6, 0xd262d45a78a0635d, + 0xef340a98172aace4, 0x86fb897116c87c34, + 0x9580869f0e7aac0e, 0xd45d35e6ae3d4da0, + 0xbae0a846d2195712, 0x8974836059cca109, + 0xe998d258869facd7, 0x2bd1a438703fc94b, + 0x91ff83775423cc06, 0x7b6306a34627ddcf, + 0xb67f6455292cbf08, 0x1a3bc84c17b1d542, + 0xe41f3d6a7377eeca, 0x20caba5f1d9e4a93, + 0x8e938662882af53e, 0x547eb47b7282ee9c, + 0xb23867fb2a35b28d, 0xe99e619a4f23aa43, + 0xdec681f9f4c31f31, 0x6405fa00e2ec94d4, + 0x8b3c113c38f9f37e, 0xde83bc408dd3dd04, + 0xae0b158b4738705e, 0x9624ab50b148d445, + 0xd98ddaee19068c76, 0x3badd624dd9b0957, + 0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d6, + 0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4c, + 0xd47487cc8470652b, 0x7647c3200069671f, + 0x84c8d4dfd2c63f3b, 0x29ecd9f40041e073, + 0xa5fb0a17c777cf09, 0xf468107100525890, + 0xcf79cc9db955c2cc, 0x7182148d4066eeb4, + 0x81ac1fe293d599bf, 0xc6f14cd848405530, + 0xa21727db38cb002f, 0xb8ada00e5a506a7c, + 0xca9cf1d206fdc03b, 0xa6d90811f0e4851c, + 0xfd442e4688bd304a, 0x908f4a166d1da663, + 0x9e4a9cec15763e2e, 0x9a598e4e043287fe, + 0xc5dd44271ad3cdba, 0x40eff1e1853f29fd, + 0xf7549530e188c128, 0xd12bee59e68ef47c, + 0x9a94dd3e8cf578b9, 0x82bb74f8301958ce, + 0xc13a148e3032d6e7, 0xe36a52363c1faf01, + 0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac1, + 0x96f5600f15a7b7e5, 0x29ab103a5ef8c0b9, + 0xbcb2b812db11a5de, 0x7415d448f6b6f0e7, + 0xebdf661791d60f56, 0x111b495b3464ad21, + 0x936b9fcebb25c995, 0xcab10dd900beec34, + 0xb84687c269ef3bfb, 0x3d5d514f40eea742, + 0xe65829b3046b0afa, 0xcb4a5a3112a5112, + 0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ab, + 0xb3f4e093db73a093, 0x59ed216765690f56, + 0xe0f218b8d25088b8, 0x306869c13ec3532c, + 0x8c974f7383725573, 0x1e414218c73a13fb, + 0xafbd2350644eeacf, 0xe5d1929ef90898fa, + 0xdbac6c247d62a583, 0xdf45f746b74abf39, + 0x894bc396ce5da772, 0x6b8bba8c328eb783, + 0xab9eb47c81f5114f, 0x66ea92f3f326564, + 0xd686619ba27255a2, 0xc80a537b0efefebd, + 0x8613fd0145877585, 0xbd06742ce95f5f36, + 0xa798fc4196e952e7, 0x2c48113823b73704, + 0xd17f3b51fca3a7a0, 0xf75a15862ca504c5, + 0x82ef85133de648c4, 0x9a984d73dbe722fb, + 0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebba, + 0xcc963fee10b7d1b3, 0x318df905079926a8, + 0xffbbcfe994e5c61f, 0xfdf17746497f7052, + 0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa633, + 0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc0, + 0xf9bd690a1b68637b, 0x3dfdce7aa3c673b0, + 0x9c1661a651213e2d, 0x6bea10ca65c084e, + 0xc31bfa0fe5698db8, 0x486e494fcff30a62, + 0xf3e2f893dec3f126, 0x5a89dba3c3efccfa, + 0x986ddb5c6b3a76b7, 0xf89629465a75e01c, + 0xbe89523386091465, 0xf6bbb397f1135823, + 0xee2ba6c0678b597f, 0x746aa07ded582e2c, + 0x94db483840b717ef, 0xa8c2a44eb4571cdc, + 0xba121a4650e4ddeb, 0x92f34d62616ce413, + 0xe896a0d7e51e1566, 0x77b020baf9c81d17, + 0x915e2486ef32cd60, 0xace1474dc1d122e, + 0xb5b5ada8aaff80b8, 0xd819992132456ba, + 0xe3231912d5bf60e6, 0x10e1fff697ed6c69, + 0x8df5efabc5979c8f, 0xca8d3ffa1ef463c1, + 0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb2, + 0xddd0467c64bce4a0, 0xac7cb3f6d05ddbde, + 0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96b, + 0xad4ab7112eb3929d, 0x86c16c98d2c953c6, + 0xd89d64d57a607744, 0xe871c7bf077ba8b7, + 0x87625f056c7c4a8b, 0x11471cd764ad4972, + 0xa93af6c6c79b5d2d, 0xd598e40d3dd89bcf, + 0xd389b47879823479, 0x4aff1d108d4ec2c3, + 0x843610cb4bf160cb, 0xcedf722a585139ba, + 0xa54394fe1eedb8fe, 0xc2974eb4ee658828, + 0xce947a3da6a9273e, 0x733d226229feea32, + 0x811ccc668829b887, 0x806357d5a3f525f, + 0xa163ff802a3426a8, 0xca07c2dcb0cf26f7, + 0xc9bcff6034c13052, 0xfc89b393dd02f0b5, + 0xfc2c3f3841f17c67, 0xbbac2078d443ace2, + 0x9d9ba7832936edc0, 0xd54b944b84aa4c0d, + 0xc5029163f384a931, 0xa9e795e65d4df11, + 0xf64335bcf065d37d, 0x4d4617b5ff4a16d5, + 0x99ea0196163fa42e, 0x504bced1bf8e4e45, + 0xc06481fb9bcf8d39, 0xe45ec2862f71e1d6, + 0xf07da27a82c37088, 0x5d767327bb4e5a4c, + 0x964e858c91ba2655, 0x3a6a07f8d510f86f, + 0xbbe226efb628afea, 0x890489f70a55368b, + 0xeadab0aba3b2dbe5, 0x2b45ac74ccea842e, + 0x92c8ae6b464fc96f, 0x3b0b8bc90012929d, + 0xb77ada0617e3bbcb, 0x9ce6ebb40173744, + 0xe55990879ddcaabd, 0xcc420a6a101d0515, + 0x8f57fa54c2a9eab6, 0x9fa946824a12232d, + 0xb32df8e9f3546564, 0x47939822dc96abf9, + 0xdff9772470297ebd, 0x59787e2b93bc56f7, + 0x8bfbea76c619ef36, 0x57eb4edb3c55b65a, + 0xaefae51477a06b03, 0xede622920b6b23f1, + 0xdab99e59958885c4, 0xe95fab368e45eced, + 0x88b402f7fd75539b, 0x11dbcb0218ebb414, + 0xaae103b5fcd2a881, 0xd652bdc29f26a119, + 0xd59944a37c0752a2, 0x4be76d3346f0495f, + 0x857fcae62d8493a5, 0x6f70a4400c562ddb, + 0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb952, + 0xd097ad07a71f26b2, 0x7e2000a41346a7a7, + 0x825ecc24c873782f, 0x8ed400668c0c28c8, + 0xa2f67f2dfa90563b, 0x728900802f0f32fa, + 0xcbb41ef979346bca, 0x4f2b40a03ad2ffb9, + 0xfea126b7d78186bc, 0xe2f610c84987bfa8, + 0x9f24b832e6b0f436, 0xdd9ca7d2df4d7c9, + 0xc6ede63fa05d3143, 0x91503d1c79720dbb, + 0xf8a95fcf88747d94, 0x75a44c6397ce912a, + 0x9b69dbe1b548ce7c, 0xc986afbe3ee11aba, + 0xc24452da229b021b, 0xfbe85badce996168, + 0xf2d56790ab41c2a2, 0xfae27299423fb9c3, + 0x97c560ba6b0919a5, 0xdccd879fc967d41a, + 0xbdb6b8e905cb600f, 0x5400e987bbc1c920, + 0xed246723473e3813, 0x290123e9aab23b68, + 0x9436c0760c86e30b, 0xf9a0b6720aaf6521, + 0xb94470938fa89bce, 0xf808e40e8d5b3e69, + 0xe7958cb87392c2c2, 0xb60b1d1230b20e04, + 0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c2, + 0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af3, + 0xe2280b6c20dd5232, 0x25c6da63c38de1b0, + 0x8d590723948a535f, 0x579c487e5a38ad0e, + 0xb0af48ec79ace837, 0x2d835a9df0c6d851, + 0xdcdb1b2798182244, 0xf8e431456cf88e65, + 0x8a08f0f8bf0f156b, 0x1b8e9ecb641b58ff, + 0xac8b2d36eed2dac5, 0xe272467e3d222f3f, + 0xd7adf884aa879177, 0x5b0ed81dcc6abb0f, + 0x86ccbb52ea94baea, 0x98e947129fc2b4e9, + 0xa87fea27a539e9a5, 0x3f2398d747b36224, + 0xd29fe4b18e88640e, 0x8eec7f0d19a03aad, + 0x83a3eeeef9153e89, 0x1953cf68300424ac, + 0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd7, + 0xcdb02555653131b6, 0x3792f412cb06794d, + 0x808e17555f3ebf11, 0xe2bbd88bbee40bd0, + 0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec4, + 0xc8de047564d20a8b, 0xf245825a5a445275, + 0xfb158592be068d2e, 0xeed6e2f0f0d56712, + 0x9ced737bb6c4183d, 0x55464dd69685606b, + 0xc428d05aa4751e4c, 0xaa97e14c3c26b886, + 0xf53304714d9265df, 0xd53dd99f4b3066a8, + 0x993fe2c6d07b7fab, 0xe546a8038efe4029, + 0xbf8fdb78849a5f96, 0xde98520472bdd033, + 0xef73d256a5c0f77c, 0x963e66858f6d4440, + 0x95a8637627989aad, 0xdde7001379a44aa8, + 0xbb127c53b17ec159, 0x5560c018580d5d52, + 0xe9d71b689dde71af, 0xaab8f01e6e10b4a6, + 0x9226712162ab070d, 0xcab3961304ca70e8, + 0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d22, + 0xe45c10c42a2b3b05, 0x8cb89a7db77c506a, + 0x8eb98a7a9a5b04e3, 0x77f3608e92adb242, + 0xb267ed1940f1c61c, 0x55f038b237591ed3, + 0xdf01e85f912e37a3, 0x6b6c46dec52f6688, + 0x8b61313bbabce2c6, 0x2323ac4b3b3da015, + 0xae397d8aa96c1b77, 0xabec975e0a0d081a, + 0xd9c7dced53c72255, 0x96e7bd358c904a21, + 0x881cea14545c7575, 0x7e50d64177da2e54, + 0xaa242499697392d2, 0xdde50bd1d5d0b9e9, + 0xd4ad2dbfc3d07787, 0x955e4ec64b44e864, + 0x84ec3c97da624ab4, 0xbd5af13bef0b113e, + 0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58e, + 0xcfb11ead453994ba, 0x67de18eda5814af2, + 0x81ceb32c4b43fcf4, 0x80eacf948770ced7, + 0xa2425ff75e14fc31, 0xa1258379a94d028d, + 0xcad2f7f5359a3b3e, 0x96ee45813a04330, + 0xfd87b5f28300ca0d, 0x8bca9d6e188853fc, + 0x9e74d1b791e07e48, 0x775ea264cf55347e, + 0xc612062576589dda, 0x95364afe032a819e, + 0xf79687aed3eec551, 0x3a83ddbd83f52205, + 0x9abe14cd44753b52, 0xc4926a9672793543, + 0xc16d9a0095928a27, 0x75b7053c0f178294, + 0xf1c90080baf72cb1, 0x5324c68b12dd6339, + 0x971da05074da7bee, 0xd3f6fc16ebca5e04, + 0xbce5086492111aea, 0x88f4bb1ca6bcf585, + 0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6, + 0x9392ee8e921d5d07, 0x3aff322e62439fd0, + 0xb877aa3236a4b449, 0x9befeb9fad487c3, + 0xe69594bec44de15b, 0x4c2ebe687989a9b4, + 0x901d7cf73ab0acd9, 0xf9d37014bf60a11, + 0xb424dc35095cd80f, 0x538484c19ef38c95, + 0xe12e13424bb40e13, 0x2865a5f206b06fba, + 0x8cbccc096f5088cb, 0xf93f87b7442e45d4, + 0xafebff0bcb24aafe, 0xf78f69a51539d749, + 0xdbe6fecebdedd5be, 0xb573440e5a884d1c, + 0x89705f4136b4a597, 0x31680a88f8953031, + 0xabcc77118461cefc, 0xfdc20d2b36ba7c3e, + 0xd6bf94d5e57a42bc, 0x3d32907604691b4d, + 0x8637bd05af6c69b5, 0xa63f9a49c2c1b110, + 0xa7c5ac471b478423, 0xfcf80dc33721d54, + 0xd1b71758e219652b, 0xd3c36113404ea4a9, + 0x83126e978d4fdf3b, 0x645a1cac083126ea, + 0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4, + 0xcccccccccccccccc, 0xcccccccccccccccd, + 0x8000000000000000, 0x0, + 0xa000000000000000, 0x0, + 0xc800000000000000, 0x0, + 0xfa00000000000000, 0x0, + 0x9c40000000000000, 0x0, + 0xc350000000000000, 0x0, + 0xf424000000000000, 0x0, + 0x9896800000000000, 0x0, + 0xbebc200000000000, 0x0, + 0xee6b280000000000, 0x0, + 0x9502f90000000000, 0x0, + 0xba43b74000000000, 0x0, + 0xe8d4a51000000000, 0x0, + 0x9184e72a00000000, 0x0, + 0xb5e620f480000000, 0x0, + 0xe35fa931a0000000, 0x0, + 0x8e1bc9bf04000000, 0x0, + 0xb1a2bc2ec5000000, 0x0, + 0xde0b6b3a76400000, 0x0, + 0x8ac7230489e80000, 0x0, + 0xad78ebc5ac620000, 0x0, + 0xd8d726b7177a8000, 0x0, + 0x878678326eac9000, 0x0, + 0xa968163f0a57b400, 0x0, + 0xd3c21bcecceda100, 0x0, + 0x84595161401484a0, 0x0, + 0xa56fa5b99019a5c8, 0x0, + 0xcecb8f27f4200f3a, 0x0, + 0x813f3978f8940984, 0x4000000000000000, + 0xa18f07d736b90be5, 0x5000000000000000, + 0xc9f2c9cd04674ede, 0xa400000000000000, + 0xfc6f7c4045812296, 0x4d00000000000000, + 0x9dc5ada82b70b59d, 0xf020000000000000, + 0xc5371912364ce305, 0x6c28000000000000, + 0xf684df56c3e01bc6, 0xc732000000000000, + 0x9a130b963a6c115c, 0x3c7f400000000000, + 0xc097ce7bc90715b3, 0x4b9f100000000000, + 0xf0bdc21abb48db20, 0x1e86d40000000000, + 0x96769950b50d88f4, 0x1314448000000000, + 0xbc143fa4e250eb31, 0x17d955a000000000, + 0xeb194f8e1ae525fd, 0x5dcfab0800000000, + 0x92efd1b8d0cf37be, 0x5aa1cae500000000, + 0xb7abc627050305ad, 0xf14a3d9e40000000, + 0xe596b7b0c643c719, 0x6d9ccd05d0000000, + 0x8f7e32ce7bea5c6f, 0xe4820023a2000000, + 0xb35dbf821ae4f38b, 0xdda2802c8a800000, + 0xe0352f62a19e306e, 0xd50b2037ad200000, + 0x8c213d9da502de45, 0x4526f422cc340000, + 0xaf298d050e4395d6, 0x9670b12b7f410000, + 0xdaf3f04651d47b4c, 0x3c0cdd765f114000, + 0x88d8762bf324cd0f, 0xa5880a69fb6ac800, + 0xab0e93b6efee0053, 0x8eea0d047a457a00, + 0xd5d238a4abe98068, 0x72a4904598d6d880, + 0x85a36366eb71f041, 0x47a6da2b7f864750, + 0xa70c3c40a64e6c51, 0x999090b65f67d924, + 0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d, + 0x82818f1281ed449f, 0xbff8f10e7a8921a4, + 0xa321f2d7226895c7, 0xaff72d52192b6a0d, + 0xcbea6f8ceb02bb39, 0x9bf4f8a69f764490, + 0xfee50b7025c36a08, 0x2f236d04753d5b4, + 0x9f4f2726179a2245, 0x1d762422c946590, + 0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef5, + 0xf8ebad2b84e0d58b, 0xd2e0898765a7deb2, + 0x9b934c3b330c8577, 0x63cc55f49f88eb2f, + 0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fb, + 0xf316271c7fc3908a, 0x8bef464e3945ef7a, + 0x97edd871cfda3a56, 0x97758bf0e3cbb5ac, + 0xbde94e8e43d0c8ec, 0x3d52eeed1cbea317, + 0xed63a231d4c4fb27, 0x4ca7aaa863ee4bdd, + 0x945e455f24fb1cf8, 0x8fe8caa93e74ef6a, + 0xb975d6b6ee39e436, 0xb3e2fd538e122b44, + 0xe7d34c64a9c85d44, 0x60dbbca87196b616, + 0x90e40fbeea1d3a4a, 0xbc8955e946fe31cd, + 0xb51d13aea4a488dd, 0x6babab6398bdbe41, + 0xe264589a4dcdab14, 0xc696963c7eed2dd1, + 0x8d7eb76070a08aec, 0xfc1e1de5cf543ca2, + 0xb0de65388cc8ada8, 0x3b25a55f43294bcb, + 0xdd15fe86affad912, 0x49ef0eb713f39ebe, + 0x8a2dbf142dfcc7ab, 0x6e3569326c784337, + 0xacb92ed9397bf996, 0x49c2c37f07965404, + 0xd7e77a8f87daf7fb, 0xdc33745ec97be906, + 0x86f0ac99b4e8dafd, 0x69a028bb3ded71a3, + 0xa8acd7c0222311bc, 0xc40832ea0d68ce0c, + 0xd2d80db02aabd62b, 0xf50a3fa490c30190, + 0x83c7088e1aab65db, 0x792667c6da79e0fa, + 0xa4b8cab1a1563f52, 0x577001b891185938, + 0xcde6fd5e09abcf26, 0xed4c0226b55e6f86, + 0x80b05e5ac60b6178, 0x544f8158315b05b4, + 0xa0dc75f1778e39d6, 0x696361ae3db1c721, + 0xc913936dd571c84c, 0x3bc3a19cd1e38e9, + 0xfb5878494ace3a5f, 0x4ab48a04065c723, + 0x9d174b2dcec0e47b, 0x62eb0d64283f9c76, + 0xc45d1df942711d9a, 0x3ba5d0bd324f8394, + 0xf5746577930d6500, 0xca8f44ec7ee36479, + 0x9968bf6abbe85f20, 0x7e998b13cf4e1ecb, + 0xbfc2ef456ae276e8, 0x9e3fedd8c321a67e, + 0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101e, + 0x95d04aee3b80ece5, 0xbba1f1d158724a12, + 0xbb445da9ca61281f, 0x2a8a6e45ae8edc97, + 0xea1575143cf97226, 0xf52d09d71a3293bd, + 0x924d692ca61be758, 0x593c2626705f9c56, + 0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836c, + 0xe498f455c38b997a, 0xb6dfb9c0f956447, + 0x8edf98b59a373fec, 0x4724bd4189bd5eac, + 0xb2977ee300c50fe7, 0x58edec91ec2cb657, + 0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ed, + 0x8b865b215899f46c, 0xbd79e0d20082ee74, + 0xae67f1e9aec07187, 0xecd8590680a3aa11, + 0xda01ee641a708de9, 0xe80e6f4820cc9495, + 0x884134fe908658b2, 0x3109058d147fdcdd, + 0xaa51823e34a7eede, 0xbd4b46f0599fd415, + 0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91a, + 0x850fadc09923329e, 0x3e2cf6bc604ddb0, + 0xa6539930bf6bff45, 0x84db8346b786151c, + 0xcfe87f7cef46ff16, 0xe612641865679a63, + 0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07e, + 0xa26da3999aef7749, 0xe3be5e330f38f09d, + 0xcb090c8001ab551c, 0x5cadf5bfd3072cc5, + 0xfdcb4fa002162a63, 0x73d9732fc7c8f7f6, + 0x9e9f11c4014dda7e, 0x2867e7fddcdd9afa, + 0xc646d63501a1511d, 0xb281e1fd541501b8, + 0xf7d88bc24209a565, 0x1f225a7ca91a4226, + 0x9ae757596946075f, 0x3375788de9b06958, + 0xc1a12d2fc3978937, 0x52d6b1641c83ae, + 0xf209787bb47d6b84, 0xc0678c5dbd23a49a, + 0x9745eb4d50ce6332, 0xf840b7ba963646e0, + 0xbd176620a501fbff, 0xb650e5a93bc3d898, + 0xec5d3fa8ce427aff, 0xa3e51f138ab4cebe, + 0x93ba47c980e98cdf, 0xc66f336c36b10137, + 0xb8a8d9bbe123f017, 0xb80b0047445d4184, + 0xe6d3102ad96cec1d, 0xa60dc059157491e5, + 0x9043ea1ac7e41392, 0x87c89837ad68db2f, + 0xb454e4a179dd1877, 0x29babe4598c311fb, + 0xe16a1dc9d8545e94, 0xf4296dd6fef3d67a, + 0x8ce2529e2734bb1d, 0x1899e4a65f58660c, + 0xb01ae745b101e9e4, 0x5ec05dcff72e7f8f, + 0xdc21a1171d42645d, 0x76707543f4fa1f73, + 0x899504ae72497eba, 0x6a06494a791c53a8, + 0xabfa45da0edbde69, 0x487db9d17636892, + 0xd6f8d7509292d603, 0x45a9d2845d3c42b6, + 0x865b86925b9bc5c2, 0xb8a2392ba45a9b2, + 0xa7f26836f282b732, 0x8e6cac7768d7141e, + 0xd1ef0244af2364ff, 0x3207d795430cd926, + 0x8335616aed761f1f, 0x7f44e6bd49e807b8, + 0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a6, + 0xcd036837130890a1, 0x36dba887c37a8c0f, + 0x802221226be55a64, 0xc2494954da2c9789, + 0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6c, + 0xc83553c5c8965d3d, 0x6f92829494e5acc7, + 0xfa42a8b73abbf48c, 0xcb772339ba1f17f9, + 0x9c69a97284b578d7, 0xff2a760414536efb, + 0xc38413cf25e2d70d, 0xfef5138519684aba, + 0xf46518c2ef5b8cd1, 0x7eb258665fc25d69, + 0x98bf2f79d5993802, 0xef2f773ffbd97a61, + 0xbeeefb584aff8603, 0xaafb550ffacfd8fa, + 0xeeaaba2e5dbf6784, 0x95ba2a53f983cf38, + 0x952ab45cfa97a0b2, 0xdd945a747bf26183, + 0xba756174393d88df, 0x94f971119aeef9e4, + 0xe912b9d1478ceb17, 0x7a37cd5601aab85d, + 0x91abb422ccb812ee, 0xac62e055c10ab33a, + 0xb616a12b7fe617aa, 0x577b986b314d6009, + 0xe39c49765fdf9d94, 0xed5a7e85fda0b80b, + 0x8e41ade9fbebc27d, 0x14588f13be847307, + 0xb1d219647ae6b31c, 0x596eb2d8ae258fc8, + 0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bb, + 0x8aec23d680043bee, 0x25de7bb9480d5854, + 0xada72ccc20054ae9, 0xaf561aa79a10ae6a, + 0xd910f7ff28069da4, 0x1b2ba1518094da04, + 0x87aa9aff79042286, 0x90fb44d2f05d0842, + 0xa99541bf57452b28, 0x353a1607ac744a53, + 0xd3fa922f2d1675f2, 0x42889b8997915ce8, + 0x847c9b5d7c2e09b7, 0x69956135febada11, + 0xa59bc234db398c25, 0x43fab9837e699095, + 0xcf02b2c21207ef2e, 0x94f967e45e03f4bb, + 0x8161afb94b44f57d, 0x1d1be0eebac278f5, + 0xa1ba1ba79e1632dc, 0x6462d92a69731732, + 0xca28a291859bbf93, 0x7d7b8f7503cfdcfe, + 0xfcb2cb35e702af78, 0x5cda735244c3d43e, + 0x9defbf01b061adab, 0x3a0888136afa64a7, + 0xc56baec21c7a1916, 0x88aaa1845b8fdd0, + 0xf6c69a72a3989f5b, 0x8aad549e57273d45, + 0x9a3c2087a63f6399, 0x36ac54e2f678864b, + 0xc0cb28a98fcf3c7f, 0x84576a1bb416a7dd, + 0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d5, + 0x969eb7c47859e743, 0x9f644ae5a4b1b325, + 0xbc4665b596706114, 0x873d5d9f0dde1fee, + 0xeb57ff22fc0c7959, 0xa90cb506d155a7ea, + 0x9316ff75dd87cbd8, 0x9a7f12442d588f2, + 0xb7dcbf5354e9bece, 0xc11ed6d538aeb2f, + 0xe5d3ef282a242e81, 0x8f1668c8a86da5fa, + 0x8fa475791a569d10, 0xf96e017d694487bc, + 0xb38d92d760ec4455, 0x37c981dcc395a9ac, + 0xe070f78d3927556a, 0x85bbe253f47b1417, + 0x8c469ab843b89562, 0x93956d7478ccec8e, + 0xaf58416654a6babb, 0x387ac8d1970027b2, + 0xdb2e51bfe9d0696a, 0x6997b05fcc0319e, + 0x88fcf317f22241e2, 0x441fece3bdf81f03, + 0xab3c2fddeeaad25a, 0xd527e81cad7626c3, + 0xd60b3bd56a5586f1, 0x8a71e223d8d3b074, + 0x85c7056562757456, 0xf6872d5667844e49, + 0xa738c6bebb12d16c, 0xb428f8ac016561db, + 0xd106f86e69d785c7, 0xe13336d701beba52, + 0x82a45b450226b39c, 0xecc0024661173473, + 0xa34d721642b06084, 0x27f002d7f95d0190, + 0xcc20ce9bd35c78a5, 0x31ec038df7b441f4, + 0xff290242c83396ce, 0x7e67047175a15271, + 0x9f79a169bd203e41, 0xf0062c6e984d386, + 0xc75809c42c684dd1, 0x52c07b78a3e60868, + 0xf92e0c3537826145, 0xa7709a56ccdf8a82, + 0x9bbcc7a142b17ccb, 0x88a66076400bb691, + 0xc2abf989935ddbfe, 0x6acff893d00ea435, + 0xf356f7ebf83552fe, 0x583f6b8c4124d43, + 0x98165af37b2153de, 0xc3727a337a8b704a, + 0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5c, + 0xeda2ee1c7064130c, 0x1162def06f79df73, + 0x9485d4d1c63e8be7, 0x8addcb5645ac2ba8, + 0xb9a74a0637ce2ee1, 0x6d953e2bd7173692, + 0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0437, + 0x910ab1d4db9914a0, 0x1d9c9892400a22a2, + 0xb54d5e4a127f59c8, 0x2503beb6d00cab4b, + 0xe2a0b5dc971f303a, 0x2e44ae64840fd61d, + 0x8da471a9de737e24, 0x5ceaecfed289e5d2, + 0xb10d8e1456105dad, 0x7425a83e872c5f47, + 0xdd50f1996b947518, 0xd12f124e28f77719, + 0x8a5296ffe33cc92f, 0x82bd6b70d99aaa6f, + 0xace73cbfdc0bfb7b, 0x636cc64d1001550b, + 0xd8210befd30efa5a, 0x3c47f7e05401aa4e, + 0x8714a775e3e95c78, 0x65acfaec34810a71, + 0xa8d9d1535ce3b396, 0x7f1839a741a14d0d, + 0xd31045a8341ca07c, 0x1ede48111209a050, + 0x83ea2b892091e44d, 0x934aed0aab460432, + 0xa4e4b66b68b65d60, 0xf81da84d5617853f, + 0xce1de40642e3f4b9, 0x36251260ab9d668e, + 0x80d2ae83e9ce78f3, 0xc1d72b7c6b426019, + 0xa1075a24e4421730, 0xb24cf65b8612f81f, + 0xc94930ae1d529cfc, 0xdee033f26797b627, + 0xfb9b7cd9a4a7443c, 0x169840ef017da3b1, + 0x9d412e0806e88aa5, 0x8e1f289560ee864e, + 0xc491798a08a2ad4e, 0xf1a6f2bab92a27e2, + 0xf5b5d7ec8acb58a2, 0xae10af696774b1db, + 0x9991a6f3d6bf1765, 0xacca6da1e0a8ef29, + 0xbff610b0cc6edd3f, 0x17fd090a58d32af3, + 0xeff394dcff8a948e, 0xddfc4b4cef07f5b0, + 0x95f83d0a1fb69cd9, 0x4abdaf101564f98e, + 0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f1, + 0xea53df5fd18d5513, 0x84c86189216dc5ed, + 0x92746b9be2f8552c, 0x32fd3cf5b4e49bb4, + 0xb7118682dbb66a77, 0x3fbc8c33221dc2a1, + 0xe4d5e82392a40515, 0xfabaf3feaa5334a, + 0x8f05b1163ba6832d, 0x29cb4d87f2a7400e, + 0xb2c71d5bca9023f8, 0x743e20e9ef511012, + 0xdf78e4b2bd342cf6, 0x914da9246b255416, + 0x8bab8eefb6409c1a, 0x1ad089b6c2f7548e, + 0xae9672aba3d0c320, 0xa184ac2473b529b1, + 0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741e, + 0x8865899617fb1871, 0x7e2fa67c7a658892, + 0xaa7eebfb9df9de8d, 0xddbb901b98feeab7, + 0xd51ea6fa85785631, 0x552a74227f3ea565, + 0x8533285c936b35de, 0xd53a88958f87275f, + 0xa67ff273b8460356, 0x8a892abaf368f137, + 0xd01fef10a657842c, 0x2d2b7569b0432d85, + 0x8213f56a67f6b29b, 0x9c3b29620e29fc73, + 0xa298f2c501f45f42, 0x8349f3ba91b47b8f, + 0xcb3f2f7642717713, 0x241c70a936219a73, + 0xfe0efb53d30dd4d7, 0xed238cd383aa0110, + 0x9ec95d1463e8a506, 0xf4363804324a40aa, + 0xc67bb4597ce2ce48, 0xb143c6053edcd0d5, + 0xf81aa16fdc1b81da, 0xdd94b7868e94050a, + 0x9b10a4e5e9913128, 0xca7cf2b4191c8326, + 0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f0, + 0xf24a01a73cf2dccf, 0xbc633b39673c8cec, + 0x976e41088617ca01, 0xd5be0503e085d813, + 0xbd49d14aa79dbc82, 0x4b2d8644d8a74e18, + 0xec9c459d51852ba2, 0xddf8e7d60ed1219e, + 0x93e1ab8252f33b45, 0xcabb90e5c942b503, + 0xb8da1662e7b00a17, 0x3d6a751f3b936243, + 0xe7109bfba19c0c9d, 0xcc512670a783ad4, + 0x906a617d450187e2, 0x27fb2b80668b24c5, + 0xb484f9dc9641e9da, 0xb1f9f660802dedf6, + 0xe1a63853bbd26451, 0x5e7873f8a0396973, + 0x8d07e33455637eb2, 0xdb0b487b6423e1e8, + 0xb049dc016abc5e5f, 0x91ce1a9a3d2cda62, + 0xdc5c5301c56b75f7, 0x7641a140cc7810fb, + 0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9d, + 0xac2820d9623bf429, 0x546345fa9fbdcd44, + 0xd732290fbacaf133, 0xa97c177947ad4095, + 0x867f59a9d4bed6c0, 0x49ed8eabcccc485d, + 0xa81f301449ee8c70, 0x5c68f256bfff5a74, + 0xd226fc195c6a2f8c, 0x73832eec6fff3111, + 0x83585d8fd9c25db7, 0xc831fd53c5ff7eab, + 0xa42e74f3d032f525, 0xba3e7ca8b77f5e55, + 0xcd3a1230c43fb26f, 0x28ce1bd2e55f35eb, + 0x80444b5e7aa7cf85, 0x7980d163cf5b81b3, + 0xa0555e361951c366, 0xd7e105bcc332621f, + 0xc86ab5c39fa63440, 0x8dd9472bf3fefaa7, + 0xfa856334878fc150, 0xb14f98f6f0feb951, + 0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d3, + 0xc3b8358109e84f07, 0xa862f80ec4700c8, + 0xf4a642e14c6262c8, 0xcd27bb612758c0fa, + 0x98e7e9cccfbd7dbd, 0x8038d51cb897789c, + 0xbf21e44003acdd2c, 0xe0470a63e6bd56c3, + 0xeeea5d5004981478, 0x1858ccfce06cac74, + 0x95527a5202df0ccb, 0xf37801e0c43ebc8, + 0xbaa718e68396cffd, 0xd30560258f54e6ba, + 0xe950df20247c83fd, 0x47c6b82ef32a2069, + 0x91d28b7416cdd27e, 0x4cdc331d57fa5441, + 0xb6472e511c81471d, 0xe0133fe4adf8e952, + 0xe3d8f9e563a198e5, 0x58180fddd97723a6, + 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648, +}; + +/* binary_format constants from fast_float. */ +#define FFP_MANTISSA_BITS 52 +#define FFP_MIN_EXPONENT (-1023) +#define FFP_INFINITE_POWER 0x7FF +#define FFP_SMALLEST_POW10 (-342) +#define FFP_LARGEST_POW10 308 +#define FFP_MIN_RTE (-4) /* min_exponent_round_to_even */ +#define FFP_MAX_RTE 23 /* max_exponent_round_to_even */ + +/* (((152170 + 65536) * q) >> 16) + 63 == floor(log2(10^q)) + q + 63, see paper. */ +static inline int32_t ffp_power(int32_t q) { + return (((152170 + 65536) * q) >> 16) + 63; +} + +static inline void ffp_mul128(uint64_t a, uint64_t b, uint64_t *hi, uint64_t *lo) { +#if defined(__SIZEOF_INT128__) + __uint128_t p = (__uint128_t)a * (__uint128_t)b; + *lo = (uint64_t)p; + *hi = (uint64_t)(p >> 64); +#else + uint64_t a0 = (uint32_t)a, a1 = a >> 32, b0 = (uint32_t)b, b1 = b >> 32; + uint64_t p00 = a0 * b0, p01 = a0 * b1, p10 = a1 * b0, p11 = a1 * b1; + uint64_t mid = p10 + (p00 >> 32) + (uint32_t)p01; + *hi = p11 + (mid >> 32) + (p01 >> 32); + *lo = (mid << 32) | (uint32_t)p00; +#endif +} + +static inline double ffp_bits2double(uint64_t bits) { + double d; + memcpy(&d, &bits, sizeof(d)); + return d; +} + +#if defined(__x86_64__) && defined(HAVE_X86INTRIN_H) +# include +#elif defined(_MSC_VER) && defined(HAVE_INTRIN_H) +# include +#endif + +#if defined(_MSC_VER) && defined(__AVX2__) +# pragma intrinsic(__lzcnt64) +#endif + +static inline unsigned int +ffc_nlz_int64(uint64_t x) +{ +#if defined(HAVE_BUILTIN___BUILTIN_CLZLL) && !(defined(__sun) && defined(__sparc)) + return (unsigned int)__builtin_clzll((unsigned long long)x); + +#elif defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT64) + return (unsigned int)__lzcnt64(x); + +#elif defined(__x86_64__) && defined(__LZCNT__) && defined(HAVE__LZCNT_U64) + return (unsigned int)_lzcnt_u64(x); + +#elif defined(_WIN64) && defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE64) + unsigned long r; + return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; + +#else + uint64_t y; + unsigned int n = 64; + y = x >> 32; if (y) {n -= 32; x = y;} + y = x >> 16; if (y) {n -= 16; x = y;} + y = x >> 8; if (y) {n -= 8; x = y;} + y = x >> 4; if (y) {n -= 4; x = y;} + y = x >> 2; if (y) {n -= 2; x = y;} + y = x >> 1; if (y) {return n - 2;} + return (unsigned int)(n - x); + +#endif +} + +/* q = power of ten, w = mantissa (exact, fits in uint64). neg = sign. */ +static inline double ffp_s2d(int64_t q, uint64_t w, bool neg) { + if (w == 0) { + return neg ? -0.0 : 0.0; + } + + const uint64_t sign = (uint64_t)(neg != 0) << 63; + uint64_t mantissa, prod_hi, prod_lo, sp_hi, sp_lo; + int32_t power2; + int upperbit, shift, index; + + if (q < FFP_SMALLEST_POW10) return ffp_bits2double(sign); /* underflow -> 0 */ + if (q > FFP_LARGEST_POW10) { + return ffp_bits2double(sign | ((uint64_t)FFP_INFINITE_POWER << FFP_MANTISSA_BITS)); + } + + unsigned int lz = ffc_nlz_int64(w); + w <<= lz; + + /* compute_product_approximation: precision_mask = 0x1FF. */ + index = 2 * (int)(q - FFP_SMALLEST_POWER_OF_FIVE); + ffp_mul128(w, ffp_power_of_five_128[index], &prod_hi, &prod_lo); + if ((prod_hi & 0x1FF) == 0x1FF) { + ffp_mul128(w, ffp_power_of_five_128[index + 1], &sp_hi, &sp_lo); + prod_lo += sp_hi; + if (sp_hi > prod_lo) { + prod_hi++; + } + } + + upperbit = (int)(prod_hi >> 63); + shift = upperbit + 64 - FFP_MANTISSA_BITS - 3; /* upperbit + 9 */ + mantissa = prod_hi >> shift; + power2 = (int32_t)(ffp_power((int32_t)q) + upperbit - lz - FFP_MIN_EXPONENT); + + if (power2 <= 0) { /* subnormal */ + if (-power2 + 1 >= 64) return ffp_bits2double(sign); /* far below min -> 0 */ + mantissa >>= (-power2 + 1); + mantissa += (mantissa & 1); + mantissa >>= 1; + power2 = (mantissa < ((uint64_t)1 << FFP_MANTISSA_BITS)) ? 0 : 1; + return ffp_bits2double(sign | ((uint64_t)power2 << FFP_MANTISSA_BITS) | mantissa); + } + + /* round-to-even: if we land exactly between two doubles, round down. */ + if ((prod_lo <= 1) && (q >= FFP_MIN_RTE) && (q <= FFP_MAX_RTE) && + ((mantissa & 3) == 1) && ((mantissa << shift) == prod_hi)) { + mantissa &= ~(uint64_t)1; + } + + mantissa += (mantissa & 1); + mantissa >>= 1; + if (mantissa >= ((uint64_t)2 << FFP_MANTISSA_BITS)) { + mantissa = (uint64_t)1 << FFP_MANTISSA_BITS; + power2++; + } + + mantissa &= ~((uint64_t)1 << FFP_MANTISSA_BITS); /* drop implicit bit */ + if (power2 >= FFP_INFINITE_POWER) { + return ffp_bits2double(sign | ((uint64_t)FFP_INFINITE_POWER << FFP_MANTISSA_BITS)); + } + return ffp_bits2double(sign | ((uint64_t)power2 << FFP_MANTISSA_BITS) | mantissa); +} + +#endif // FFP_H diff --git a/ext/json/ext/vendor/fpconv.c b/ext/json/ext/vendor/fpconv.c new file mode 100644 index 000000000..6c9bc2c10 --- /dev/null +++ b/ext/json/ext/vendor/fpconv.c @@ -0,0 +1,480 @@ +// Boost Software License - Version 1.0 - August 17th, 2003 +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +// The contents of this file is extracted from https://github.com/night-shift/fpconv +// It was slightly modified to append ".0" to plain floats, for use with the https://github.com/ruby/json package. + +#include +#include +#include + +#if JSON_DEBUG +#include +#endif + +#define npowers 87 +#define steppowers 8 +#define firstpower -348 /* 10 ^ -348 */ + +#define expmax -32 +#define expmin -60 + +typedef struct Fp { + uint64_t frac; + int exp; +} Fp; + +static const Fp powers_ten[] = { + { 18054884314459144840U, -1220 }, { 13451937075301367670U, -1193 }, + { 10022474136428063862U, -1166 }, { 14934650266808366570U, -1140 }, + { 11127181549972568877U, -1113 }, { 16580792590934885855U, -1087 }, + { 12353653155963782858U, -1060 }, { 18408377700990114895U, -1034 }, + { 13715310171984221708U, -1007 }, { 10218702384817765436U, -980 }, + { 15227053142812498563U, -954 }, { 11345038669416679861U, -927 }, + { 16905424996341287883U, -901 }, { 12595523146049147757U, -874 }, + { 9384396036005875287U, -847 }, { 13983839803942852151U, -821 }, + { 10418772551374772303U, -794 }, { 15525180923007089351U, -768 }, + { 11567161174868858868U, -741 }, { 17236413322193710309U, -715 }, + { 12842128665889583758U, -688 }, { 9568131466127621947U, -661 }, + { 14257626930069360058U, -635 }, { 10622759856335341974U, -608 }, + { 15829145694278690180U, -582 }, { 11793632577567316726U, -555 }, + { 17573882009934360870U, -529 }, { 13093562431584567480U, -502 }, + { 9755464219737475723U, -475 }, { 14536774485912137811U, -449 }, + { 10830740992659433045U, -422 }, { 16139061738043178685U, -396 }, + { 12024538023802026127U, -369 }, { 17917957937422433684U, -343 }, + { 13349918974505688015U, -316 }, { 9946464728195732843U, -289 }, + { 14821387422376473014U, -263 }, { 11042794154864902060U, -236 }, + { 16455045573212060422U, -210 }, { 12259964326927110867U, -183 }, + { 18268770466636286478U, -157 }, { 13611294676837538539U, -130 }, + { 10141204801825835212U, -103 }, { 15111572745182864684U, -77 }, + { 11258999068426240000U, -50 }, { 16777216000000000000U, -24 }, + { 12500000000000000000U, 3 }, { 9313225746154785156U, 30 }, + { 13877787807814456755U, 56 }, { 10339757656912845936U, 83 }, + { 15407439555097886824U, 109 }, { 11479437019748901445U, 136 }, + { 17105694144590052135U, 162 }, { 12744735289059618216U, 189 }, + { 9495567745759798747U, 216 }, { 14149498560666738074U, 242 }, + { 10542197943230523224U, 269 }, { 15709099088952724970U, 295 }, + { 11704190886730495818U, 322 }, { 17440603504673385349U, 348 }, + { 12994262207056124023U, 375 }, { 9681479787123295682U, 402 }, + { 14426529090290212157U, 428 }, { 10748601772107342003U, 455 }, + { 16016664761464807395U, 481 }, { 11933345169920330789U, 508 }, + { 17782069995880619868U, 534 }, { 13248674568444952270U, 561 }, + { 9871031767461413346U, 588 }, { 14708983551653345445U, 614 }, + { 10959046745042015199U, 641 }, { 16330252207878254650U, 667 }, + { 12166986024289022870U, 694 }, { 18130221999122236476U, 720 }, + { 13508068024458167312U, 747 }, { 10064294952495520794U, 774 }, + { 14996968138956309548U, 800 }, { 11173611982879273257U, 827 }, + { 16649979327439178909U, 853 }, { 12405201291620119593U, 880 }, + { 9242595204427927429U, 907 }, { 13772540099066387757U, 933 }, + { 10261342003245940623U, 960 }, { 15290591125556738113U, 986 }, + { 11392378155556871081U, 1013 }, { 16975966327722178521U, 1039 }, + { 12648080533535911531U, 1066 } +}; + +static Fp find_cachedpow10(int exp, int* k) +{ + const double one_log_ten = 0.30102999566398114; + + int approx = (int)(-(exp + npowers) * one_log_ten); + int idx = (approx - firstpower) / steppowers; + + while(1) { + int current = exp + powers_ten[idx].exp + 64; + + if(current < expmin) { + idx++; + continue; + } + + if(current > expmax) { + idx--; + continue; + } + + *k = (firstpower + idx * steppowers); + + return powers_ten[idx]; + } +} + +#define fracmask 0x000FFFFFFFFFFFFFU +#define expmask 0x7FF0000000000000U +#define hiddenbit 0x0010000000000000U +#define signmask 0x8000000000000000U +#define expbias (1023 + 52) + +#define absv(n) ((n) < 0 ? -(n) : (n)) +#define minv(a, b) ((a) < (b) ? (a) : (b)) + +static const uint64_t tens[] = { + 10000000000000000000U, 1000000000000000000U, 100000000000000000U, + 10000000000000000U, 1000000000000000U, 100000000000000U, + 10000000000000U, 1000000000000U, 100000000000U, + 10000000000U, 1000000000U, 100000000U, + 10000000U, 1000000U, 100000U, + 10000U, 1000U, 100U, + 10U, 1U +}; + +static inline uint64_t get_dbits(double d) +{ + union { + double dbl; + uint64_t i; + } dbl_bits = { d }; + + return dbl_bits.i; +} + +static Fp build_fp(double d) +{ + uint64_t bits = get_dbits(d); + + Fp fp; + fp.frac = bits & fracmask; + fp.exp = (bits & expmask) >> 52; + + if(fp.exp) { + fp.frac += hiddenbit; + fp.exp -= expbias; + + } else { + fp.exp = -expbias + 1; + } + + return fp; +} + +static void normalize(Fp* fp) +{ + while ((fp->frac & hiddenbit) == 0) { + fp->frac <<= 1; + fp->exp--; + } + + int shift = 64 - 52 - 1; + fp->frac <<= shift; + fp->exp -= shift; +} + +static void get_normalized_boundaries(Fp* fp, Fp* lower, Fp* upper) +{ + upper->frac = (fp->frac << 1) + 1; + upper->exp = fp->exp - 1; + + while ((upper->frac & (hiddenbit << 1)) == 0) { + upper->frac <<= 1; + upper->exp--; + } + + int u_shift = 64 - 52 - 2; + + upper->frac <<= u_shift; + upper->exp = upper->exp - u_shift; + + + int l_shift = fp->frac == hiddenbit ? 2 : 1; + + lower->frac = (fp->frac << l_shift) - 1; + lower->exp = fp->exp - l_shift; + + + lower->frac <<= lower->exp - upper->exp; + lower->exp = upper->exp; +} + +static Fp multiply(Fp* a, Fp* b) +{ + const uint64_t lomask = 0x00000000FFFFFFFF; + + uint64_t ah_bl = (a->frac >> 32) * (b->frac & lomask); + uint64_t al_bh = (a->frac & lomask) * (b->frac >> 32); + uint64_t al_bl = (a->frac & lomask) * (b->frac & lomask); + uint64_t ah_bh = (a->frac >> 32) * (b->frac >> 32); + + uint64_t tmp = (ah_bl & lomask) + (al_bh & lomask) + (al_bl >> 32); + /* round up */ + tmp += 1U << 31; + + Fp fp = { + ah_bh + (ah_bl >> 32) + (al_bh >> 32) + (tmp >> 32), + a->exp + b->exp + 64 + }; + + return fp; +} + +static void round_digit(char* digits, int ndigits, uint64_t delta, uint64_t rem, uint64_t kappa, uint64_t frac) +{ + while (rem < frac && delta - rem >= kappa && + (rem + kappa < frac || frac - rem > rem + kappa - frac)) { + + digits[ndigits - 1]--; + rem += kappa; + } +} + +static int generate_digits(Fp* fp, Fp* upper, Fp* lower, char* digits, int* K) +{ + uint64_t wfrac = upper->frac - fp->frac; + uint64_t delta = upper->frac - lower->frac; + + Fp one; + one.frac = 1ULL << -upper->exp; + one.exp = upper->exp; + + uint64_t part1 = upper->frac >> -one.exp; + uint64_t part2 = upper->frac & (one.frac - 1); + + int idx = 0, kappa = 10; + const uint64_t* divp; + /* 1000000000 */ + for(divp = tens + 10; kappa > 0; divp++) { + + uint64_t div = *divp; + unsigned digit = (unsigned) (part1 / div); + + if (digit || idx) { + digits[idx++] = digit + '0'; + } + + part1 -= digit * div; + kappa--; + + uint64_t tmp = (part1 <<-one.exp) + part2; + if (tmp <= delta) { + *K += kappa; + round_digit(digits, idx, delta, tmp, div << -one.exp, wfrac); + + return idx; + } + } + + /* 10 */ + const uint64_t* unit = tens + 18; + + while(true) { + part2 *= 10; + delta *= 10; + kappa--; + + unsigned digit = (unsigned) (part2 >> -one.exp); + if (digit || idx) { + digits[idx++] = digit + '0'; + } + + part2 &= one.frac - 1; + if (part2 < delta) { + *K += kappa; + round_digit(digits, idx, delta, part2, one.frac, wfrac * *unit); + + return idx; + } + + unit--; + } +} + +static int grisu2(double d, char* digits, int* K) +{ + Fp w = build_fp(d); + + Fp lower, upper; + get_normalized_boundaries(&w, &lower, &upper); + + normalize(&w); + + int k; + Fp cp = find_cachedpow10(upper.exp, &k); + + w = multiply(&w, &cp); + upper = multiply(&upper, &cp); + lower = multiply(&lower, &cp); + + lower.frac++; + upper.frac--; + + *K = -k; + + return generate_digits(&w, &upper, &lower, digits, K); +} + +static int emit_digits(char* digits, int ndigits, char* dest, int K, bool neg) +{ + int exp = absv(K + ndigits - 1); + + if(K >= 0 && exp < 15) { + memcpy(dest, digits, ndigits); + memset(dest + ndigits, '0', K); + + /* add a .0 to mark this as a float. */ + dest[ndigits + K] = '.'; + dest[ndigits + K + 1] = '0'; + + return ndigits + K + 2; + } + + /* write decimal w/o scientific notation */ + if(K < 0 && (K > -7 || exp < 10)) { + int offset = ndigits - absv(K); + /* fp < 1.0 -> write leading zero */ + if(offset <= 0) { + offset = -offset; + dest[0] = '0'; + dest[1] = '.'; + memset(dest + 2, '0', offset); + memcpy(dest + offset + 2, digits, ndigits); + + return ndigits + 2 + offset; + + /* fp > 1.0 */ + } else { + memcpy(dest, digits, offset); + dest[offset] = '.'; + memcpy(dest + offset + 1, digits + offset, ndigits - offset); + + return ndigits + 1; + } + } + + /* write decimal w/ scientific notation */ + ndigits = minv(ndigits, 18 - neg); + + int idx = 0; + dest[idx++] = digits[0]; + + if(ndigits > 1) { + dest[idx++] = '.'; + memcpy(dest + idx, digits + 1, ndigits - 1); + idx += ndigits - 1; + } + + dest[idx++] = 'e'; + + char sign = K + ndigits - 1 < 0 ? '-' : '+'; + dest[idx++] = sign; + + int cent = 0; + + if(exp > 99) { + cent = exp / 100; + dest[idx++] = cent + '0'; + exp -= cent * 100; + } + if(exp > 9) { + int dec = exp / 10; + dest[idx++] = dec + '0'; + exp -= dec * 10; + + } else if(cent) { + dest[idx++] = '0'; + } + + dest[idx++] = exp % 10 + '0'; + + return idx; +} + +static int filter_special(double fp, char* dest) +{ + if(fp == 0.0) { + dest[0] = '0'; + dest[1] = '.'; + dest[2] = '0'; + return 3; + } + + uint64_t bits = get_dbits(fp); + + bool nan = (bits & expmask) == expmask; + + if(!nan) { + return 0; + } + + if(bits & fracmask) { + dest[0] = 'n'; dest[1] = 'a'; dest[2] = 'n'; + + } else { + dest[0] = 'i'; dest[1] = 'n'; dest[2] = 'f'; + } + + return 3; +} + +/* Fast and accurate double to string conversion based on Florian Loitsch's + * Grisu-algorithm[1]. + * + * Input: + * fp -> the double to convert, dest -> destination buffer. + * The generated string will never be longer than 32 characters. + * Make sure to pass a pointer to at least 32 bytes of memory. + * The emitted string will not be null terminated. + * + * + * + * Output: + * The number of written characters. + * + * Exemplary usage: + * + * void print(double d) + * { + * char buf[28 + 1] // plus null terminator + * int str_len = fpconv_dtoa(d, buf); + * + * buf[str_len] = '\0'; + * printf("%s", buf); + * } + * + */ +static int fpconv_dtoa(double d, char dest[32]) +{ + char digits[18]; + + int str_len = 0; + bool neg = false; + + if(get_dbits(d) & signmask) { + dest[0] = '-'; + str_len++; + neg = true; + } + + int spec = filter_special(d, dest + str_len); + + if(spec) { + return str_len + spec; + } + + int K = 0; + int ndigits = grisu2(d, digits, &K); + + str_len += emit_digits(digits, ndigits, dest + str_len, K, neg); +#if JSON_DEBUG + assert(str_len <= 32); +#endif + + return str_len; +} diff --git a/ext/json/ext/vendor/jeaiii-ltoa.h b/ext/json/ext/vendor/jeaiii-ltoa.h new file mode 100644 index 000000000..ba4f497fc --- /dev/null +++ b/ext/json/ext/vendor/jeaiii-ltoa.h @@ -0,0 +1,267 @@ +/* + +This file is released under the terms of the MIT License. It is based on the +work of James Edward Anhalt III, with the original license listed below. + +MIT License + +Copyright (c) 2024,2025 Enrico Thierbach - https://github.com/radiospiel +Copyright (c) 2022 James Edward Anhalt III - https://github.com/jeaiii/itoa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef JEAIII_TO_TEXT_H_ +#define JEAIII_TO_TEXT_H_ + +#include + +typedef uint_fast32_t u32_t; +typedef uint_fast64_t u64_t; + +#define u32(x) ((u32_t)(x)) +#define u64(x) ((u64_t)(x)) + +struct digit_pair +{ + char dd[2]; +}; + +static const struct digit_pair *digits_dd = (struct digit_pair *)( + "00" "01" "02" "03" "04" "05" "06" "07" "08" "09" + "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" + "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" + "30" "31" "32" "33" "34" "35" "36" "37" "38" "39" + "40" "41" "42" "43" "44" "45" "46" "47" "48" "49" + "50" "51" "52" "53" "54" "55" "56" "57" "58" "59" + "60" "61" "62" "63" "64" "65" "66" "67" "68" "69" + "70" "71" "72" "73" "74" "75" "76" "77" "78" "79" + "80" "81" "82" "83" "84" "85" "86" "87" "88" "89" + "90" "91" "92" "93" "94" "95" "96" "97" "98" "99" +); + +static const struct digit_pair *digits_fd = (struct digit_pair *)( + "0_" "1_" "2_" "3_" "4_" "5_" "6_" "7_" "8_" "9_" + "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" + "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" + "30" "31" "32" "33" "34" "35" "36" "37" "38" "39" + "40" "41" "42" "43" "44" "45" "46" "47" "48" "49" + "50" "51" "52" "53" "54" "55" "56" "57" "58" "59" + "60" "61" "62" "63" "64" "65" "66" "67" "68" "69" + "70" "71" "72" "73" "74" "75" "76" "77" "78" "79" + "80" "81" "82" "83" "84" "85" "86" "87" "88" "89" + "90" "91" "92" "93" "94" "95" "96" "97" "98" "99" +); + +static const u64_t mask24 = (u64(1) << 24) - 1; +static const u64_t mask32 = (u64(1) << 32) - 1; +static const u64_t mask57 = (u64(1) << 57) - 1; + +#define COPY(buffer, digits) memcpy(buffer, &(digits), sizeof(struct digit_pair)) + +static char * +jeaiii_ultoa(char *b, u64_t n) +{ + if (n < u32(1e2)) { + COPY(b, digits_fd[n]); + return n < 10 ? b + 1 : b + 2; + } + + if (n < u32(1e6)) { + if (n < u32(1e4)) { + u32_t f0 = u32((10 * (1 << 24) / 1e3 + 1) * n); + COPY(b, digits_fd[f0 >> 24]); + + b -= n < u32(1e3); + u32_t f2 = (f0 & mask24) * 100; + COPY(b + 2, digits_dd[f2 >> 24]); + + return b + 4; + } + + u64_t f0 = u64(10 * (1ull << 32ull)/ 1e5 + 1) * n; + COPY(b, digits_fd[f0 >> 32]); + + b -= n < u32(1e5); + u64_t f2 = (f0 & mask32) * 100; + COPY(b + 2, digits_dd[f2 >> 32]); + + u64_t f4 = (f2 & mask32) * 100; + COPY(b + 4, digits_dd[f4 >> 32]); + return b + 6; + } + + if (n < u64(1ull << 32ull)) { + if (n < u32(1e8)) { + u64_t f0 = u64(10 * (1ull << 48ull) / 1e7 + 1) * n >> 16; + COPY(b, digits_fd[f0 >> 32]); + + b -= n < u32(1e7); + u64_t f2 = (f0 & mask32) * 100; + COPY(b + 2, digits_dd[f2 >> 32]); + + u64_t f4 = (f2 & mask32) * 100; + COPY(b + 4, digits_dd[f4 >> 32]); + + u64_t f6 = (f4 & mask32) * 100; + COPY(b + 6, digits_dd[f6 >> 32]); + + return b + 8; + } + + u64_t f0 = u64(10 * (1ull << 57ull) / 1e9 + 1) * n; + COPY(b, digits_fd[f0 >> 57]); + + b -= n < u32(1e9); + u64_t f2 = (f0 & mask57) * 100; + COPY(b + 2, digits_dd[f2 >> 57]); + + u64_t f4 = (f2 & mask57) * 100; + COPY(b + 4, digits_dd[f4 >> 57]); + + u64_t f6 = (f4 & mask57) * 100; + COPY(b + 6, digits_dd[f6 >> 57]); + + u64_t f8 = (f6 & mask57) * 100; + COPY(b + 8, digits_dd[f8 >> 57]); + + return b + 10; + } + + // if we get here U must be u64 but some compilers don't know that, so reassign n to a u64 to avoid warnings + u32_t z = n % u32(1e8); + u64_t u = n / u32(1e8); + + if (u < u32(1e2)) { + // u can't be 1 digit (if u < 10 it would have been handled above as a 9 digit 32bit number) + COPY(b, digits_dd[u]); + b += 2; + } + else if (u < u32(1e6)) { + if (u < u32(1e4)) { + u32_t f0 = u32((10 * (1 << 24) / 1e3 + 1) * u); + COPY(b, digits_fd[f0 >> 24]); + + b -= u < u32(1e3); + u32_t f2 = (f0 & mask24) * 100; + COPY(b + 2, digits_dd[f2 >> 24]); + b += 4; + } + else { + u64_t f0 = u64(10 * (1ull << 32ull) / 1e5 + 1) * u; + COPY(b, digits_fd[f0 >> 32]); + + b -= u < u32(1e5); + u64_t f2 = (f0 & mask32) * 100; + COPY(b + 2, digits_dd[f2 >> 32]); + + u64_t f4 = (f2 & mask32) * 100; + COPY(b + 4, digits_dd[f4 >> 32]); + b += 6; + } + } + else if (u < u32(1e8)) { + u64_t f0 = u64(10 * (1ull << 48ull) / 1e7 + 1) * u >> 16; + COPY(b, digits_fd[f0 >> 32]); + + b -= u < u32(1e7); + u64_t f2 = (f0 & mask32) * 100; + COPY(b + 2, digits_dd[f2 >> 32]); + + u64_t f4 = (f2 & mask32) * 100; + COPY(b + 4, digits_dd[f4 >> 32]); + + u64_t f6 = (f4 & mask32) * 100; + COPY(b + 6, digits_dd[f6 >> 32]); + + b += 8; + } + else if (u < u64(1ull << 32ull)) { + u64_t f0 = u64(10 * (1ull << 57ull) / 1e9 + 1) * u; + COPY(b, digits_fd[f0 >> 57]); + + b -= u < u32(1e9); + u64_t f2 = (f0 & mask57) * 100; + COPY(b + 2, digits_dd[f2 >> 57]); + + u64_t f4 = (f2 & mask57) * 100; + COPY(b + 4, digits_dd[f4 >> 57]); + + u64_t f6 = (f4 & mask57) * 100; + COPY(b + 6, digits_dd[f6 >> 57]); + + u64_t f8 = (f6 & mask57) * 100; + COPY(b + 8, digits_dd[f8 >> 57]); + b += 10; + } + else { + u32_t y = u % u32(1e8); + u /= u32(1e8); + + // u is 2, 3, or 4 digits (if u < 10 it would have been handled above) + if (u < u32(1e2)) { + COPY(b, digits_dd[u]); + b += 2; + } + else { + u32_t f0 = u32((10 * (1 << 24) / 1e3 + 1) * u); + COPY(b, digits_fd[f0 >> 24]); + + b -= u < u32(1e3); + u32_t f2 = (f0 & mask24) * 100; + COPY(b + 2, digits_dd[f2 >> 24]); + + b += 4; + } + // do 8 digits + u64_t f0 = (u64((1ull << 48ull) / 1e6 + 1) * y >> 16) + 1; + COPY(b, digits_dd[f0 >> 32]); + + u64_t f2 = (f0 & mask32) * 100; + COPY(b + 2, digits_dd[f2 >> 32]); + + u64_t f4 = (f2 & mask32) * 100; + COPY(b + 4, digits_dd[f4 >> 32]); + + u64_t f6 = (f4 & mask32) * 100; + COPY(b + 6, digits_dd[f6 >> 32]); + b += 8; + } + + // do 8 digits + u64_t f0 = (u64((1ull << 48ull) / 1e6 + 1) * z >> 16) + 1; + COPY(b, digits_dd[f0 >> 32]); + + u64_t f2 = (f0 & mask32) * 100; + COPY(b + 2, digits_dd[f2 >> 32]); + + u64_t f4 = (f2 & mask32) * 100; + COPY(b + 4, digits_dd[f4 >> 32]); + + u64_t f6 = (f4 & mask32) * 100; + COPY(b + 6, digits_dd[f6 >> 32]); + + return b + 8; +} + +#undef u32 +#undef u64 +#undef COPY + +#endif // JEAIII_TO_TEXT_H_ diff --git a/install.rb b/install.rb deleted file mode 100755 index 9d3ae12d8..000000000 --- a/install.rb +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env ruby - -require 'fileutils' -include FileUtils::Verbose -require 'rbconfig' -include\ - begin - RbConfig - rescue NameError - Config - end - -sitelibdir = CONFIG["sitelibdir"] -cd 'lib' do - install('json.rb', sitelibdir) - mkdir_p File.join(sitelibdir, 'json') - for file in Dir['json/**/*}'] - d = File.join(sitelibdir, file) - mkdir_p File.dirname(d) - install(file, d) - end -end -warn " *** Installed PURE ruby library." diff --git a/java/src/json/ext/AbstractByteListDirectOutputStream.java b/java/src/json/ext/AbstractByteListDirectOutputStream.java new file mode 100644 index 000000000..b94d7f59a --- /dev/null +++ b/java/src/json/ext/AbstractByteListDirectOutputStream.java @@ -0,0 +1,29 @@ +package json.ext; + +import java.io.OutputStream; + +import org.jcodings.Encoding; +import org.jruby.util.ByteList; + +abstract class AbstractByteListDirectOutputStream extends OutputStream { + + private static final String PROP_SEGMENTED_BUFFER = "jruby.json.useSegmentedOutputStream"; + private static final String PROP_SEGMENTED_BUFFER_DEFAULT = "true"; + + private static final boolean USE_SEGMENTED_BUFFER; + + static { + String useSegmentedOutputStream = System.getProperty(PROP_SEGMENTED_BUFFER, PROP_SEGMENTED_BUFFER_DEFAULT); + USE_SEGMENTED_BUFFER = Boolean.parseBoolean(useSegmentedOutputStream); + } + + public static AbstractByteListDirectOutputStream create(int estimatedSize) { + if (USE_SEGMENTED_BUFFER) { + return new SegmentedByteListDirectOutputStream(estimatedSize); + } else { + return new ByteListDirectOutputStream(estimatedSize); + } + } + + public abstract ByteList toByteListDirect(Encoding encoding); +} diff --git a/java/src/json/ext/ByteListDirectOutputStream.java b/java/src/json/ext/ByteListDirectOutputStream.java index b22d48128..a92753ca3 100644 --- a/java/src/json/ext/ByteListDirectOutputStream.java +++ b/java/src/json/ext/ByteListDirectOutputStream.java @@ -4,10 +4,9 @@ import org.jruby.util.ByteList; import java.io.IOException; -import java.io.OutputStream; import java.util.Arrays; -public class ByteListDirectOutputStream extends OutputStream { +public class ByteListDirectOutputStream extends AbstractByteListDirectOutputStream { private byte[] buffer; private int length; diff --git a/java/src/json/ext/Generator.java b/java/src/json/ext/Generator.java index c46a1e475..750be6bf6 100644 --- a/java/src/json/ext/Generator.java +++ b/java/src/json/ext/Generator.java @@ -5,6 +5,7 @@ */ package json.ext; +import org.jcodings.Encoding; import org.jcodings.specific.UTF8Encoding; import org.jruby.Ruby; import org.jruby.RubyArray; @@ -15,6 +16,8 @@ import org.jruby.RubyFixnum; import org.jruby.RubyFloat; import org.jruby.RubyHash; +import org.jruby.RubyIO; +import org.jruby.RubyProc; import org.jruby.RubyString; import org.jruby.RubySymbol; import org.jruby.runtime.Helpers; @@ -24,6 +27,8 @@ import org.jruby.util.IOOutputStream; import org.jruby.util.TypeConverter; +import json.ext.ByteListDirectOutputStream; + import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -50,7 +55,14 @@ static RubyString generateJson(ThreadContext context, T static RubyString generateJson(ThreadContext context, T object, Handler handler, IRubyObject arg0) { Session session = new Session(arg0); - return handler.generateNew(context, session, object); + GeneratorState state = session.getState(context); + state.validateRfc8785(context); + int depth = state.depth; + try { + return handler.generateNew(context, session, object); + } finally { + state.depth = depth; + } } /** @@ -75,15 +87,53 @@ static RubyString generateJson(ThreadContext context, T generateJson(ThreadContext context, T object, GeneratorState config, IRubyObject io) { Session session = new Session(config); - Handler handler = getHandlerFor(context.runtime, object); + GeneratorState state = session.getState(context); + state.validateRfc8785(context); + int depth = state.depth; - if (io.isNil()) { - return handler.generateNew(context, session, object); + try { + Handler handler = getHandlerFor(context.runtime, object); + + if (io.isNil()) { + return handler.generateNew(context, session, object); + } + + BufferedOutputStream buffer = + new BufferedOutputStream( + new PatchedIOOutputStream(io, UTF8Encoding.INSTANCE), + IO_BUFFER_SIZE); + handler.generateToBuffer(context, session, object, buffer); + return io; + } finally { + state.depth = depth; } + } + + /** + * A version of IOOutputStream hacked to avoid fast-path RubyIO calls when the target IO has an external encoding. + * + * All calls to the underlying IO will be done dynamically and all incoming bytes wrapped in RubyString instances. + * This avoids bugs in the fast-path logic in JRuby 9.4.12.0 and earlier that fails to properly handle writing bytes + * when the source and target destination are the same. + * + * See https://github.com/jruby/jruby/issues/8682 + */ + private static class PatchedIOOutputStream extends IOOutputStream { + public PatchedIOOutputStream(IRubyObject io, Encoding encoding) { + super(io, encoding); + } + + @Override + public RubyIO getRealIO(IRubyObject io) { + RubyIO realIO = super.getRealIO(io); - BufferedOutputStream buffer = new BufferedOutputStream(new IOOutputStream(io), IO_BUFFER_SIZE); - handler.generateToBuffer(context, session, object, buffer); - return io; + // if the real IO has an external encoding, don't use fast path + if (realIO == null || realIO.getEnc() != null) { + return null; + } + + return realIO; + } } /** @@ -126,8 +176,8 @@ private static void generateFor(ThreadContext context, S case TRUE : buffer.write(TRUE_STRING); return; case FALSE : buffer.write(FALSE_STRING); return; case FLOAT : generateFloat(context, session, (RubyFloat) object, buffer); return; - case FIXNUM : generateFixnum(session, (RubyFixnum) object, buffer); return; - case BIGNUM : generateBignum((RubyBignum) object, buffer); return; + case FIXNUM : generateFixnum(context, session, (RubyFixnum) object, buffer); return; + case BIGNUM : generateBignum(context, session, (RubyBignum) object, buffer); return; case SYMBOL : generateSymbol(context, session, (RubySymbol) object, buffer); return; case STRING : if (Helpers.metaclass(object) != context.runtime.getString()) break; @@ -200,7 +250,7 @@ public StringEncoder getStringEncoder(ThreadContext context) { GeneratorState state = getState(context); stringEncoder = state.asciiOnly() ? new StringEncoderAsciiOnly(state.scriptSafe()) : - new StringEncoder(state.scriptSafe()); + (state.scriptSafe()) ? new StringEncoder(state.scriptSafe()) : StringEncoder.createBasicEncoder(); } return stringEncoder; } @@ -220,7 +270,7 @@ int guessSize(ThreadContext context, Session session, T object) { } RubyString generateNew(ThreadContext context, Session session, T object) { - ByteListDirectOutputStream buffer = new ByteListDirectOutputStream(guessSize(context, session, object)); + AbstractByteListDirectOutputStream buffer = AbstractByteListDirectOutputStream.create(guessSize(context, session, object)); generateToBuffer(context, session, object, buffer); return RubyString.newString(context.runtime, buffer.toByteListDirect(UTF8Encoding.INSTANCE)); } @@ -297,11 +347,15 @@ void generate(ThreadContext context, Session session, T object, OutputStream buf private static class BignumHandler extends Handler { @Override void generate(ThreadContext context, Session session, RubyBignum object, OutputStream buffer) throws IOException { - generateBignum(object, buffer); + generateBignum(context, session, object, buffer); } } - private static void generateBignum(RubyBignum object, OutputStream buffer) throws IOException { + private static void generateBignum(ThreadContext context, Session session, RubyBignum object, OutputStream buffer) throws IOException { + if (session.getState(context).rfc8785()) { + generateRfc8785Number(context, object, buffer); + return; + } BigInteger bigInt = object.getValue(); buffer.write(bigInt.toString().getBytes(UTF_8)); } @@ -309,11 +363,15 @@ private static void generateBignum(RubyBignum object, OutputStream buffer) throw private static class FixnumHandler extends Handler { @Override void generate(ThreadContext context, Session session, RubyFixnum object, OutputStream buffer) throws IOException { - generateFixnum(session, object, buffer); + generateFixnum(context, session, object, buffer); } } - static void generateFixnum(Session session, RubyFixnum object, OutputStream buffer) throws IOException { + static void generateFixnum(ThreadContext context, Session session, RubyFixnum object, OutputStream buffer) throws IOException { + if (session.getState(context).rfc8785()) { + generateRfc8785Number(context, object, buffer); + return; + } long i = object.getLongValue(); if (i == 0) { buffer.write('0'); @@ -355,14 +413,13 @@ void generate(ThreadContext context, Session session, RubyFloat object, OutputSt } static void generateFloat(ThreadContext context, Session session, RubyFloat object, OutputStream buffer) throws IOException { + final GeneratorState state = session.getState(context); double value = object.getValue(); if (Double.isInfinite(value) || Double.isNaN(value)) { - GeneratorState state = session.getState(context); - if (!state.allowNaN()) { if (state.strict() && state.getAsJSON() != null) { - IRubyObject castedValue = state.getAsJSON().call(context, object); + IRubyObject castedValue = state.getAsJSON().call(context, object, context.getRuntime().getFalse()); if (castedValue != object) { getHandlerFor(context.runtime, castedValue).generate(context, session, castedValue, buffer); return; @@ -373,7 +430,17 @@ static void generateFloat(ThreadContext context, Session session, RubyFloat obje } } - buffer.write(Double.toString(value).getBytes(UTF_8)); + if (state.rfc8785()) { + generateRfc8785Number(context, object, buffer); + } else { + buffer.write(Double.toString(value).getBytes(UTF_8)); + } + } + + private static void generateRfc8785Number(ThreadContext context, IRubyObject object, OutputStream buffer) throws IOException { + RubyProc numberProc = (RubyProc)GeneratorState.rfc8785NumberFormatterProc; + RubyString numberString = (RubyString)Helpers.invoke(context, numberProc, "call", object); + buffer.write(numberString.toString().getBytes(UTF_8)); } private static final byte[] EMPTY_ARRAY_BYTES = "[]".getBytes(); @@ -428,10 +495,10 @@ static void generateArray(ThreadContext context, Session session, RubyArray) object.directEntrySet()) { + if (firstPair) { + tracker.trackFirst(context, session, (IRubyObject)entry.getKey()); + } else { + tracker.track(context, session, (IRubyObject)entry.getKey()); + } processEntry(context, session, buffer, entry, firstPair, objectNl, indent, spaceBefore, space); firstPair = false; } @@ -488,7 +623,33 @@ static void generateHash(ThreadContext context, Session session, RubyHash object buffer.write('}'); } + private static IRubyObject castKey(ThreadContext context, IRubyObject key) { + RubyClass keyClass = key.getType(); + Ruby runtime = context.runtime; + + if (key instanceof RubyString) { + if (keyClass == runtime.getString()) { + return key; + } else { + // C: convert_string_subclass + IRubyObject keyStr = key.callMethod(context, "to_s"); + if (!(keyStr instanceof RubyString)) { + IRubyObject className = keyClass.getRealClass().callMethod(context, "to_s"); + throw runtime.newTypeError("can't convert " + className + " to String (" + + className + "#to_s gives " + keyStr.getMetaClass().getRealClass().callMethod(context, "to_s") + ")"); + } + return keyStr; + } + } else if (keyClass == runtime.getSymbol()) { + return ((RubySymbol) key).id2name(context); + } else { + return null; + } + } + private static void processEntry(ThreadContext context, Session session, OutputStream buffer, RubyHash.RubyHashEntry entry, boolean firstPair, ByteList objectNl, byte[] indent, ByteList spaceBefore, ByteList space) { + StringEncoder encoder = session.getStringEncoder(context); + IRubyObject key = (IRubyObject) entry.getKey(); IRubyObject value = (IRubyObject) entry.getValue(); @@ -501,18 +662,22 @@ private static void processEntry(ThreadContext context, Session session, OutputS Ruby runtime = context.runtime; - IRubyObject keyStr; - RubyClass keyClass = key.getType(); - if (key instanceof RubyString) { - if (keyClass == runtime.getString()) { - keyStr = key; - } else { - keyStr = key.callMethod(context, "to_s"); + IRubyObject keyStr = castKey(context, key); + if (keyStr == null || !(keyStr instanceof RubyString) || !encoder.hasValidEncoding((RubyString)keyStr)) { + GeneratorState state = session.getState(context); + if (state.strict()) { + if (state.getAsJSON() != null) { + key = state.getAsJSON().call(context, key, context.getRuntime().getTrue()); + keyStr = castKey(context, key); + } + + if (keyStr == null) { + throw Utils.buildGeneratorError(context, key, key.getType().name(context) + " not allowed as object key in JSON").toThrowable(); + } + } + else { + keyStr = TypeConverter.convertToType(key, runtime.getString(), "to_s"); } - } else if (keyClass == runtime.getSymbol()) { - keyStr = ((RubySymbol) key).id2name(context); - } else { - keyStr = TypeConverter.convertToType(key, runtime.getString(), "to_s"); } if (keyStr.getMetaClass() == runtime.getString()) { @@ -543,6 +708,19 @@ int guessSize(ThreadContext context, Session session, RubyString object) { @Override void generate(ThreadContext context, Session session, RubyString object, OutputStream buffer) throws IOException { + GeneratorState state = session.getState(context); + StringEncoder encoder = session.getStringEncoder(context); + + if (state.strict() && !encoder.hasValidEncoding(object) && state.getAsJSON() != null) { + IRubyObject value = state.getAsJSON().call(context, object, context.getRuntime().getFalse()); + if (value instanceof RubyString) { + object = (RubyString)value; + } else { + Handler handler = getHandlerFor(context.runtime, value); + handler.generate(context, session, value, buffer); + return; + } + } generateString(context, session, object, buffer); } } @@ -572,6 +750,7 @@ static RubyString generateFragmentNew(ThreadContext context, Session session, IR static void generateFragment(ThreadContext context, Session session, IRubyObject object, OutputStream buffer) throws IOException { RubyString result = generateFragmentNew(context, session, object); + if (buffer instanceof BufferedOutputStream) result = result.newFrozen(); ByteList bytes = result.getByteList(); buffer.write(bytes.unsafeBytes(), bytes.begin(), bytes.length()); } @@ -638,8 +817,8 @@ void generate(ThreadContext context, Session session, IRubyObject object, Output static RubyString generateGenericNew(ThreadContext context, Session session, IRubyObject object) { GeneratorState state = session.getState(context); if (state.strict()) { - if (state.getAsJSON() != null ) { - IRubyObject value = state.getAsJSON().call(context, object); + if (state.getAsJSON() != null) { + IRubyObject value = state.getAsJSON().call(context, object, context.getRuntime().getFalse()); Handler handler = getHandlerFor(context.runtime, value); if (handler == GENERIC_HANDLER) { throw Utils.buildGeneratorError(context, object, value + " returned by as_json not allowed in JSON").toThrowable(); @@ -648,7 +827,13 @@ static RubyString generateGenericNew(ThreadContext context, Session session, IRu } throw Utils.buildGeneratorError(context, object, object + " not allowed in JSON").toThrowable(); } else if (object.respondsTo("to_json")) { - IRubyObject result = object.callMethod(context, "to_json", state); + int depth = state.depth; + IRubyObject result; + try { + result = object.callMethod(context, "to_json", state); + } finally { + state.depth = depth; + } if (result instanceof RubyString) return (RubyString)result; throw context.runtime.newTypeError("to_json must return a String"); } else { @@ -658,6 +843,7 @@ static RubyString generateGenericNew(ThreadContext context, Session session, IRu static void generateGeneric(ThreadContext context, Session session, IRubyObject object, OutputStream buffer) throws IOException { RubyString result = generateGenericNew(context, session, object); + if (buffer instanceof BufferedOutputStream) result = result.newFrozen(); ByteList bytes = result.getByteList(); buffer.write(bytes.unsafeBytes(), bytes.begin(), bytes.length()); } diff --git a/java/src/json/ext/GeneratorMethods.java b/java/src/json/ext/GeneratorMethods.java index eb31b9efc..3019ae091 100644 --- a/java/src/json/ext/GeneratorMethods.java +++ b/java/src/json/ext/GeneratorMethods.java @@ -43,9 +43,6 @@ static void populate(RuntimeInfo info, RubyModule module) { defineMethods(module, "Object", RbObject.class); defineMethods(module, "String", RbString.class); defineMethods(module, "TrueClass", RbTrue.class); - - RubyModule stringExtend = module.defineModuleUnder("String").defineModuleUnder("Extend"); - stringExtend.defineAnnotatedMethods(StringExtend.class); } /** @@ -118,95 +115,6 @@ public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf) { public static IRubyObject to_json(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { return Generator.generateJson(context, (RubyString)vSelf, Generator.STRING_HANDLER, arg0); } - - /** - * {@link RubyString String}#to_json_raw(*) - * - *

This method creates a JSON text from the result of a call to - * {@link #to_json_raw_object} of this String. - */ - @JRubyMethod - public static IRubyObject to_json_raw(ThreadContext context, IRubyObject vSelf) { - RubyHash obj = toJsonRawObject(context, Utils.ensureString(vSelf)); - return Generator.generateJson(context, obj, Generator.HASH_HANDLER); - } - - @JRubyMethod - public static IRubyObject to_json_raw(ThreadContext context, IRubyObject vSelf, IRubyObject arg0) { - RubyHash obj = toJsonRawObject(context, Utils.ensureString(vSelf)); - return Generator.generateJson(context, obj, Generator.HASH_HANDLER, arg0); - } - - /** - * {@link RubyString String}#to_json_raw_object(*) - * - *

This method creates a raw object Hash, that can be nested into - * other data structures and will be unparsed as a raw string. This - * method should be used if you want to convert raw strings to JSON - * instead of UTF-8 strings, e.g. binary data. - */ - @JRubyMethod - public static IRubyObject to_json_raw_object(ThreadContext context, IRubyObject vSelf) { - return toJsonRawObject(context, Utils.ensureString(vSelf)); - } - - private static RubyHash toJsonRawObject(ThreadContext context, - RubyString self) { - Ruby runtime = context.runtime; - RubyHash result = RubyHash.newHash(runtime); - - IRubyObject createId = RuntimeInfo.forRuntime(runtime) - .jsonModule.get().callMethod(context, "create_id"); - result.op_aset(context, createId, self.getMetaClass().to_s()); - - ByteList bl = self.getByteList(); - byte[] uBytes = bl.unsafeBytes(); - RubyArray array = runtime.newArray(bl.length()); - for (int i = bl.begin(), t = bl.begin() + bl.length(); i < t; i++) { - array.store(i, runtime.newFixnum(uBytes[i] & 0xff)); - } - - result.op_aset(context, runtime.newString("raw"), array); - return result; - } - - @JRubyMethod(module=true) - public static IRubyObject included(ThreadContext context, IRubyObject extendModule, IRubyObject module) { - RuntimeInfo info = RuntimeInfo.forRuntime(context.runtime); - return module.callMethod(context, "extend", ((RubyModule) extendModule).getConstant("Extend")); - } - } - - public static class StringExtend { - /** - * {@link RubyString String}#json_create(o) - * - *

Raw Strings are JSON Objects (the raw bytes are stored in an - * array for the key "raw"). The Ruby String can be created by this - * module method. - */ - @JRubyMethod - public static IRubyObject json_create(ThreadContext context, - IRubyObject vSelf, IRubyObject vHash) { - Ruby runtime = context.runtime; - RubyHash o = vHash.convertToHash(); - IRubyObject rawData = o.fastARef(runtime.newString("raw")); - if (rawData == null) { - throw runtime.newArgumentError("\"raw\" value not defined " - + "for encoded String"); - } - RubyArray ary = Utils.ensureArray(rawData); - byte[] bytes = new byte[ary.getLength()]; - for (int i = 0, t = ary.getLength(); i < t; i++) { - IRubyObject element = ary.eltInternal(i); - if (element instanceof RubyFixnum) { - bytes[i] = (byte)RubyNumeric.fix2long(element); - } else { - throw runtime.newTypeError(element, runtime.getFixnum()); - } - } - return runtime.newString(new ByteList(bytes, false)); - } } public static class RbTrue { diff --git a/java/src/json/ext/GeneratorState.java b/java/src/json/ext/GeneratorState.java index 524d6b057..a0f3dfc21 100644 --- a/java/src/json/ext/GeneratorState.java +++ b/java/src/json/ext/GeneratorState.java @@ -34,6 +34,12 @@ * @author mernen */ public class GeneratorState extends RubyObject { + private boolean allowDuplicateKey = false; + + private static IRubyObject defaultSortKeyProc; + public static IRubyObject rfc8785NumberFormatterProc; + public static IRubyObject rfc8785SortKeysProc; + /** * The indenting unit string. Will be repeated several times for larger * indenting levels. @@ -82,12 +88,6 @@ public class GeneratorState extends RubyObject { */ private boolean asciiOnly = DEFAULT_ASCII_ONLY; static final boolean DEFAULT_ASCII_ONLY = false; - /** - * If set to true all JSON values generated might not be - * RFC-conform JSON documents. - */ - private boolean quirksMode = DEFAULT_QUIRKS_MODE; - static final boolean DEFAULT_QUIRKS_MODE = false; /** * If set to true the forward slash will be escaped in * json output. @@ -107,10 +107,19 @@ public class GeneratorState extends RubyObject { private int bufferInitialLength = DEFAULT_BUFFER_INITIAL_LENGTH; static final int DEFAULT_BUFFER_INITIAL_LENGTH = 1024; + /** + * Controls key sorting when generating JSON. null means keys + * are emitted in insertion order; a true value sorts keys lexicographically; + * a {@link RubyProc} is used as a comparator receiving two [key, value] pairs. + */ + private IRubyObject sortKeys; + + private boolean rfc8785 = false; + /** * The current depth (inside a #to_json call) */ - private int depth = 0; + protected int depth = 0; static final ObjectAllocator ALLOCATOR = GeneratorState::new; @@ -158,8 +167,25 @@ static GeneratorState fromState(ThreadContext context, RuntimeInfo info, } } - // for other values, return the safe prototype - return (GeneratorState)info.getSafeStatePrototype(context).dup(); + return (GeneratorState)klass.newInstance(context, context.nil); + } + + @JRubyMethod(meta=true, name="default_sort_keys_proc=") + public static IRubyObject setDefaultSortKeyProc(IRubyObject klass, IRubyObject proc) { + defaultSortKeyProc = proc; + return proc; + } + + @JRubyMethod(meta=true, name="rfc8785_number_formatter_proc=") + public static IRubyObject setRfc8785NumberFormatterProc(IRubyObject klass, IRubyObject proc) { + rfc8785NumberFormatterProc = proc; + return proc; + } + + @JRubyMethod(meta=true, name="rfc8785_sort_keys_proc=") + public static IRubyObject setRfc8785SortKeysProc(IRubyObject klass, IRubyObject proc) { + rfc8785SortKeysProc = proc; + return proc; } /** @@ -219,11 +245,14 @@ public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) { this.maxNesting = orig.maxNesting; this.allowNaN = orig.allowNaN; this.asciiOnly = orig.asciiOnly; - this.quirksMode = orig.quirksMode; this.scriptSafe = orig.scriptSafe; this.strict = orig.strict; this.bufferInitialLength = orig.bufferInitialLength; this.depth = orig.depth; + + this.allowDuplicateKey = orig.allowDuplicateKey; + this.sortKeys = orig.sortKeys; + return this; } @@ -232,7 +261,7 @@ public IRubyObject initialize_copy(ThreadContext context, IRubyObject vOrig) { * the result. If no valid JSON document can be created this method raises * a GeneratorError exception. */ - @JRubyMethod(alias="generate_new") + @JRubyMethod public IRubyObject generate(ThreadContext context, IRubyObject obj, IRubyObject io) { IRubyObject result = Generator.generateJson(context, obj, this, io); RuntimeInfo info = RuntimeInfo.forRuntime(context.runtime); @@ -252,34 +281,11 @@ public IRubyObject generate(ThreadContext context, IRubyObject obj, IRubyObject return resultString; } - @JRubyMethod(alias="generate_new") + @JRubyMethod public IRubyObject generate(ThreadContext context, IRubyObject obj) { return generate(context, obj, context.nil); } - @JRubyMethod(name="[]") - public IRubyObject op_aref(ThreadContext context, IRubyObject vName) { - String name = vName.asJavaString(); - if (getMetaClass().isMethodBound(name, true)) { - return send(context, vName, Block.NULL_BLOCK); - } else { - IRubyObject value = getInstanceVariables().getInstanceVariable("@" + name); - return value == null ? context.nil : value; - } - } - - @JRubyMethod(name="[]=") - public IRubyObject op_aset(ThreadContext context, IRubyObject vName, IRubyObject value) { - String name = vName.asJavaString(); - String nameWriter = name + "="; - if (getMetaClass().isMethodBound(nameWriter, true)) { - return send(context, context.runtime.newString(nameWriter), value, Block.NULL_BLOCK); - } else { - getInstanceVariables().setInstanceVariable("@" + name, value); - } - return context.nil; - } - public ByteList getIndent() { return indent; } @@ -291,6 +297,7 @@ public RubyString indent_get(ThreadContext context) { @JRubyMethod(name="indent=") public IRubyObject indent_set(ThreadContext context, IRubyObject indent) { + checkFrozen(); this.indent = prepareByteList(context, indent); return indent; } @@ -306,6 +313,7 @@ public RubyString space_get(ThreadContext context) { @JRubyMethod(name="space=") public IRubyObject space_set(ThreadContext context, IRubyObject space) { + checkFrozen(); this.space = prepareByteList(context, space); return space; } @@ -322,6 +330,7 @@ public RubyString space_before_get(ThreadContext context) { @JRubyMethod(name="space_before=") public IRubyObject space_before_set(ThreadContext context, IRubyObject spaceBefore) { + checkFrozen(); this.spaceBefore = prepareByteList(context, spaceBefore); return spaceBefore; } @@ -338,6 +347,7 @@ public RubyString object_nl_get(ThreadContext context) { @JRubyMethod(name="object_nl=") public IRubyObject object_nl_set(ThreadContext context, IRubyObject objectNl) { + checkFrozen(); this.objectNl = prepareByteList(context, objectNl); return objectNl; } @@ -354,6 +364,7 @@ public RubyString array_nl_get(ThreadContext context) { @JRubyMethod(name="array_nl=") public IRubyObject array_nl_set(ThreadContext context, IRubyObject arrayNl) { + checkFrozen(); this.arrayNl = prepareByteList(context, arrayNl); return arrayNl; } @@ -369,6 +380,7 @@ public IRubyObject as_json_get(ThreadContext context) { @JRubyMethod(name="as_json=") public IRubyObject as_json_set(ThreadContext context, IRubyObject asJSON) { + checkFrozen(); if (asJSON.isNil() || asJSON == context.getRuntime().getFalse()) { this.asJSON = null; } else { @@ -389,6 +401,7 @@ public RubyInteger max_nesting_get(ThreadContext context) { @JRubyMethod(name="max_nesting=") public IRubyObject max_nesting_set(IRubyObject max_nesting) { + checkFrozen(); maxNesting = RubyNumeric.fix2int(max_nesting); return max_nesting; } @@ -400,18 +413,19 @@ public boolean scriptSafe() { return scriptSafe; } - @JRubyMethod(name="script_safe", alias="escape_slash") + @JRubyMethod(name="script_safe") public RubyBoolean script_safe_get(ThreadContext context) { return RubyBoolean.newBoolean(context, scriptSafe); } - @JRubyMethod(name="script_safe=", alias="escape_slash=") + @JRubyMethod(name="script_safe=") public IRubyObject script_safe_set(IRubyObject script_safe) { + checkFrozen(); scriptSafe = script_safe.isTrue(); return script_safe.getRuntime().newBoolean(scriptSafe); } - @JRubyMethod(name="script_safe?", alias="escape_slash?") + @JRubyMethod(name="script_safe?") public RubyBoolean script_safe_p(ThreadContext context) { return RubyBoolean.newBoolean(context, scriptSafe); } @@ -423,6 +437,23 @@ public boolean strict() { return strict; } + /** + * Returns the proc used to sort the keys of an object, or + * null if keys should not be sorted. The proc receives the + * entire Hash and returns a Hash with its pairs in the desired order. + */ + public RubyProc getSortKeysProc() { + return sortKeys instanceof RubyProc ? (RubyProc) sortKeys : null; + } + + private static IRubyObject normalizeSortKeys(ThreadContext context, IRubyObject value) { + if (value instanceof RubyProc) return value; + if (value != null && value.isTrue()) { + return defaultSortKeyProc; + } + return null; + } + @JRubyMethod(name={"strict","strict?"}) public RubyBoolean strict_get(ThreadContext context) { return RubyBoolean.newBoolean(context, strict); @@ -430,6 +461,7 @@ public RubyBoolean strict_get(ThreadContext context) { @JRubyMethod(name="strict=") public IRubyObject strict_set(IRubyObject isStrict) { + checkFrozen(); strict = isStrict.isTrue(); return isStrict.getRuntime().newBoolean(strict); } @@ -459,11 +491,56 @@ public RubyInteger buffer_initial_length_get(ThreadContext context) { @JRubyMethod(name="buffer_initial_length=") public IRubyObject buffer_initial_length_set(IRubyObject buffer_initial_length) { + checkFrozen(); int newLength = RubyNumeric.fix2int(buffer_initial_length); if (newLength > 0) bufferInitialLength = newLength; return buffer_initial_length; } + @JRubyMethod(name="sort_keys") + public IRubyObject sort_keys_get(ThreadContext context) { + return sortKeys == null ? context.getRuntime().getFalse() : sortKeys; + } + + @JRubyMethod(name="sort_keys=") + public IRubyObject sort_keys_set(ThreadContext context, IRubyObject sortKeys) { + checkFrozen(); + this.sortKeys = normalizeSortKeys(context, sortKeys); + return sortKeys; + } + + @JRubyMethod(name="rfc8785?") + public IRubyObject rfc8785_p(ThreadContext context) { + return RubyBoolean.newBoolean(context, rfc8785); + } + + @JRubyMethod(name="rfc8785=") + public IRubyObject rfc8785_set(ThreadContext context, IRubyObject rfc8785) { + checkFrozen(); + this.rfc8785 = rfc8785.isTrue(); + return rfc8785; + } + + public boolean rfc8785() { + return this.rfc8785; + } + + public void validateRfc8785(ThreadContext context) { + if (!rfc8785) return; + + String option = !indent.isEmpty() ? "indent" : + !space.isEmpty() ? "space" : + !spaceBefore.isEmpty() ? "space_before" : + !objectNl.isEmpty() ? "object_nl" : + !arrayNl.isEmpty() ? "array_nl" : + asciiOnly ? "ascii_only" : + scriptSafe ? "script_safe" : + allowNaN ? "allow_nan" : null; + if (option != null) { + throw context.runtime.newArgumentError(option + " cannot be used with rfc8785"); + } + } + public int getDepth() { return depth; } @@ -474,8 +551,12 @@ public RubyInteger depth_get(ThreadContext context) { } @JRubyMethod(name="depth=") - public IRubyObject depth_set(IRubyObject vDepth) { + public IRubyObject depth_set(ThreadContext context, IRubyObject vDepth) { + checkFrozen(); depth = RubyNumeric.fix2int(vDepth); + if (depth < 0) { + throw context.runtime.newArgumentError("depth must be >= 0 (got: " + depth + ")"); + } return vDepth; } @@ -487,6 +568,18 @@ private ByteList prepareByteList(ThreadContext context, IRubyObject value) { return str.getByteList().dup(); } + @JRubyMethod(name="allow_duplicate_key?", visibility=Visibility.PRIVATE) + public IRubyObject allow_duplicate_key_p(ThreadContext context) { + if (allowDuplicateKey) { + return context.runtime.getTrue(); + } + return context.runtime.getFalse(); + } + + public boolean getAllowDuplicateKey() { + return allowDuplicateKey; + } + /** * State#configure(opts) * @@ -497,40 +590,47 @@ private ByteList prepareByteList(ThreadContext context, IRubyObject value) { */ @JRubyMethod(visibility=Visibility.PRIVATE) public IRubyObject _configure(ThreadContext context, IRubyObject vOpts) { + checkFrozen(); OptionsReader opts = new OptionsReader(context, vOpts); - ByteList indent = opts.getString("indent"); - if (indent != null) this.indent = indent; + this.indent = stringConfig(opts, "indent", this.indent); + this.space = stringConfig(opts, "space", this.space); + this.spaceBefore = stringConfig(opts, "space_before", this.spaceBefore); + this.arrayNl = stringConfig(opts, "array_nl", this.arrayNl); + this.objectNl = stringConfig(opts, "object_nl", this.objectNl); - ByteList space = opts.getString("space"); - if (space != null) this.space = space; + if (opts.hasKey("as_json")) this.asJSON = opts.getProc("as_json"); - ByteList spaceBefore = opts.getString("space_before"); - if (spaceBefore != null) this.spaceBefore = spaceBefore; + maxNesting = opts.getInt("max_nesting", maxNesting); + allowNaN = opts.getBool("allow_nan", allowNaN); + asciiOnly = opts.getBool("ascii_only", asciiOnly); + scriptSafe = opts.getBool("script_safe", scriptSafe); + strict = opts.getBool("strict", strict); + bufferInitialLength = opts.getInt("buffer_initial_length", bufferInitialLength); - ByteList arrayNl = opts.getString("array_nl"); - if (arrayNl != null) this.arrayNl = arrayNl; - - this.asJSON = opts.getProc("as_json"); + depth = opts.getInt("depth", depth); + if (depth < 0) { + throw context.runtime.newArgumentError("depth must be >= 0 (got: " + depth + ")"); + } + this.allowDuplicateKey = opts.getBool("allow_duplicate_key", allowDuplicateKey); - ByteList objectNl = opts.getString("object_nl"); - if (objectNl != null) this.objectNl = objectNl; + if (opts.hasKey("sort_keys")) sortKeys = normalizeSortKeys(context, opts.get("sort_keys")); - maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); - allowNaN = opts.getBool("allow_nan", DEFAULT_ALLOW_NAN); - asciiOnly = opts.getBool("ascii_only", DEFAULT_ASCII_ONLY); - scriptSafe = opts.getBool("script_safe", DEFAULT_SCRIPT_SAFE); - if (!scriptSafe) { - scriptSafe = opts.getBool("escape_slash", DEFAULT_SCRIPT_SAFE); - } - strict = opts.getBool("strict", DEFAULT_STRICT); - bufferInitialLength = opts.getInt("buffer_initial_length", DEFAULT_BUFFER_INITIAL_LENGTH); + rfc8785 = opts.getBool("rfc8785", rfc8785); - depth = opts.getInt("depth", 0); + opts.ensureEmpty(); + validateRfc8785(context); return this; } + // A falsy value writes the empty string, as string_config() does in the C extension. + private static ByteList stringConfig(OptionsReader opts, String key, ByteList current) { + if (!opts.hasKey(key)) return current; + ByteList value = opts.getString(key); + return value == null ? ByteList.EMPTY_BYTELIST : value; + } + /** * State#to_h() * @@ -556,6 +656,9 @@ public RubyHash to_h(ThreadContext context) { result.op_aset(context, runtime.newSymbol("strict"), strict_get(context)); result.op_aset(context, runtime.newSymbol("depth"), depth_get(context)); result.op_aset(context, runtime.newSymbol("buffer_initial_length"), buffer_initial_length_get(context)); + result.op_aset(context, runtime.newSymbol("sort_keys"), sort_keys_get(context)); + result.op_aset(context, runtime.newSymbol("allow_duplicate_key"), allow_duplicate_key_p(context)); + for (String name: getInstanceVariableNameList()) { result.op_aset(context, runtime.newSymbol(name.substring(1)), getInstanceVariables().getInstanceVariable(name)); } @@ -563,9 +666,8 @@ public RubyHash to_h(ThreadContext context) { } public int increaseDepth(ThreadContext context) { - depth++; checkMaxNesting(context); - return depth; + return ++depth; } public int decreaseDepth() { @@ -573,13 +675,12 @@ public int decreaseDepth() { } /** - * Checks if the current depth is allowed as per this state's options. + * Checks if increasing the depth is allowed as per this state's options. * @param context The current context */ private void checkMaxNesting(ThreadContext context) { - if (maxNesting != 0 && depth > maxNesting) { - depth--; - throw Utils.newException(context, Utils.M_NESTING_ERROR, "nesting of " + depth + " is too deep"); + if (maxNesting != 0 && depth >= maxNesting) { + throw Utils.newException(context, Utils.M_NESTING_ERROR, "nesting of " + (depth + 1) + " is too deep. Did you try to serialize objects with circular references?"); } } } diff --git a/java/src/json/ext/OptionsReader.java b/java/src/json/ext/OptionsReader.java index c372eb1a8..d93d3cd79 100644 --- a/java/src/json/ext/OptionsReader.java +++ b/java/src/json/ext/OptionsReader.java @@ -26,15 +26,19 @@ final class OptionsReader { OptionsReader(ThreadContext context, IRubyObject vOpts) { this.context = context; this.runtime = context.runtime; + + RubyHash options; if (vOpts == null || vOpts.isNil()) { - opts = null; + options = null; } else if (vOpts.respondsTo("to_hash")) { - opts = vOpts.convertToHash(); + options = vOpts.convertToHash(); } else if (vOpts.respondsTo("to_h")) { - opts = vOpts.callMethod(context, "to_h").convertToHash(); + options = vOpts.callMethod(context, "to_h").convertToHash(); } else { - opts = vOpts.convertToHash(); /* Should just raise the correct TypeError */ + options = vOpts.convertToHash(); /* Should just raise the correct TypeError */ } + + this.opts = options == null ? null : (RubyHash)options.dup(); } private RuntimeInfo getRuntimeInfo() { @@ -43,6 +47,19 @@ private RuntimeInfo getRuntimeInfo() { return info; } + + void ensureEmpty() { + if (opts == null) return; + + if (opts.size() == 1) { + throw context.runtime.newArgumentError("unknown keyword: " + opts.keys().first()); + } + + if (opts.size() > 1) { + throw context.runtime.newArgumentError("unknown keywords: " + opts.keys().join(context, runtime.newString(", "))); + } + } + /** * Efficiently looks up items with a {@link org.jruby.RubySymbol Symbol} key * @param key The Symbol name to look up for @@ -50,7 +67,17 @@ private RuntimeInfo getRuntimeInfo() { * if not found */ IRubyObject get(String key) { - return opts == null ? null : opts.fastARef(runtime.newSymbol(key)); + if (opts == null) { + return null; + } + return opts.delete(runtime.newSymbol(key)); + } + + boolean hasKey(String key) { + if (opts == null) { + return false; + } + return opts.hasKey(runtime.newSymbol(key)); } boolean getBool(String key, boolean defaultValue) { diff --git a/java/src/json/ext/Parser.java b/java/src/json/ext/Parser.java new file mode 100644 index 000000000..eb5ce3a9a --- /dev/null +++ b/java/src/json/ext/Parser.java @@ -0,0 +1,977 @@ +package json.ext; + +import org.jcodings.Encoding; +import org.jcodings.specific.ASCIIEncoding; +import org.jcodings.specific.UTF8Encoding; +import org.jruby.Ruby; +import org.jruby.RubyArray; +import org.jruby.RubyClass; +import org.jruby.RubyException; +import org.jruby.RubyFloat; +import org.jruby.RubyHash; +import org.jruby.RubyObject; +import org.jruby.RubyProc; +import org.jruby.RubyString; +import org.jruby.RubySymbol; +import org.jruby.anno.JRubyMethod; +import org.jruby.exceptions.RaiseException; +import org.jruby.runtime.Block; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.Visibility; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.util.ByteList; +import org.jruby.util.ConvertBytes; +import org.jruby.util.StringSupport; +import org.jruby.util.ConvertDouble.DoubleConverter; +import org.jruby.util.ConvertBytes; +import org.jruby.util.ConvertDouble.DoubleConverter; +import org.jruby.util.ConvertBytes; +import org.jruby.util.ConvertDouble.DoubleConverter; +import org.jruby.util.ConvertBytes; +import org.jruby.util.ConvertDouble.DoubleConverter; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.function.BiFunction; + +import static json.ext.Ryu.ryuS2dFromParts; +import static org.jruby.util.ConvertDouble.DoubleConverter; + +/** + * This is a port of the parser from the C extension. + */ +public class Parser extends RubyObject { + private final RuntimeInfo info; + private int maxNesting; + private boolean allowNaN; + private boolean allowTrailingComma; + private boolean allowComments; + private boolean allowControlCharacters; + private boolean allowInvalidEscape; + private boolean allowDuplicateKey; + private boolean symbolizeNames; + private boolean freeze; + private RubyProc onLoadProc; + private RubyClass decimalClass; + BiFunction decimalFactory; + + private static final int DEFAULT_MAX_NESTING = 100; + + // Maximum number of deprecation warnings emitted per parse, to avoid + // flooding output on pathological inputs. + private static final int MAX_DEPRECATIONS = 5; + + // constant names in the JSON module containing those values + private static final String CONST_NAN = "NaN"; + private static final String CONST_INFINITY = "Infinity"; + private static final String CONST_MINUS_INFINITY = "MinusInfinity"; + + private static final String VALIDATE_UTF8_STRINGS = "jruby.json.validateUTF8Strings"; + private static final String VALIDATE_UTF8_STRINGS_DEFAULT = "true"; + private static final boolean validateUTF8Strings; + + static { + String enable = System.getProperty(VALIDATE_UTF8_STRINGS, VALIDATE_UTF8_STRINGS_DEFAULT); + validateUTF8Strings = "true".equalsIgnoreCase(enable) || "1".equals(enable); + } + + static final ObjectAllocator ALLOCATOR = Parser::new; + + public Parser(Ruby runtime, RubyClass metaClass) { + super(runtime, metaClass); + info = RuntimeInfo.forRuntime(runtime); + } + + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, Block block) { + Parser config = (Parser)((RubyClass)clazz).allocate(); + config.callInit(arg0, block); + return config; + } + + @JRubyMethod(name = "new", meta = true) + public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, IRubyObject arg1, Block block) { + Parser config = (Parser)((RubyClass)clazz).allocate(); + config.callInit(arg0, arg1, block); + return config; + } + + @JRubyMethod(visibility = Visibility.PRIVATE) + public IRubyObject initialize(ThreadContext context, IRubyObject options) { + checkFrozen(); + Ruby runtime = context.runtime; + + OptionsReader opts = new OptionsReader(context, options); + this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); + this.allowNaN = opts.getBool("allow_nan", false); + this.allowComments = opts.getBool("allow_comments", false); + this.allowControlCharacters = opts.getBool("allow_control_characters", false); + this.allowInvalidEscape = opts.getBool("allow_invalid_escape", false); + this.allowTrailingComma = opts.getBool("allow_trailing_comma", false); + this.symbolizeNames = opts.getBool("symbolize_names", false); + this.allowDuplicateKey = opts.getBool("allow_duplicate_key", false); + + this.freeze = opts.getBool("freeze", false); + this.onLoadProc = opts.getProc("on_load"); + + this.decimalClass = opts.getClass("decimal_class", null); + + if (decimalClass == null) { + this.decimalFactory = this::createFloat; + } else if (decimalClass == runtime.getClass("BigDecimal")) { + this.decimalFactory = this::createBigDecimal; + } else { + this.decimalFactory = this::createCustomDecimal; + } + + opts.ensureEmpty(); + + return this; + } + + public IRubyObject onLoad(ThreadContext context, IRubyObject object) { + if (onLoadProc == null) { + return object; + } else { + return onLoadProc.call(context, object); + } + } + + /** + * Checks the given string's encoding. If a non-UTF-8 encoding is detected, + * a converted copy is returned. + * Returns the source string if no conversion is needed. + */ + private RubyString convertEncoding(ThreadContext context, RubyString source) { + Encoding encoding = source.getEncoding(); + if (encoding == ASCIIEncoding.INSTANCE) { + source = (RubyString) source.dup(); + source.setEncoding(UTF8Encoding.INSTANCE); + source.clearCodeRange(); + } else if (encoding != UTF8Encoding.INSTANCE) { + source = (RubyString) source.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); + } + return source; + } + + /** + * Parser#parse() + * + *

Parses the current JSON text source and returns the + * complete data structure as a result. + */ + @JRubyMethod + public IRubyObject parse(ThreadContext context, IRubyObject source) { + // Like rb_str_new_frozen in parser.c: the caller's string becomes shared, so mutating it during parsing + // (e.g. from a decimal_class callback) copies its bytes instead of changing the ones being parsed. + RubyString frozenSource = convertEncoding(context, source.convertToString()).newFrozen(); + return new ParserSession(this, frozenSource, info).parse(context); + } + + private RubyFloat createFloat(final ThreadContext context, final ByteList num) { + return RubyFloat.newFloat(context.runtime, new DoubleConverter().parse(num, true, true)); + } + + private IRubyObject createBigDecimal(final ThreadContext context, final ByteList num) { + final Ruby runtime = context.runtime; + return runtime.getKernel().callMethod(context, "BigDecimal", runtime.newString(num)); + } + + private IRubyObject createCustomDecimal(final ThreadContext context, final ByteList num) { + return decimalClass.newInstance(context, context.runtime.newString(num), Block.NULL_BLOCK); + } + + /** + * A single parsing session over one source string. + * + *

Once a ParserSession is instantiated, the source string should not + * change until the parsing is complete. The ParserSession object assumes + * the source {@link RubyString} is still associated to its original + * {@link ByteList}, which in turn must still be bound to the same + * byte[] value (and on the same offset). + */ + private static final class ParserSession { + private static final int INITIAL_FRAME_CAPACITY = 32; + private static final int INITIAL_VALUE_CAPACITY = 64; + + // Integers with fewer than this many digits are built directly from a + // long accumulated during the digit scan; longer ones fall back to + // ConvertBytes (Bignum). 17 digits (< 1e17) always fit a signed long, + // including after negation. Mirrors parser.c's MAX_FAST_INTEGER_SIZE. + private static final int MAX_FAST_INTEGER_SIZE = 18; + + // Same idea as the rvalue_cache in the C extension. + // + // This is bigger than the C implementation as this is + // heap allocated. + private static final int KEY_CACHE_CAPA = 128; + private static final int KEY_CACHE_MAX_ENTRY_LENGTH = 55; + + private static final long SPACES = 0x2020202020202020L; + + private enum FrameType { + ROOT(FramePhase.DONE), + ARRAY(FramePhase.ARRAY_COMMA), + OBJECT(FramePhase.OBJECT_COMMA); + + private final FramePhase nextPhase; + + FrameType(FramePhase nextPhase) { + this.nextPhase = nextPhase; + } + + FramePhase nextPhase() { + return nextPhase; + } + } + + private enum FramePhase { + DONE, + ARRAY_COMMA, + OBJECT_COMMA, + VALUE, + OBJECT_KEY, + OBJECT_COLON + } + + private static final class Frame { + FrameType type; + FramePhase phase; + // The position within the value stack when this frame was created. + int valueStackHead; + // The cursor position when we encountered a '{'. + // Used for error message reporting when decoding an JSON object. + int startCursor; + } + + private final Parser config; + private final RuntimeInfo info; + private final ByteList byteList; + private final ByteList view; + private final byte[] data; + + // Little-endian view over {@link #data}, used by the SWAR string scan + // so that {@link Long#numberOfTrailingZeros} locates the first + // interesting" byte within an 8-byte chunk. + private final ByteBuffer chunks; + private final StringScanner scanner; + private final StringDecoder decoder; + private final int begin; + private final int end; + private int cursor; + + private ThreadContext context; + + // Integer value accumulated by the most recent scanDigits() call. + private long digitsValue; + private int currentNesting = 0; + + private int inArray = 0; + + private final IRubyObject[] keyCache = new IRubyObject[KEY_CACHE_CAPA]; + private int keyCacheLength = 0; + private int emittedDeprecations = 0; + + + private Frame[] frameStack = new Frame[INITIAL_FRAME_CAPACITY]; + private int frameDepth = 0; + + private IRubyObject[] valueStack = new IRubyObject[INITIAL_VALUE_CAPACITY]; + private int valueTop = 0; + + private ParserSession(Parser config, RubyString source, RuntimeInfo info) { + this.config = config; + this.info = info; + this.byteList = source.getByteList(); + this.data = byteList.unsafeBytes(); + this.chunks = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); + this.scanner = StringScanner.getInstance(); + this.view = new ByteList(data, false); + this.begin = byteList.begin(); + this.end = begin + byteList.length(); + this.decoder = new StringDecoder(config.allowControlCharacters, config.allowInvalidEscape); + } + + public IRubyObject parse(ThreadContext context) { + this.context = context; + this.cursor = begin; + pushFrame(FrameType.ROOT, FramePhase.VALUE, 0, -1); + + IRubyObject result = run(); + + // Only trailing whitespace (and comments) may follow the document. + eatWhitespace(); + if (cursor < end) { + throw unexpectedToken(cursor, end); + } + return result; + } + + private IRubyObject run() { + while (true) { + Frame frame = topFrame(); + + switch (frame.phase) { + case DONE: + return valueStack[valueTop - 1]; + + case VALUE: { + eatWhitespace(); + IRubyObject value; + byte c = peek(); + switch (c) { + case 'n': + if (matchKeyword("null")) { value = context.nil; break; } + throw unexpectedToken(cursor, end); + case 't': + if (matchKeyword("true")) { value = context.tru; break; } + throw unexpectedToken(cursor, end); + case 'f': + if (matchKeyword("false")) { value = context.fals; break; } + throw unexpectedToken(cursor, end); + case 'N': + if (config.allowNaN && matchKeyword("NaN")) { + value = getConstant(CONST_NAN); + break; + } + throw unexpectedToken(cursor, end); + case 'I': + if (config.allowNaN && matchKeyword("Infinity")) { + value = getConstant(CONST_INFINITY); + break; + } + throw unexpectedToken(cursor, end); + case '-': + cursor++; + if (config.allowNaN && matchKeyword("Infinity")) { + value = getConstant(CONST_MINUS_INFINITY); + } else { + value = parseNumber(cursor - 1, peek(), true); + } + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + value = parseNumber(cursor, c, false); + break; + case '"': + value = parseString(false); + break; + case '[': { + cursor++; + eatWhitespace(); + if (peek() == ']') { + cursor++; + value = decodeArray(0); + break; + } + currentNesting++; + checkNesting(); + inArray++; + // Phase stays VALUE: the next iteration reads + // the first element. + pushFrame(FrameType.ARRAY, FramePhase.VALUE, valueTop, -1); + continue; + } + case '{': { + int objectStart = cursor; + cursor++; + eatWhitespace(); + if (peek() == '}') { + cursor++; + value = decodeObject(0); + break; + } + currentNesting++; + checkNesting(); + // Phase KEY: the next iteration reads the first key. + pushFrame(FrameType.OBJECT, FramePhase.OBJECT_KEY, valueTop, objectStart); + continue; + } + case 0: + throw parseError("unexpected end of input"); + default: + throw unexpectedToken(cursor, end); + } + + pushValue(value); + valueCompleted(frame); + continue; + } + + case OBJECT_KEY: { + eatWhitespace(); + if (peek() == '"') { + pushValue(parseString(true)); + frame.phase = FramePhase.OBJECT_COLON; + continue; + } + throw unexpectedToken(cursor, end); + } + + case OBJECT_COLON: { + eatWhitespace(); + if (peek() == ':') { + cursor++; + frame.phase = FramePhase.VALUE; + continue; + } + throw unexpectedToken(cursor, end); + } + + case ARRAY_COMMA: { + eatWhitespace(); + byte b = peek(); + if (b == ',') { + cursor++; + if (config.allowTrailingComma) { + eatWhitespace(); + if (peek() == ']') { + // Trailing comma: re-enter COMMA to close. + continue; + } + } + frame.phase = FramePhase.VALUE; + continue; + } else if (b == ']') { + cursor++; + int count = entryCount(frame); + currentNesting--; + inArray--; + popFrame(); + pushValue(decodeArray(count)); + valueCompleted(topFrame()); + continue; + } + throw unexpectedToken(cursor, end); + } + + case OBJECT_COMMA: { + eatWhitespace(); + byte b = peek(); + if (b == ',') { + cursor++; + if (config.allowTrailingComma) { + eatWhitespace(); + if (peek() == '}') { + // Trailing comma: re-enter COMMA to close. + continue; + } + } + frame.phase = FramePhase.OBJECT_KEY; + continue; + } else if (b == '}') { + cursor++; + currentNesting--; + int count = entryCount(frame); + // Temporarily rewind the cursor so a duplicate-key + // error points at the object's opening brace. + int finalCursor = cursor; + cursor = frame.startCursor; + IRubyObject object = decodeObject(count); + cursor = finalCursor; + popFrame(); + pushValue(object); + valueCompleted(topFrame()); + continue; + } + throw unexpectedToken(cursor, end); + } + + default: + throw context.runtime.newRuntimeError("unreachable parser state"); + } + } + } + + private void pushValue(IRubyObject value) { + if (valueTop == valueStack.length) { + valueStack = Arrays.copyOf(valueStack, valueStack.length * 2); + } + valueStack[valueTop++] = config.onLoad(context, value); + } + + private void valueCompleted(Frame frame) { + frame.phase = frame.type.nextPhase(); + } + + private int entryCount(Frame frame) { + return valueTop - frame.valueStackHead; + } + + private Frame topFrame() { + return frameStack[frameDepth - 1]; + } + + private void pushFrame(FrameType type, FramePhase phase, + int valueStackHead, int startCursor) { + if (frameDepth == frameStack.length) { + frameStack = Arrays.copyOf(frameStack, frameStack.length * 2); + } + Frame frame = frameStack[frameDepth]; + if (frame == null) { + frame = new Frame(); + frameStack[frameDepth] = frame; + } + frame.type = type; + frame.phase = phase; + frame.valueStackHead = valueStackHead; + frame.startCursor = startCursor; + frameDepth++; + } + + private void popFrame() { + frameDepth--; + } + + private void checkNesting() { + if (config.maxNesting > 0 && currentNesting > config.maxNesting) { + throw newException(Utils.M_NESTING_ERROR, + "nesting of " + currentNesting + " is too deep"); + } + } + + private IRubyObject decodeArray(int count) { + int base = valueTop - count; + IRubyObject[] elements = new IRubyObject[count]; + System.arraycopy(valueStack, base, elements, 0, count); + valueTop = base; + RubyArray array = RubyArray.newArrayNoCopy(context.runtime, elements); + if (config.freeze) { + array.setFrozen(true); + } + return array; + } + + private IRubyObject decodeObject(int count) { + final Ruby runtime = context.runtime; + int base = valueTop - count; + int limit = valueTop; + RubyHash hash = RubyHash.newHash(runtime); + for (int i = base; i < limit; i += 2) { + // We use RubyHash#fastASet because all object keys have already been + // frozen and deduplicated. + // + // This was significantly faster than RubyHash#op_aset. + hash.fastASet(valueStack[i], valueStack[i + 1]); + } + valueTop = base; + + if (!config.allowDuplicateKey && hash.size() < count / 2) { + onDuplicateKey(findDuplicateKey(base, limit)); + } + + if (config.freeze) { + hash.setFrozen(true); + } + return hash; + } + + private IRubyObject findDuplicateKey(int base, int limit) { + RubyHash seen = RubyHash.newHash(context.runtime); + for (int i = base; i < limit; i += 2) { + int before = seen.size(); + IRubyObject key = valueStack[i]; + seen.fastASetCheckString(context.runtime, key, context.tru); + if (seen.size() == before) { + return key; + } + } + return context.nil; + } + + private void onDuplicateKey(IRubyObject key) { + // Symbol keys are reported by their string form (":a" -> "a") to + // match the C parser's message. + String keyInspect = key.callMethod(context, "to_s") + .callMethod(context, "inspect").asJavaString(); + throw parseError(context.runtime.newString("duplicate key " + keyInspect), key); + } + + private int parseDigits(long value) { + int start = cursor; + byte c = peek(); + for (; c >= '0' && c <= '9'; c = advance()) { + value = value * 10 + (c - '0'); + } + digitsValue = value; + return cursor - start; + } + + private IRubyObject parseNumber(int numberStart, byte first, boolean negative) { + int mantissaDigits = parseDigits(0); + long mantissa = digitsValue; + + if ((first == '0' && mantissaDigits > 1) || mantissaDigits == 0) { + throw unexpectedToken(numberStart, end); + } + + boolean integer = true; + int decimal_point_pos = -1; + + if (peek() == '.') { + integer = false; + decimal_point_pos = mantissaDigits; + cursor++; + int fracDigits = parseDigits(mantissa); + if (fracDigits == 0) { + throw unexpectedToken(numberStart, end); + } + mantissaDigits += fracDigits; + mantissa = digitsValue; + } + + int c = peek(); + long exponent = 0; + if (c == 'e' || c == 'E') { + integer = false; + c = advance(); + boolean negative_exponent = c == '-'; + if (negative_exponent || c == '+') advance(); + + int exponent_digits = parseDigits(0); + long abs_exponent = digitsValue; + if (exponent_digits == 0) { + throw unexpectedToken(numberStart, end); + } + + if (exponent_digits >= 20 || Long.compareUnsigned(abs_exponent, Long.MAX_VALUE) > 0) { + exponent = negative_exponent ? Integer.MIN_VALUE : Integer.MAX_VALUE; + } else { + exponent = negative_exponent ? -abs_exponent : abs_exponent; + } + } + + if (integer) { + if (mantissaDigits < MAX_FAST_INTEGER_SIZE) { + return context.runtime.newFixnum(negative ? -mantissa : mantissa); + } + return ConvertBytes.byteListToInum(context.runtime, absSubSequence(numberStart, cursor), 10, true); + } + + // Adjust exponent based on decimal point position + if (decimal_point_pos >= 0) exponent -= (mantissaDigits - decimal_point_pos); + + return decodeFloat(context, mantissa, mantissaDigits, exponent, negative, numberStart); + } + + IRubyObject decodeFloat(ThreadContext context, long mantissa, int mantissa_digits, long exponent, boolean negative, int start) { + if (config.decimalClass != null) return config.decimalFactory.apply(context, absSubSequence(start, cursor)); + if (exponent >= Integer.MAX_VALUE) return getConstant(negative ? CONST_MINUS_INFINITY : CONST_INFINITY); + if (exponent <= Integer.MIN_VALUE) return RubyFloat.newFloat(context.runtime, negative ? -0.0 : 0.0); + + // Ryu has rounding issues with subnormals around 1e-310 (< 2.225e-308) + if (mantissa_digits > 17 || mantissa_digits + exponent < -307) { + return RubyFloat.newFloat(context.runtime, Double.parseDouble(new String(data, start, cursor - start))); + } + + return RubyFloat.newFloat(context.runtime, ryuS2dFromParts(mantissa, mantissa_digits, (int) exponent, negative)); + } + + private IRubyObject parseString(boolean isName) { + final byte[] data = this.data; + final int contentStart = cursor + 1; // skip opening quote + + long scanned = scanner.scan(data, chunks, contentStart, end, validateUTF8Strings); + final int q = (int) scanned; + if (q < 0) { + throw parseError("unexpected end of input, expected closing \""); + } + + boolean plain = (scanned & StringScanner.PLAIN_BIT) != 0; + boolean isAscii = (scanned & StringScanner.ASCII_BIT) != 0; + + cursor = q + 1; // past closing quote + + // Note: When running multiple read-world benchmarks in the same JVM, + // this seems consistently faster than "if (isName && plain)" + // and only handling the ASCII-only path in the cache. + // + // Note 2: It's important that all object keys are frozen and deduplicated, + // decodeObject relies this. + if (isName) { + // Resolve the key's decoded bytes without building a RubyString + // yet, so a cache hit skips allocation entirely. + byte[] buf; + int off; + int len; + if (plain) { + buf = data; + off = contentStart; + len = q - contentStart; + } else { + ByteList decoded = decodeString(context, byteList, contentStart - begin, q - begin, isAscii); + buf = decoded.getUnsafeBytes(); + off = decoded.begin(); + len = decoded.realSize(); + } + // Same as the C extension. + if (inArray > 0 && len > 0 && len <= KEY_CACHE_MAX_ENTRY_LENGTH && isLetter(buf[off])) { + return cachedKey(buf, off, len); + } + return internedKey(buf, off, len); + } + + RubyString string; + if (plain) { + string = RubyString.newString(context.runtime, data, contentStart, + q - contentStart, UTF8Encoding.INSTANCE); + } else { + ByteList content = decodeString(context, byteList, contentStart - begin, q - begin, isAscii); + string = context.runtime.newString(content); + string.setEncoding(UTF8Encoding.INSTANCE); + string.clearCodeRange(); + } + + if (config.freeze) { + return context.runtime.freezeAndDedupString(string); + } + + return string; + } + + private ByteList decodeString(ThreadContext context, ByteList byteList, int start, int end, boolean isAscii) { + if (isAscii) { + return decoder.decodeNoValidate(context, byteList, start, end, chunks); + } + if (!validateUTF8Strings) { + return decoder.decodeNoValidate(context, byteList, start, end, chunks); + } + return decoder.decode(context, byteList, start, end); + } + + private static boolean isLetter(byte b) { + return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z'); + } + + private IRubyObject internedKey(byte[] buf, int off, int len) { + RubyString string = context.runtime.newString( + new ByteList(buf, off, len, UTF8Encoding.INSTANCE, true)); + if (config.symbolizeNames) { + return string.intern(); + } + string.setFrozen(true); + return context.runtime.freezeAndDedupString(string); + } + + private IRubyObject cachedKey(byte[] buf, int off, int len) { + int low = 0; + int high = keyCacheLength - 1; + while (low <= high) { + int mid = (low + high) >>> 1; + int cmp = compareKey(buf, off, len, keyCache[mid]); + if (cmp == 0) { + return keyCache[mid]; + } else if (cmp > 0) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + IRubyObject key = internedKey(buf, off, len); + if (keyCacheLength < KEY_CACHE_CAPA) { + System.arraycopy(keyCache, low, keyCache, low + 1, keyCacheLength - low); + keyCache[low] = key; + keyCacheLength++; + } + return key; + } + + // Orders by length first, then unsigned byte value + private static int compareKey(byte[] buf, int off, int len, IRubyObject entry) { + ByteList eb = entry instanceof RubySymbol + ? ((RubySymbol) entry).getBytes() + : ((RubyString) entry).getByteList(); + int elen = eb.realSize(); + if (len != elen) { + return len - elen; + } + byte[] ebuf = eb.getUnsafeBytes(); + int ebeg = eb.begin(); + for (int i = 0; i < len; i++) { + int cmp = Byte.toUnsignedInt(buf[off + i]) - Byte.toUnsignedInt(ebuf[ebeg + i]); + if (cmp != 0) { + return cmp; + } + } + return 0; + } + + private byte peek() { + return cursor < end ? data[cursor] : 0; + } + private byte advance() { + cursor++; + return peek(); + } + + private boolean matchKeyword(String keyword) { + int len = keyword.length(); + if (end - cursor < len) { + return false; + } + for (int i = 0; i < len; i++) { + if (data[cursor + i] != (byte) keyword.charAt(i)) { + return false; + } + } + cursor += len; + return true; + } + + private void eatWhitespace() { + while (cursor < end) { + switch (data[cursor]) { + case ' ': + case '\t': + case '\r': + cursor++; + break; + case '\n': + cursor++; + // Same heuristic from the C parser: a newline in + // pretty-printed JSON is almost always followed by a run + // of indentation spaces, so skip them eight at a time. + while (cursor + 8 <= end) { + long x = chunks.getLong(cursor); + if (x == SPACES) { + cursor += 8; + } else { + cursor += Long.numberOfTrailingZeros(x ^ SPACES) >>> 3; + break; + } + } + break; + case '/': + eatComments(); + break; + default: + return; + } + } + } + + private void eatComments() { + if (!config.allowComments) { + throw unexpectedToken(cursor, end); + } + + int start = cursor; + cursor++; // skip '/' + switch (peek()) { + case '/': + cursor++; + while (cursor < end && data[cursor] != '\n') cursor++; + if (cursor < end) cursor++; // consume newline + break; + case '*': + cursor++; + while (true) { + while (cursor < end && data[cursor] != '*') cursor++; + if (cursor >= end) { + throw parseError("unterminated comment, expected closing '*/'"); + } + cursor++; // past '*' + if (peek() == '/') { + cursor++; + break; + } + } + break; + default: + throw unexpectedToken(start, end); + } + } + + /** + * Updates the "view" ByteList with the new offsets and returns it. The + * returned ByteList must be consumed before the next call, since the + * same instance is reused. + */ + private ByteList absSubSequence(int absStart, int absEnd) { + view.setBegin(absStart); + view.setRealSize(absEnd - absStart); + return view; + } + + private IRubyObject getConstant(String name) { + return info.jsonModule.get().getConstant(name); + } + + private long cursorPosition(int position) { + position = Math.min(position, end); + int i = position; + int line = 1; + while (i > begin && data[i - 1] != '\n') { + i--; + } + long column = StringSupport.strLength(UTF8Encoding.INSTANCE, data, i, position) + 1; + while (i > begin) { + if (data[--i] == '\n') { + line++; + } + } + return ((long) line << 32) | column; + } + + private RubyArray jsonPathSegments(IRubyObject duplicateKey) { + Ruby runtime = context.runtime; + RubyArray segments = RubyArray.newArray(runtime); + for (int depth = 1; depth < frameDepth; depth++) { + Frame frame = frameStack[depth]; + boolean innermost = depth == frameDepth - 1; + int childHead = innermost ? valueTop : frameStack[depth + 1].valueStackHead; + int count = childHead - frame.valueStackHead; + + if (frame.type == FrameType.ARRAY) { + segments.append(runtime.newFixnum(frame.phase == FramePhase.ARRAY_COMMA ? count - 1 : count)); + } else if (innermost && duplicateKey != null) { + segments.append(duplicateKey); + } else if ((count & 1) == 1) { + segments.append(valueStack[childHead - 1]); + } else if (frame.phase == FramePhase.OBJECT_COMMA && count >= 2) { + segments.append(valueStack[childHead - 2]); + } else { + break; + } + } + return segments; + } + + private RaiseException parseError(String message) { + return parseError(context.runtime.newString(message), null); + } + + private RaiseException parseError(RubyString message, IRubyObject duplicateKey) { + Ruby runtime = context.runtime; + long position = cursorPosition(cursor); + int line = (int) (position >>> 32); + int column = (int) position; + message.catString(" at line " + line + " column " + column); + RaiseException error = Utils.newException(context, Utils.M_PARSER_ERROR, message); + RubyException exception = error.getException(); + exception.setInstanceVariable("@line", runtime.newFixnum(line)); + exception.setInstanceVariable("@column", runtime.newFixnum(column)); + exception.setInstanceVariable("@json_path", jsonPathSegments(duplicateKey)); + return error; + } + + private RaiseException parsingError(int absStart, int absEnd) { + cursor = absStart; + RubyString msg = context.runtime.newString("unexpected token at '") + .cat(data, absStart, Math.min(absEnd - absStart, 32)) + .cat((byte)'\''); + return parseError(msg, null); + } + + private RaiseException unexpectedToken(int absStart, int absEnd) { + return parsingError(absStart, absEnd); + } + + private RaiseException newException(String className, String message) { + return Utils.newException(context, className, message); + } + } +} diff --git a/java/src/json/ext/ParserConfig.java b/java/src/json/ext/ParserConfig.java deleted file mode 100644 index 6596f97f0..000000000 --- a/java/src/json/ext/ParserConfig.java +++ /dev/null @@ -1,2518 +0,0 @@ - -// line 1 "ParserConfig.rl" -/* - * This code is copyrighted work by Daniel Luz . - * - * Distributed under the Ruby license: https://www.ruby-lang.org/en/about/license.txt - */ -package json.ext; - -import org.jcodings.Encoding; -import org.jcodings.specific.ASCIIEncoding; -import org.jcodings.specific.UTF8Encoding; -import org.jruby.Ruby; -import org.jruby.RubyArray; -import org.jruby.RubyClass; -import org.jruby.RubyFloat; -import org.jruby.RubyHash; -import org.jruby.RubyInteger; -import org.jruby.RubyObject; -import org.jruby.RubyString; -import org.jruby.anno.JRubyMethod; -import org.jruby.exceptions.JumpException; -import org.jruby.exceptions.RaiseException; -import org.jruby.runtime.Block; -import org.jruby.runtime.Helpers; -import org.jruby.runtime.ObjectAllocator; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.Visibility; -import org.jruby.runtime.builtin.IRubyObject; -import org.jruby.util.ByteList; -import org.jruby.util.ConvertBytes; - -import java.util.function.BiFunction; - -import static org.jruby.util.ConvertDouble.DoubleConverter; - -/** - * The JSON::Ext::Parser class. - * - *

This is the JSON parser implemented as a Java class. To use it as the - * standard parser, set - *

JSON.parser = JSON::Ext::Parser
- * This is performed for you when you include "json/ext". - * - *

This class does not perform the actual parsing, just acts as an interface - * to Ruby code. When the {@link #parse(ThreadContext)} method is invoked, a - * ParserConfig.ParserSession object is instantiated, which handles the process. - * - * @author mernen - */ -public class ParserConfig extends RubyObject { - private final RuntimeInfo info; - private RubyString createId; - private boolean createAdditions; - private boolean deprecatedCreateAdditions; - private int maxNesting; - private boolean allowNaN; - private boolean allowTrailingComma; - private boolean symbolizeNames; - private boolean freeze; - private RubyClass objectClass; - private RubyClass arrayClass; - private RubyClass decimalClass; - BiFunction decimalFactory; - private RubyHash match_string; - - private static final int DEFAULT_MAX_NESTING = 100; - - private static final ByteList JSON_MINUS_INFINITY = new ByteList(ByteList.plain("-Infinity")); - // constant names in the JSON module containing those values - private static final String CONST_NAN = "NaN"; - private static final String CONST_INFINITY = "Infinity"; - private static final String CONST_MINUS_INFINITY = "MinusInfinity"; - - static final ObjectAllocator ALLOCATOR = ParserConfig::new; - - /** - * Multiple-value return for internal parser methods. - * - *

All the parseStuff methods return instances of - * ParserResult when successful, or null when - * there's a problem with the input data. - */ - static final class ParserResult { - /** - * The result of the successful parsing. Should never be - * null. - */ - IRubyObject result; - /** - * The point where the parser returned. - */ - int p; - - void update(IRubyObject result, int p) { - this.result = result; - this.p = p; - } - } - - public ParserConfig(Ruby runtime, RubyClass metaClass) { - super(runtime, metaClass); - info = RuntimeInfo.forRuntime(runtime); - } - - /** - * ParserConfig.new(source, opts = {}) - * - *

Creates a new JSON::Ext::Parser instance for the string - * source. - * It will be configured by the opts Hash. - * opts can have the following keys: - *

- *

- *
:max_nesting - *
The maximum depth of nesting allowed in the parsed data - * structures. Disable depth checking with :max_nesting => false|nil|0, - * it defaults to 100. - *

- *

:allow_nan - *
If set to true, allow NaN, - * Infinity and -Infinity in defiance of RFC 4627 - * to be parsed by the Parser. This option defaults to false. - *

- *

:allow_trailing_comma - *
If set to true, allow arrays and objects with a trailing - * comma in defiance of RFC 4627 to be parsed by the Parser. - * This option defaults to false. - *

- *

:symbolize_names - *
If set to true, returns symbols for the names (keys) in - * a JSON object. Otherwise strings are returned, which is also the default. - *

- *

:create_additions - *
If set to false, the Parser doesn't create additions - * even if a matching class and create_id was found. This option - * defaults to true. - *

- *

:object_class - *
Defaults to Hash. If another type is provided, it will be used - * instead of Hash to represent JSON objects. The type must respond to - * new without arguments, and return an object that respond to []=. - *

- *

:array_class - *
Defaults to Array. If another type is provided, it will be used - * instead of Hash to represent JSON arrays. The type must respond to - * new without arguments, and return an object that respond to <<. - *

- *

:decimal_class - *
Specifies which class to use instead of the default (Float) when - * parsing decimal numbers. This class must accept a single string argument - * in its constructor. - *
- */ - - @JRubyMethod(name = "new", meta = true) - public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, Block block) { - ParserConfig config = (ParserConfig)((RubyClass)clazz).allocate(); - - config.callInit(arg0, block); - - return config; - } - - @JRubyMethod(name = "new", meta = true) - public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, IRubyObject arg1, Block block) { - ParserConfig config = (ParserConfig)((RubyClass)clazz).allocate(); - - config.callInit(arg0, arg1, block); - - return config; - } - - @JRubyMethod(visibility = Visibility.PRIVATE) - public IRubyObject initialize(ThreadContext context, IRubyObject options) { - Ruby runtime = context.runtime; - - OptionsReader opts = new OptionsReader(context, options); - this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); - this.allowNaN = opts.getBool("allow_nan", false); - this.allowTrailingComma = opts.getBool("allow_trailing_comma", false); - this.symbolizeNames = opts.getBool("symbolize_names", false); - this.freeze = opts.getBool("freeze", false); - this.createId = opts.getString("create_id", getCreateId(context)); - - IRubyObject additions = opts.get("create_additions"); - this.createAdditions = false; - this.deprecatedCreateAdditions = false; - - if (additions != null) { - if (additions.isNil()) { - this.createAdditions = true; - this.deprecatedCreateAdditions = true; - } else { - this.createAdditions = opts.getBool("create_additions", false); - } - } - - this.objectClass = opts.getClass("object_class", runtime.getHash()); - this.arrayClass = opts.getClass("array_class", runtime.getArray()); - this.decimalClass = opts.getClass("decimal_class", null); - this.match_string = opts.getHash("match_string"); - - if (decimalClass == null) { - this.decimalFactory = this::createFloat; - } else if (decimalClass == runtime.getClass("BigDecimal")) { - this.decimalFactory = this::createBigDecimal; - } else { - this.decimalFactory = this::createCustomDecimal; - } - - if(symbolizeNames && createAdditions) { - throw runtime.newArgumentError("options :symbolize_names and :create_additions cannot be used in conjunction"); - } - - return this; - } - - /** - * Checks the given string's encoding. If a non-UTF-8 encoding is detected, - * a converted copy is returned. - * Returns the source string if no conversion is needed. - */ - private RubyString convertEncoding(ThreadContext context, RubyString source) { - Encoding encoding = source.getEncoding(); - if (encoding == ASCIIEncoding.INSTANCE) { - source = (RubyString) source.dup(); - source.setEncoding(UTF8Encoding.INSTANCE); - source.clearCodeRange(); - } else if (encoding != UTF8Encoding.INSTANCE) { - source = (RubyString) source.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); - } - return source; - } - - /** - * Parser#parse() - * - *

Parses the current JSON text source and returns the - * complete data structure as a result. - */ - @JRubyMethod - public IRubyObject parse(ThreadContext context, IRubyObject source) { - return new ParserSession(this, convertEncoding(context, source.convertToString()), context, info).parse(context); - } - - /** - * Queries JSON.create_id. Returns null if it is - * set to nil or false, and a String if not. - */ - private RubyString getCreateId(ThreadContext context) { - IRubyObject v = info.jsonModule.get().callMethod(context, "create_id"); - return v.isTrue() ? v.convertToString() : null; - } - - private RubyFloat createFloat(final ThreadContext context, final ByteList num) { - return RubyFloat.newFloat(context.runtime, new DoubleConverter().parse(num, true, true)); - } - - private IRubyObject createBigDecimal(final ThreadContext context, final ByteList num) { - final Ruby runtime = context.runtime; - return runtime.getKernel().callMethod(context, "BigDecimal", runtime.newString(num)); - } - - private IRubyObject createCustomDecimal(final ThreadContext context, final ByteList num) { - return decimalClass.newInstance(context, context.runtime.newString(num), Block.NULL_BLOCK); - } - - /** - * A string parsing session. - * - *

Once a ParserSession is instantiated, the source string should not - * change until the parsing is complete. The ParserSession object assumes - * the source {@link RubyString} is still associated to its original - * {@link ByteList}, which in turn must still be bound to the same - * byte[] value (and on the same offset). - */ - // Ragel uses lots of fall-through - @SuppressWarnings("fallthrough") - private static class ParserSession { - private final ParserConfig config; - private final RuntimeInfo info; - private final ByteList byteList; - private final ByteList view; - private final byte[] data; - private final StringDecoder decoder; - private int currentNesting = 0; - - private ParserSession(ParserConfig config, RubyString source, ThreadContext context, RuntimeInfo info) { - this.config = config; - this.info = info; - this.byteList = source.getByteList(); - this.data = byteList.unsafeBytes(); - this.view = new ByteList(data, false); - this.decoder = new StringDecoder(); - } - - private RaiseException unexpectedToken(ThreadContext context, int absStart, int absEnd) { - RubyString msg = context.runtime.newString("unexpected token at '") - .cat(data, absStart, Math.min(absEnd - absStart, 32)) - .cat((byte)'\''); - return newException(context, Utils.M_PARSER_ERROR, msg); - } - - -// line 329 "ParserConfig.rl" - - - -// line 311 "ParserConfig.java" -private static byte[] init__JSON_value_actions_0() -{ - return new byte [] { - 0, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, - 5, 1, 6, 1, 7, 1, 8, 1, 9 - }; -} - -private static final byte _JSON_value_actions[] = init__JSON_value_actions_0(); - - -private static byte[] init__JSON_value_key_offsets_0() -{ - return new byte [] { - 0, 0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 - }; -} - -private static final byte _JSON_value_key_offsets[] = init__JSON_value_key_offsets_0(); - - -private static char[] init__JSON_value_trans_keys_0() -{ - return new char [] { - 34, 45, 73, 78, 91, 102, 110, 116, 123, 48, 57, 110, - 102, 105, 110, 105, 116, 121, 97, 78, 97, 108, 115, 101, - 117, 108, 108, 114, 117, 101, 0 - }; -} - -private static final char _JSON_value_trans_keys[] = init__JSON_value_trans_keys_0(); - - -private static byte[] init__JSON_value_single_lengths_0() -{ - return new byte [] { - 0, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 - }; -} - -private static final byte _JSON_value_single_lengths[] = init__JSON_value_single_lengths_0(); - - -private static byte[] init__JSON_value_range_lengths_0() -{ - return new byte [] { - 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_value_range_lengths[] = init__JSON_value_range_lengths_0(); - - -private static byte[] init__JSON_value_index_offsets_0() -{ - return new byte [] { - 0, 0, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, - 31, 33, 35, 37, 39, 41, 43, 45, 47, 49 - }; -} - -private static final byte _JSON_value_index_offsets[] = init__JSON_value_index_offsets_0(); - - -private static byte[] init__JSON_value_trans_targs_0() -{ - return new byte [] { - 21, 21, 2, 9, 21, 11, 15, 18, 21, 21, 0, 3, - 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 21, - 0, 10, 0, 21, 0, 12, 0, 13, 0, 14, 0, 21, - 0, 16, 0, 17, 0, 21, 0, 19, 0, 20, 0, 21, - 0, 0, 0 - }; -} - -private static final byte _JSON_value_trans_targs[] = init__JSON_value_trans_targs_0(); - - -private static byte[] init__JSON_value_trans_actions_0() -{ - return new byte [] { - 13, 11, 0, 0, 15, 0, 0, 0, 17, 11, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, - 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 3, - 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 5, - 0, 0, 0 - }; -} - -private static final byte _JSON_value_trans_actions[] = init__JSON_value_trans_actions_0(); - - -private static byte[] init__JSON_value_from_state_actions_0() -{ - return new byte [] { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 19 - }; -} - -private static final byte _JSON_value_from_state_actions[] = init__JSON_value_from_state_actions_0(); - - -static final int JSON_value_start = 1; -static final int JSON_value_first_final = 21; -static final int JSON_value_error = 0; - -static final int JSON_value_en_main = 1; - - -// line 435 "ParserConfig.rl" - - - void parseValue(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - IRubyObject result = null; - - -// line 433 "ParserConfig.java" - { - cs = JSON_value_start; - } - -// line 442 "ParserConfig.rl" - -// line 440 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _acts = _JSON_value_from_state_actions[cs]; - _nacts = (int) _JSON_value_actions[_acts++]; - while ( _nacts-- > 0 ) { - switch ( _JSON_value_actions[_acts++] ) { - case 9: -// line 420 "ParserConfig.rl" - { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; -// line 472 "ParserConfig.java" - } - } - - _match: do { - _keys = _JSON_value_key_offsets[cs]; - _trans = _JSON_value_index_offsets[cs]; - _klen = _JSON_value_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( data[p] < _JSON_value_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( data[p] > _JSON_value_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_value_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( data[p] < _JSON_value_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( data[p] > _JSON_value_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - cs = _JSON_value_trans_targs[_trans]; - - if ( _JSON_value_trans_actions[_trans] != 0 ) { - _acts = _JSON_value_trans_actions[_trans]; - _nacts = (int) _JSON_value_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_value_actions[_acts++] ) - { - case 0: -// line 337 "ParserConfig.rl" - { - result = context.nil; - } - break; - case 1: -// line 340 "ParserConfig.rl" - { - result = context.fals; - } - break; - case 2: -// line 343 "ParserConfig.rl" - { - result = context.tru; - } - break; - case 3: -// line 346 "ParserConfig.rl" - { - if (config.allowNaN) { - result = getConstant(CONST_NAN); - } else { - throw unexpectedToken(context, p - 2, pe); - } - } - break; - case 4: -// line 353 "ParserConfig.rl" - { - if (config.allowNaN) { - result = getConstant(CONST_INFINITY); - } else { - throw unexpectedToken(context, p - 7, pe); - } - } - break; - case 5: -// line 360 "ParserConfig.rl" - { - if (pe > p + 8 && - absSubSequence(p, p + 9).equals(JSON_MINUS_INFINITY)) { - - if (config.allowNaN) { - result = getConstant(CONST_MINUS_INFINITY); - {p = (( p + 10))-1;} - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - throw unexpectedToken(context, p, pe); - } - } - parseFloat(context, res, p, pe); - if (res.result != null) { - result = res.result; - {p = (( res.p))-1;} - } - parseInteger(context, res, p, pe); - if (res.result != null) { - result = res.result; - {p = (( res.p))-1;} - } - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; - case 6: -// line 386 "ParserConfig.rl" - { - parseString(context, res, p, pe); - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - result = res.result; - {p = (( res.p))-1;} - } - } - break; - case 7: -// line 396 "ParserConfig.rl" - { - currentNesting++; - parseArray(context, res, p, pe); - currentNesting--; - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - result = res.result; - {p = (( res.p))-1;} - } - } - break; - case 8: -// line 408 "ParserConfig.rl" - { - currentNesting++; - parseObject(context, res, p, pe); - currentNesting--; - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - result = res.result; - {p = (( res.p))-1;} - } - } - break; -// line 644 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 443 "ParserConfig.rl" - - if (cs >= JSON_value_first_final && result != null) { - if (config.freeze) { - result.setFrozen(true); - } - res.update(result, p); - } else { - res.update(null, p); - } - } - - -// line 677 "ParserConfig.java" -private static byte[] init__JSON_integer_actions_0() -{ - return new byte [] { - 0, 1, 0 - }; -} - -private static final byte _JSON_integer_actions[] = init__JSON_integer_actions_0(); - - -private static byte[] init__JSON_integer_key_offsets_0() -{ - return new byte [] { - 0, 0, 4, 7, 9, 9 - }; -} - -private static final byte _JSON_integer_key_offsets[] = init__JSON_integer_key_offsets_0(); - - -private static char[] init__JSON_integer_trans_keys_0() -{ - return new char [] { - 45, 48, 49, 57, 48, 49, 57, 48, 57, 48, 57, 0 - }; -} - -private static final char _JSON_integer_trans_keys[] = init__JSON_integer_trans_keys_0(); - - -private static byte[] init__JSON_integer_single_lengths_0() -{ - return new byte [] { - 0, 2, 1, 0, 0, 0 - }; -} - -private static final byte _JSON_integer_single_lengths[] = init__JSON_integer_single_lengths_0(); - - -private static byte[] init__JSON_integer_range_lengths_0() -{ - return new byte [] { - 0, 1, 1, 1, 0, 1 - }; -} - -private static final byte _JSON_integer_range_lengths[] = init__JSON_integer_range_lengths_0(); - - -private static byte[] init__JSON_integer_index_offsets_0() -{ - return new byte [] { - 0, 0, 4, 7, 9, 10 - }; -} - -private static final byte _JSON_integer_index_offsets[] = init__JSON_integer_index_offsets_0(); - - -private static byte[] init__JSON_integer_indicies_0() -{ - return new byte [] { - 0, 2, 3, 1, 2, 3, 1, 1, 4, 1, 3, 4, - 0 - }; -} - -private static final byte _JSON_integer_indicies[] = init__JSON_integer_indicies_0(); - - -private static byte[] init__JSON_integer_trans_targs_0() -{ - return new byte [] { - 2, 0, 3, 5, 4 - }; -} - -private static final byte _JSON_integer_trans_targs[] = init__JSON_integer_trans_targs_0(); - - -private static byte[] init__JSON_integer_trans_actions_0() -{ - return new byte [] { - 0, 0, 0, 0, 1 - }; -} - -private static final byte _JSON_integer_trans_actions[] = init__JSON_integer_trans_actions_0(); - - -static final int JSON_integer_start = 1; -static final int JSON_integer_first_final = 3; -static final int JSON_integer_error = 0; - -static final int JSON_integer_en_main = 1; - - -// line 465 "ParserConfig.rl" - - - void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { - int new_p = parseIntegerInternal(p, pe); - if (new_p == -1) { - res.update(null, p); - return; - } - RubyInteger number = createInteger(context, p, new_p); - res.update(number, new_p + 1); - } - - int parseIntegerInternal(int p, int pe) { - int cs; - - -// line 793 "ParserConfig.java" - { - cs = JSON_integer_start; - } - -// line 481 "ParserConfig.rl" - int memo = p; - -// line 801 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _match: do { - _keys = _JSON_integer_key_offsets[cs]; - _trans = _JSON_integer_index_offsets[cs]; - _klen = _JSON_integer_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( data[p] < _JSON_integer_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( data[p] > _JSON_integer_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_integer_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( data[p] < _JSON_integer_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( data[p] > _JSON_integer_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - _trans = _JSON_integer_indicies[_trans]; - cs = _JSON_integer_trans_targs[_trans]; - - if ( _JSON_integer_trans_actions[_trans] != 0 ) { - _acts = _JSON_integer_trans_actions[_trans]; - _nacts = (int) _JSON_integer_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_integer_actions[_acts++] ) - { - case 0: -// line 459 "ParserConfig.rl" - { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; -// line 888 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 483 "ParserConfig.rl" - - if (cs < JSON_integer_first_final) { - return -1; - } - - return p; - } - - RubyInteger createInteger(ThreadContext context, int p, int new_p) { - Ruby runtime = context.runtime; - ByteList num = absSubSequence(p, new_p); - return bytesToInum(runtime, num); - } - - RubyInteger bytesToInum(Ruby runtime, ByteList num) { - return ConvertBytes.byteListToInum(runtime, num, 10, true); - } - - -// line 928 "ParserConfig.java" -private static byte[] init__JSON_float_actions_0() -{ - return new byte [] { - 0, 1, 0 - }; -} - -private static final byte _JSON_float_actions[] = init__JSON_float_actions_0(); - - -private static byte[] init__JSON_float_key_offsets_0() -{ - return new byte [] { - 0, 0, 4, 7, 10, 12, 16, 18, 23, 29, 29 - }; -} - -private static final byte _JSON_float_key_offsets[] = init__JSON_float_key_offsets_0(); - - -private static char[] init__JSON_float_trans_keys_0() -{ - return new char [] { - 45, 48, 49, 57, 48, 49, 57, 46, 69, 101, 48, 57, - 43, 45, 48, 57, 48, 57, 46, 69, 101, 48, 57, 69, - 101, 45, 46, 48, 57, 69, 101, 45, 46, 48, 57, 0 - }; -} - -private static final char _JSON_float_trans_keys[] = init__JSON_float_trans_keys_0(); - - -private static byte[] init__JSON_float_single_lengths_0() -{ - return new byte [] { - 0, 2, 1, 3, 0, 2, 0, 3, 2, 0, 2 - }; -} - -private static final byte _JSON_float_single_lengths[] = init__JSON_float_single_lengths_0(); - - -private static byte[] init__JSON_float_range_lengths_0() -{ - return new byte [] { - 0, 1, 1, 0, 1, 1, 1, 1, 2, 0, 2 - }; -} - -private static final byte _JSON_float_range_lengths[] = init__JSON_float_range_lengths_0(); - - -private static byte[] init__JSON_float_index_offsets_0() -{ - return new byte [] { - 0, 0, 4, 7, 11, 13, 17, 19, 24, 29, 30 - }; -} - -private static final byte _JSON_float_index_offsets[] = init__JSON_float_index_offsets_0(); - - -private static byte[] init__JSON_float_indicies_0() -{ - return new byte [] { - 0, 2, 3, 1, 2, 3, 1, 4, 5, 5, 1, 6, - 1, 7, 7, 8, 1, 8, 1, 4, 5, 5, 3, 1, - 5, 5, 1, 6, 9, 1, 1, 1, 1, 8, 9, 0 - }; -} - -private static final byte _JSON_float_indicies[] = init__JSON_float_indicies_0(); - - -private static byte[] init__JSON_float_trans_targs_0() -{ - return new byte [] { - 2, 0, 3, 7, 4, 5, 8, 6, 10, 9 - }; -} - -private static final byte _JSON_float_trans_targs[] = init__JSON_float_trans_targs_0(); - - -private static byte[] init__JSON_float_trans_actions_0() -{ - return new byte [] { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 - }; -} - -private static final byte _JSON_float_trans_actions[] = init__JSON_float_trans_actions_0(); - - -static final int JSON_float_start = 1; -static final int JSON_float_first_final = 8; -static final int JSON_float_error = 0; - -static final int JSON_float_en_main = 1; - - -// line 516 "ParserConfig.rl" - - - void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { - int new_p = parseFloatInternal(p, pe); - if (new_p == -1) { - res.update(null, p); - return; - } - final ByteList num = absSubSequence(p, new_p); - IRubyObject number = config.decimalFactory.apply(context, num); - - res.update(number, new_p + 1); - } - - int parseFloatInternal(int p, int pe) { - int cs; - - -// line 1049 "ParserConfig.java" - { - cs = JSON_float_start; - } - -// line 534 "ParserConfig.rl" - int memo = p; - -// line 1057 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _match: do { - _keys = _JSON_float_key_offsets[cs]; - _trans = _JSON_float_index_offsets[cs]; - _klen = _JSON_float_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( data[p] < _JSON_float_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( data[p] > _JSON_float_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_float_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( data[p] < _JSON_float_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( data[p] > _JSON_float_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - _trans = _JSON_float_indicies[_trans]; - cs = _JSON_float_trans_targs[_trans]; - - if ( _JSON_float_trans_actions[_trans] != 0 ) { - _acts = _JSON_float_trans_actions[_trans]; - _nacts = (int) _JSON_float_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_float_actions[_acts++] ) - { - case 0: -// line 507 "ParserConfig.rl" - { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; -// line 1144 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 536 "ParserConfig.rl" - - if (cs < JSON_float_first_final) { - return -1; - } - - return p; - } - - -// line 1174 "ParserConfig.java" -private static byte[] init__JSON_string_actions_0() -{ - return new byte [] { - 0, 2, 0, 1 - }; -} - -private static final byte _JSON_string_actions[] = init__JSON_string_actions_0(); - - -private static byte[] init__JSON_string_key_offsets_0() -{ - return new byte [] { - 0, 0, 1, 5, 8, 14, 20, 26, 32 - }; -} - -private static final byte _JSON_string_key_offsets[] = init__JSON_string_key_offsets_0(); - - -private static char[] init__JSON_string_trans_keys_0() -{ - return new char [] { - 34, 34, 92, 0, 31, 117, 0, 31, 48, 57, 65, 70, - 97, 102, 48, 57, 65, 70, 97, 102, 48, 57, 65, 70, - 97, 102, 48, 57, 65, 70, 97, 102, 0 - }; -} - -private static final char _JSON_string_trans_keys[] = init__JSON_string_trans_keys_0(); - - -private static byte[] init__JSON_string_single_lengths_0() -{ - return new byte [] { - 0, 1, 2, 1, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_string_single_lengths[] = init__JSON_string_single_lengths_0(); - - -private static byte[] init__JSON_string_range_lengths_0() -{ - return new byte [] { - 0, 0, 1, 1, 3, 3, 3, 3, 0 - }; -} - -private static final byte _JSON_string_range_lengths[] = init__JSON_string_range_lengths_0(); - - -private static byte[] init__JSON_string_index_offsets_0() -{ - return new byte [] { - 0, 0, 2, 6, 9, 13, 17, 21, 25 - }; -} - -private static final byte _JSON_string_index_offsets[] = init__JSON_string_index_offsets_0(); - - -private static byte[] init__JSON_string_indicies_0() -{ - return new byte [] { - 0, 1, 2, 3, 1, 0, 4, 1, 0, 5, 5, 5, - 1, 6, 6, 6, 1, 7, 7, 7, 1, 0, 0, 0, - 1, 1, 0 - }; -} - -private static final byte _JSON_string_indicies[] = init__JSON_string_indicies_0(); - - -private static byte[] init__JSON_string_trans_targs_0() -{ - return new byte [] { - 2, 0, 8, 3, 4, 5, 6, 7 - }; -} - -private static final byte _JSON_string_trans_targs[] = init__JSON_string_trans_targs_0(); - - -private static byte[] init__JSON_string_trans_actions_0() -{ - return new byte [] { - 0, 0, 1, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_string_trans_actions[] = init__JSON_string_trans_actions_0(); - - -static final int JSON_string_start = 1; -static final int JSON_string_first_final = 8; -static final int JSON_string_error = 0; - -static final int JSON_string_en_main = 1; - - -// line 575 "ParserConfig.rl" - - - void parseString(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - IRubyObject result = null; - - -// line 1284 "ParserConfig.java" - { - cs = JSON_string_start; - } - -// line 582 "ParserConfig.rl" - int memo = p; - -// line 1292 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _match: do { - _keys = _JSON_string_key_offsets[cs]; - _trans = _JSON_string_index_offsets[cs]; - _klen = _JSON_string_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( data[p] < _JSON_string_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( data[p] > _JSON_string_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_string_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( data[p] < _JSON_string_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( data[p] > _JSON_string_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - _trans = _JSON_string_indicies[_trans]; - cs = _JSON_string_trans_targs[_trans]; - - if ( _JSON_string_trans_actions[_trans] != 0 ) { - _acts = _JSON_string_trans_actions[_trans]; - _nacts = (int) _JSON_string_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_string_actions[_acts++] ) - { - case 0: -// line 550 "ParserConfig.rl" - { - int offset = byteList.begin(); - ByteList decoded = decoder.decode(context, byteList, memo + 1 - offset, - p - offset); - result = context.runtime.newString(decoded); - if (result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - {p = (( p + 1))-1;} - } - } - break; - case 1: -// line 563 "ParserConfig.rl" - { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; -// line 1394 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 584 "ParserConfig.rl" - - if (config.createAdditions) { - RubyHash matchString = config.match_string; - if (matchString != null) { - final IRubyObject[] memoArray = { result, null }; - try { - matchString.visitAll(context, MATCH_VISITOR, memoArray); - } catch (JumpException e) { } - if (memoArray[1] != null) { - RubyClass klass = (RubyClass) memoArray[1]; - if (klass.respondsTo("json_creatable?") && - klass.callMethod(context, "json_creatable?").isTrue()) { - if (config.deprecatedCreateAdditions) { - context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); - } - result = klass.callMethod(context, "json_create", result); - } - } - } - } - - if (cs >= JSON_string_first_final && result != null) { - if (result instanceof RubyString) { - RubyString string = (RubyString)result; - string.setEncoding(UTF8Encoding.INSTANCE); - string.clearCodeRange(); - if (config.freeze) { - string.setFrozen(true); - string = context.runtime.freezeAndDedupString(string); - } - res.update(string, p + 1); - } else { - res.update(result, p + 1); - } - } else { - res.update(null, p + 1); - } - } - - -// line 1455 "ParserConfig.java" -private static byte[] init__JSON_array_actions_0() -{ - return new byte [] { - 0, 1, 0, 1, 1 - }; -} - -private static final byte _JSON_array_actions[] = init__JSON_array_actions_0(); - - -private static byte[] init__JSON_array_cond_offsets_0() -{ - return new byte [] { - 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 6, 6, 6, 6, 6, 8, 11, 16, 19 - }; -} - -private static final byte _JSON_array_cond_offsets[] = init__JSON_array_cond_offsets_0(); - - -private static byte[] init__JSON_array_cond_lengths_0() -{ - return new byte [] { - 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 5, 0, 0, 0, 0, 2, 3, 5, 3, 0 - }; -} - -private static final byte _JSON_array_cond_lengths[] = init__JSON_array_cond_lengths_0(); - - -private static int[] init__JSON_array_cond_keys_0() -{ - return new int [] { - 44, 44, 9, 9, 10, 10, 13, 13, 32, 32, 47, 47, - 42, 42, 47, 47, 0, 41, 42, 42, 43,65535, 0, 41, - 42, 42, 43, 46, 47, 47, 48,65535, 0, 9, 10, 10, - 11,65535, 0 - }; -} - -private static final int _JSON_array_cond_keys[] = init__JSON_array_cond_keys_0(); - - -private static byte[] init__JSON_array_cond_spaces_0() -{ - return new byte [] { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_array_cond_spaces[] = init__JSON_array_cond_spaces_0(); - - -private static byte[] init__JSON_array_key_offsets_0() -{ - return new byte [] { - 0, 0, 1, 18, 26, 28, 29, 31, 32, 48, 50, 51, - 53, 54, 76, 78, 79, 81, 82, 86, 92, 100, 106 - }; -} - -private static final byte _JSON_array_key_offsets[] = init__JSON_array_key_offsets_0(); - - -private static int[] init__JSON_array_trans_keys_0() -{ - return new int [] { - 91, 13, 32, 34, 45, 47, 73, 78, 91, 93, 102, 110, - 116, 123, 9, 10, 48, 57, 13, 32, 47, 93,65580,131116, - 9, 10, 42, 47, 42, 42, 47, 10, 13, 32, 34, 45, - 47, 73, 78, 91, 102, 110, 116, 123, 9, 10, 48, 57, - 42, 47, 42, 42, 47, 10, 34, 45, 73, 78, 91, 93, - 102, 110, 116, 123,65549,65568,65583,131085,131104,131119, 48, 57, - 65545,65546,131081,131082, 42, 47, 42, 42, 47, 10,65578,65583, - 131114,131119,65578,131114,65536,131071,131072,196607,65578,65583,131114,131119, - 65536,131071,131072,196607,65546,131082,65536,131071,131072,196607, 0 - }; -} - -private static final int _JSON_array_trans_keys[] = init__JSON_array_trans_keys_0(); - - -private static byte[] init__JSON_array_single_lengths_0() -{ - return new byte [] { - 0, 1, 13, 6, 2, 1, 2, 1, 12, 2, 1, 2, - 1, 16, 2, 1, 2, 1, 4, 2, 4, 2, 0 - }; -} - -private static final byte _JSON_array_single_lengths[] = init__JSON_array_single_lengths_0(); - - -private static byte[] init__JSON_array_range_lengths_0() -{ - return new byte [] { - 0, 0, 2, 1, 0, 0, 0, 0, 2, 0, 0, 0, - 0, 3, 0, 0, 0, 0, 0, 2, 2, 2, 0 - }; -} - -private static final byte _JSON_array_range_lengths[] = init__JSON_array_range_lengths_0(); - - -private static short[] init__JSON_array_index_offsets_0() -{ - return new short [] { - 0, 0, 2, 18, 26, 29, 31, 34, 36, 51, 54, 56, - 59, 61, 81, 84, 86, 89, 91, 96, 101, 108, 113 - }; -} - -private static final short _JSON_array_index_offsets[] = init__JSON_array_index_offsets_0(); - - -private static byte[] init__JSON_array_indicies_0() -{ - return new byte [] { - 0, 1, 0, 0, 2, 2, 3, 2, 2, 2, 4, 2, - 2, 2, 2, 0, 2, 1, 5, 5, 6, 4, 7, 8, - 5, 1, 9, 10, 1, 11, 9, 11, 5, 9, 5, 10, - 7, 7, 2, 2, 12, 2, 2, 2, 2, 2, 2, 2, - 7, 2, 1, 13, 14, 1, 15, 13, 15, 7, 13, 7, - 14, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 0, - 0, 3, 8, 8, 16, 2, 0, 8, 1, 17, 18, 1, - 19, 17, 19, 0, 17, 0, 18, 17, 18, 20, 21, 1, - 19, 22, 17, 20, 1, 19, 0, 22, 8, 17, 20, 1, - 0, 8, 18, 21, 1, 1, 0 - }; -} - -private static final byte _JSON_array_indicies[] = init__JSON_array_indicies_0(); - - -private static byte[] init__JSON_array_trans_targs_0() -{ - return new byte [] { - 2, 0, 3, 14, 22, 3, 4, 8, 13, 5, 7, 6, - 9, 10, 12, 11, 18, 15, 17, 16, 19, 21, 20 - }; -} - -private static final byte _JSON_array_trans_targs[] = init__JSON_array_trans_targs_0(); - - -private static byte[] init__JSON_array_trans_actions_0() -{ - return new byte [] { - 0, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_array_trans_actions[] = init__JSON_array_trans_actions_0(); - - -static final int JSON_array_start = 1; -static final int JSON_array_first_final = 22; -static final int JSON_array_error = 0; - -static final int JSON_array_en_main = 1; - - -// line 662 "ParserConfig.rl" - - - void parseArray(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - - if (config.maxNesting > 0 && currentNesting > config.maxNesting) { - throw newException(context, Utils.M_NESTING_ERROR, - "nesting of " + currentNesting + " is too deep"); - } - - IRubyObject result; - if (config.arrayClass == context.runtime.getArray()) { - result = RubyArray.newArray(context.runtime); - } else { - result = config.arrayClass.newInstance(context, - IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); - } - - -// line 1642 "ParserConfig.java" - { - cs = JSON_array_start; - } - -// line 681 "ParserConfig.rl" - -// line 1649 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _widec; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _widec = data[p]; - _keys = _JSON_array_cond_offsets[cs]*2 -; _klen = _JSON_array_cond_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys -; int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( _widec < _JSON_array_cond_keys[_mid] ) - _upper = _mid - 2; - else if ( _widec > _JSON_array_cond_keys[_mid+1] ) - _lower = _mid + 2; - else { - switch ( _JSON_array_cond_spaces[_JSON_array_cond_offsets[cs] + ((_mid - _keys)>>1)] ) { - case 0: { - _widec = 65536 + (data[p] - 0); - if ( -// line 629 "ParserConfig.rl" - config.allowTrailingComma ) _widec += 65536; - break; - } - } - break; - } - } - } - - _match: do { - _keys = _JSON_array_key_offsets[cs]; - _trans = _JSON_array_index_offsets[cs]; - _klen = _JSON_array_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( _widec < _JSON_array_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( _widec > _JSON_array_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_array_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( _widec < _JSON_array_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( _widec > _JSON_array_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - _trans = _JSON_array_indicies[_trans]; - cs = _JSON_array_trans_targs[_trans]; - - if ( _JSON_array_trans_actions[_trans] != 0 ) { - _acts = _JSON_array_trans_actions[_trans]; - _nacts = (int) _JSON_array_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_array_actions[_acts++] ) - { - case 0: -// line 631 "ParserConfig.rl" - { - parseValue(context, res, p, pe); - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - if (config.arrayClass == context.runtime.getArray()) { - ((RubyArray)result).append(res.result); - } else { - result.callMethod(context, "<<", res.result); - } - {p = (( res.p))-1;} - } - } - break; - case 1: -// line 646 "ParserConfig.rl" - { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; -// line 1785 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 682 "ParserConfig.rl" - - if (cs >= JSON_array_first_final) { - res.update(result, p + 1); - } else { - throw unexpectedToken(context, p, pe); - } - } - - -// line 1815 "ParserConfig.java" -private static byte[] init__JSON_object_actions_0() -{ - return new byte [] { - 0, 1, 0, 1, 1, 1, 2 - }; -} - -private static final byte _JSON_object_actions[] = init__JSON_object_actions_0(); - - -private static byte[] init__JSON_object_cond_offsets_0() -{ - return new byte [] { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 8, 11, 16, - 19, 19, 19, 19, 19, 19, 19, 19, 19 - }; -} - -private static final byte _JSON_object_cond_offsets[] = init__JSON_object_cond_offsets_0(); - - -private static byte[] init__JSON_object_cond_lengths_0() -{ - return new byte [] { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 5, 3, - 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_object_cond_lengths[] = init__JSON_object_cond_lengths_0(); - - -private static int[] init__JSON_object_cond_keys_0() -{ - return new int [] { - 9, 9, 10, 10, 13, 13, 32, 32, 44, 44, 47, 47, - 42, 42, 47, 47, 0, 41, 42, 42, 43,65535, 0, 41, - 42, 42, 43, 46, 47, 47, 48,65535, 0, 9, 10, 10, - 11,65535, 0 - }; -} - -private static final int _JSON_object_cond_keys[] = init__JSON_object_cond_keys_0(); - - -private static byte[] init__JSON_object_cond_spaces_0() -{ - return new byte [] { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_object_cond_spaces[] = init__JSON_object_cond_spaces_0(); - - -private static byte[] init__JSON_object_key_offsets_0() -{ - return new byte [] { - 0, 0, 1, 8, 14, 16, 17, 19, 20, 36, 49, 56, - 62, 64, 65, 67, 68, 70, 71, 73, 74, 78, 84, 92, - 98, 100, 101, 103, 104, 106, 107, 109, 110 - }; -} - -private static final byte _JSON_object_key_offsets[] = init__JSON_object_key_offsets_0(); - - -private static int[] init__JSON_object_trans_keys_0() -{ - return new int [] { - 123, 13, 32, 34, 47, 125, 9, 10, 13, 32, 47, 58, - 9, 10, 42, 47, 42, 42, 47, 10, 13, 32, 34, 45, - 47, 73, 78, 91, 102, 110, 116, 123, 9, 10, 48, 57, - 125,65549,65568,65580,65583,131085,131104,131116,131119,65545,65546,131081, - 131082, 13, 32, 44, 47, 125, 9, 10, 13, 32, 34, 47, - 9, 10, 42, 47, 42, 42, 47, 10, 42, 47, 42, 42, - 47, 10,65578,65583,131114,131119,65578,131114,65536,131071,131072,196607, - 65578,65583,131114,131119,65536,131071,131072,196607,65546,131082,65536,131071, - 131072,196607, 42, 47, 42, 42, 47, 10, 42, 47, 42, 42, - 47, 10, 0 - }; -} - -private static final int _JSON_object_trans_keys[] = init__JSON_object_trans_keys_0(); - - -private static byte[] init__JSON_object_single_lengths_0() -{ - return new byte [] { - 0, 1, 5, 4, 2, 1, 2, 1, 12, 9, 5, 4, - 2, 1, 2, 1, 2, 1, 2, 1, 4, 2, 4, 2, - 2, 1, 2, 1, 2, 1, 2, 1, 0 - }; -} - -private static final byte _JSON_object_single_lengths[] = init__JSON_object_single_lengths_0(); - - -private static byte[] init__JSON_object_range_lengths_0() -{ - return new byte [] { - 0, 0, 1, 1, 0, 0, 0, 0, 2, 2, 1, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, - 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_object_range_lengths[] = init__JSON_object_range_lengths_0(); - - -private static short[] init__JSON_object_index_offsets_0() -{ - return new short [] { - 0, 0, 2, 9, 15, 18, 20, 23, 25, 40, 52, 59, - 65, 68, 70, 73, 75, 78, 80, 83, 85, 90, 95, 102, - 107, 110, 112, 115, 117, 120, 122, 125, 127 - }; -} - -private static final short _JSON_object_index_offsets[] = init__JSON_object_index_offsets_0(); - - -private static byte[] init__JSON_object_indicies_0() -{ - return new byte [] { - 0, 1, 0, 0, 2, 3, 4, 0, 1, 5, 5, 6, - 7, 5, 1, 8, 9, 1, 10, 8, 10, 5, 8, 5, - 9, 7, 7, 11, 11, 12, 11, 11, 11, 11, 11, 11, - 11, 7, 11, 1, 4, 13, 13, 14, 15, 16, 16, 0, - 17, 13, 16, 1, 13, 13, 14, 15, 4, 13, 1, 14, - 14, 2, 18, 14, 1, 19, 20, 1, 21, 19, 21, 14, - 19, 14, 20, 22, 23, 1, 24, 22, 24, 13, 22, 13, - 23, 22, 23, 25, 26, 1, 24, 27, 22, 25, 1, 24, - 13, 27, 16, 22, 25, 1, 13, 16, 23, 26, 1, 28, - 29, 1, 30, 28, 30, 7, 28, 7, 29, 31, 32, 1, - 33, 31, 33, 0, 31, 0, 32, 1, 0 - }; -} - -private static final byte _JSON_object_indicies[] = init__JSON_object_indicies_0(); - - -private static byte[] init__JSON_object_trans_targs_0() -{ - return new byte [] { - 2, 0, 3, 28, 32, 3, 4, 8, 5, 7, 6, 9, - 24, 10, 11, 16, 9, 20, 12, 13, 15, 14, 17, 19, - 18, 21, 23, 22, 25, 27, 26, 29, 31, 30 - }; -} - -private static final byte _JSON_object_trans_targs[] = init__JSON_object_trans_targs_0(); - - -private static byte[] init__JSON_object_trans_actions_0() -{ - return new byte [] { - 0, 0, 3, 0, 5, 0, 0, 0, 0, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_object_trans_actions[] = init__JSON_object_trans_actions_0(); - - -static final int JSON_object_start = 1; -static final int JSON_object_first_final = 32; -static final int JSON_object_error = 0; - -static final int JSON_object_en_main = 1; - - -// line 743 "ParserConfig.rl" - - - void parseObject(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - IRubyObject lastName = null; - boolean objectDefault = true; - - if (config.maxNesting > 0 && currentNesting > config.maxNesting) { - throw newException(context, Utils.M_NESTING_ERROR, - "nesting of " + currentNesting + " is too deep"); - } - - // this is guaranteed to be a RubyHash due to the earlier - // allocator test at OptionsReader#getClass - IRubyObject result; - if (config.objectClass == context.runtime.getHash()) { - result = RubyHash.newHash(context.runtime); - } else { - objectDefault = false; - result = config.objectClass.newInstance(context, - IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); - } - - -// line 2017 "ParserConfig.java" - { - cs = JSON_object_start; - } - -// line 767 "ParserConfig.rl" - -// line 2024 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _widec; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _widec = data[p]; - _keys = _JSON_object_cond_offsets[cs]*2 -; _klen = _JSON_object_cond_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys -; int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( _widec < _JSON_object_cond_keys[_mid] ) - _upper = _mid - 2; - else if ( _widec > _JSON_object_cond_keys[_mid+1] ) - _lower = _mid + 2; - else { - switch ( _JSON_object_cond_spaces[_JSON_object_cond_offsets[cs] + ((_mid - _keys)>>1)] ) { - case 0: { - _widec = 65536 + (data[p] - 0); - if ( -// line 696 "ParserConfig.rl" - config.allowTrailingComma ) _widec += 65536; - break; - } - } - break; - } - } - } - - _match: do { - _keys = _JSON_object_key_offsets[cs]; - _trans = _JSON_object_index_offsets[cs]; - _klen = _JSON_object_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( _widec < _JSON_object_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( _widec > _JSON_object_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_object_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( _widec < _JSON_object_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( _widec > _JSON_object_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - _trans = _JSON_object_indicies[_trans]; - cs = _JSON_object_trans_targs[_trans]; - - if ( _JSON_object_trans_actions[_trans] != 0 ) { - _acts = _JSON_object_trans_actions[_trans]; - _nacts = (int) _JSON_object_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_object_actions[_acts++] ) - { - case 0: -// line 698 "ParserConfig.rl" - { - parseValue(context, res, p, pe); - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - if (config.objectClass == context.runtime.getHash()) { - ((RubyHash)result).op_aset(context, lastName, res.result); - } else { - Helpers.invoke(context, result, "[]=", lastName, res.result); - } - {p = (( res.p))-1;} - } - } - break; - case 1: -// line 713 "ParserConfig.rl" - { - parseString(context, res, p, pe); - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - RubyString name = (RubyString)res.result; - if (config.symbolizeNames) { - lastName = name.intern(); - } else { - lastName = name; - } - {p = (( res.p))-1;} - } - } - break; - case 2: -// line 729 "ParserConfig.rl" - { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } - break; -// line 2178 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 768 "ParserConfig.rl" - - if (cs < JSON_object_first_final) { - res.update(null, p + 1); - return; - } - - IRubyObject returnedResult = result; - - // attempt to de-serialize object - if (config.createAdditions) { - IRubyObject vKlassName; - if (objectDefault) { - vKlassName = ((RubyHash)result).op_aref(context, config.createId); - } else { - vKlassName = result.callMethod(context, "[]", config.createId); - } - - if (!vKlassName.isNil()) { - // might throw ArgumentError, we let it propagate - IRubyObject klass = config.info.jsonModule.get(). - callMethod(context, "deep_const_get", vKlassName); - if (klass.respondsTo("json_creatable?") && - klass.callMethod(context, "json_creatable?").isTrue()) { - if (config.deprecatedCreateAdditions) { - context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); - } - - returnedResult = klass.callMethod(context, "json_create", result); - } - } - } - res.update(returnedResult, p + 1); - } - - -// line 2234 "ParserConfig.java" -private static byte[] init__JSON_actions_0() -{ - return new byte [] { - 0, 1, 0 - }; -} - -private static final byte _JSON_actions[] = init__JSON_actions_0(); - - -private static byte[] init__JSON_key_offsets_0() -{ - return new byte [] { - 0, 0, 16, 18, 19, 21, 22, 24, 25, 27, 28 - }; -} - -private static final byte _JSON_key_offsets[] = init__JSON_key_offsets_0(); - - -private static char[] init__JSON_trans_keys_0() -{ - return new char [] { - 13, 32, 34, 45, 47, 73, 78, 91, 102, 110, 116, 123, - 9, 10, 48, 57, 42, 47, 42, 42, 47, 10, 42, 47, - 42, 42, 47, 10, 13, 32, 47, 9, 10, 0 - }; -} - -private static final char _JSON_trans_keys[] = init__JSON_trans_keys_0(); - - -private static byte[] init__JSON_single_lengths_0() -{ - return new byte [] { - 0, 12, 2, 1, 2, 1, 2, 1, 2, 1, 3 - }; -} - -private static final byte _JSON_single_lengths[] = init__JSON_single_lengths_0(); - - -private static byte[] init__JSON_range_lengths_0() -{ - return new byte [] { - 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1 - }; -} - -private static final byte _JSON_range_lengths[] = init__JSON_range_lengths_0(); - - -private static byte[] init__JSON_index_offsets_0() -{ - return new byte [] { - 0, 0, 15, 18, 20, 23, 25, 28, 30, 33, 35 - }; -} - -private static final byte _JSON_index_offsets[] = init__JSON_index_offsets_0(); - - -private static byte[] init__JSON_indicies_0() -{ - return new byte [] { - 0, 0, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, - 0, 2, 1, 4, 5, 1, 6, 4, 6, 7, 4, 7, - 5, 8, 9, 1, 10, 8, 10, 0, 8, 0, 9, 7, - 7, 11, 7, 1, 0 - }; -} - -private static final byte _JSON_indicies[] = init__JSON_indicies_0(); - - -private static byte[] init__JSON_trans_targs_0() -{ - return new byte [] { - 1, 0, 10, 6, 3, 5, 4, 10, 7, 9, 8, 2 - }; -} - -private static final byte _JSON_trans_targs[] = init__JSON_trans_targs_0(); - - -private static byte[] init__JSON_trans_actions_0() -{ - return new byte [] { - 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; -} - -private static final byte _JSON_trans_actions[] = init__JSON_trans_actions_0(); - - -static final int JSON_start = 1; -static final int JSON_first_final = 10; -static final int JSON_error = 0; - -static final int JSON_en_main = 1; - - -// line 822 "ParserConfig.rl" - - - public IRubyObject parseImplementation(ThreadContext context) { - int cs; - int p, pe; - IRubyObject result = null; - ParserResult res = new ParserResult(); - - -// line 2347 "ParserConfig.java" - { - cs = JSON_start; - } - -// line 831 "ParserConfig.rl" - p = byteList.begin(); - pe = p + byteList.length(); - -// line 2356 "ParserConfig.java" - { - int _klen; - int _trans = 0; - int _acts; - int _nacts; - int _keys; - int _goto_targ = 0; - - _goto: while (true) { - switch ( _goto_targ ) { - case 0: - if ( p == pe ) { - _goto_targ = 4; - continue _goto; - } - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } -case 1: - _match: do { - _keys = _JSON_key_offsets[cs]; - _trans = _JSON_index_offsets[cs]; - _klen = _JSON_single_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + _klen - 1; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + ((_upper-_lower) >> 1); - if ( data[p] < _JSON_trans_keys[_mid] ) - _upper = _mid - 1; - else if ( data[p] > _JSON_trans_keys[_mid] ) - _lower = _mid + 1; - else { - _trans += (_mid - _keys); - break _match; - } - } - _keys += _klen; - _trans += _klen; - } - - _klen = _JSON_range_lengths[cs]; - if ( _klen > 0 ) { - int _lower = _keys; - int _mid; - int _upper = _keys + (_klen<<1) - 2; - while (true) { - if ( _upper < _lower ) - break; - - _mid = _lower + (((_upper-_lower) >> 1) & ~1); - if ( data[p] < _JSON_trans_keys[_mid] ) - _upper = _mid - 2; - else if ( data[p] > _JSON_trans_keys[_mid+1] ) - _lower = _mid + 2; - else { - _trans += ((_mid - _keys)>>1); - break _match; - } - } - _trans += _klen; - } - } while (false); - - _trans = _JSON_indicies[_trans]; - cs = _JSON_trans_targs[_trans]; - - if ( _JSON_trans_actions[_trans] != 0 ) { - _acts = _JSON_trans_actions[_trans]; - _nacts = (int) _JSON_actions[_acts++]; - while ( _nacts-- > 0 ) - { - switch ( _JSON_actions[_acts++] ) - { - case 0: -// line 808 "ParserConfig.rl" - { - parseValue(context, res, p, pe); - if (res.result == null) { - p--; - { p += 1; _goto_targ = 5; if (true) continue _goto;} - } else { - result = res.result; - {p = (( res.p))-1;} - } - } - break; -// line 2449 "ParserConfig.java" - } - } - } - -case 2: - if ( cs == 0 ) { - _goto_targ = 5; - continue _goto; - } - if ( ++p != pe ) { - _goto_targ = 1; - continue _goto; - } -case 4: -case 5: - } - break; } - } - -// line 834 "ParserConfig.rl" - - if (cs >= JSON_first_final && p == pe) { - return result; - } else { - throw unexpectedToken(context, p, pe); - } - } - - public IRubyObject parse(ThreadContext context) { - return parseImplementation(context); - } - - /** - * Updates the "view" bytelist with the new offsets and returns it. - * @param absStart - * @param absEnd - */ - private ByteList absSubSequence(int absStart, int absEnd) { - view.setBegin(absStart); - view.setRealSize(absEnd - absStart); - return view; - } - - /** - * Retrieves a constant directly descended from the JSON module. - * @param name The constant name - */ - private IRubyObject getConstant(String name) { - return config.info.jsonModule.get().getConstant(name); - } - - private RaiseException newException(ThreadContext context, String className, String message) { - return Utils.newException(context, className, message); - } - - private RaiseException newException(ThreadContext context, String className, RubyString message) { - return Utils.newException(context, className, message); - } - - RubyHash.VisitorWithState MATCH_VISITOR = new RubyHash.VisitorWithState() { - @Override - public void visit(ThreadContext context, RubyHash self, IRubyObject pattern, IRubyObject klass, int index, IRubyObject[] state) { - if (pattern.callMethod(context, "===", state[0]).isTrue()) { - state[1] = klass; - throw JumpException.SPECIAL_JUMP; - } - } - }; - } -} diff --git a/java/src/json/ext/ParserConfig.rl b/java/src/json/ext/ParserConfig.rl deleted file mode 100644 index 0382a7c5d..000000000 --- a/java/src/json/ext/ParserConfig.rl +++ /dev/null @@ -1,883 +0,0 @@ -/* - * This code is copyrighted work by Daniel Luz . - * - * Distributed under the Ruby license: https://www.ruby-lang.org/en/about/license.txt - */ -package json.ext; - -import org.jcodings.Encoding; -import org.jcodings.specific.ASCIIEncoding; -import org.jcodings.specific.UTF8Encoding; -import org.jruby.Ruby; -import org.jruby.RubyArray; -import org.jruby.RubyClass; -import org.jruby.RubyFloat; -import org.jruby.RubyHash; -import org.jruby.RubyInteger; -import org.jruby.RubyObject; -import org.jruby.RubyString; -import org.jruby.anno.JRubyMethod; -import org.jruby.exceptions.JumpException; -import org.jruby.exceptions.RaiseException; -import org.jruby.runtime.Block; -import org.jruby.runtime.Helpers; -import org.jruby.runtime.ObjectAllocator; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.Visibility; -import org.jruby.runtime.builtin.IRubyObject; -import org.jruby.util.ByteList; -import org.jruby.util.ConvertBytes; - -import java.util.function.BiFunction; - -import static org.jruby.util.ConvertDouble.DoubleConverter; - -/** - * The JSON::Ext::Parser class. - * - *

This is the JSON parser implemented as a Java class. To use it as the - * standard parser, set - *

JSON.parser = JSON::Ext::Parser
- * This is performed for you when you include "json/ext". - * - *

This class does not perform the actual parsing, just acts as an interface - * to Ruby code. When the {@link #parse(ThreadContext)} method is invoked, a - * ParserConfig.ParserSession object is instantiated, which handles the process. - * - * @author mernen - */ -public class ParserConfig extends RubyObject { - private final RuntimeInfo info; - private RubyString createId; - private boolean createAdditions; - private boolean deprecatedCreateAdditions; - private int maxNesting; - private boolean allowNaN; - private boolean allowTrailingComma; - private boolean symbolizeNames; - private boolean freeze; - private RubyClass objectClass; - private RubyClass arrayClass; - private RubyClass decimalClass; - BiFunction decimalFactory; - private RubyHash match_string; - - private static final int DEFAULT_MAX_NESTING = 100; - - private static final ByteList JSON_MINUS_INFINITY = new ByteList(ByteList.plain("-Infinity")); - // constant names in the JSON module containing those values - private static final String CONST_NAN = "NaN"; - private static final String CONST_INFINITY = "Infinity"; - private static final String CONST_MINUS_INFINITY = "MinusInfinity"; - - static final ObjectAllocator ALLOCATOR = ParserConfig::new; - - /** - * Multiple-value return for internal parser methods. - * - *

All the parseStuff methods return instances of - * ParserResult when successful, or null when - * there's a problem with the input data. - */ - static final class ParserResult { - /** - * The result of the successful parsing. Should never be - * null. - */ - IRubyObject result; - /** - * The point where the parser returned. - */ - int p; - - void update(IRubyObject result, int p) { - this.result = result; - this.p = p; - } - } - - public ParserConfig(Ruby runtime, RubyClass metaClass) { - super(runtime, metaClass); - info = RuntimeInfo.forRuntime(runtime); - } - - /** - * ParserConfig.new(source, opts = {}) - * - *

Creates a new JSON::Ext::Parser instance for the string - * source. - * It will be configured by the opts Hash. - * opts can have the following keys: - *

- *

- *
:max_nesting - *
The maximum depth of nesting allowed in the parsed data - * structures. Disable depth checking with :max_nesting => false|nil|0, - * it defaults to 100. - *

- *

:allow_nan - *
If set to true, allow NaN, - * Infinity and -Infinity in defiance of RFC 4627 - * to be parsed by the Parser. This option defaults to false. - *

- *

:allow_trailing_comma - *
If set to true, allow arrays and objects with a trailing - * comma in defiance of RFC 4627 to be parsed by the Parser. - * This option defaults to false. - *

- *

:symbolize_names - *
If set to true, returns symbols for the names (keys) in - * a JSON object. Otherwise strings are returned, which is also the default. - *

- *

:create_additions - *
If set to false, the Parser doesn't create additions - * even if a matching class and create_id was found. This option - * defaults to true. - *

- *

:object_class - *
Defaults to Hash. If another type is provided, it will be used - * instead of Hash to represent JSON objects. The type must respond to - * new without arguments, and return an object that respond to []=. - *

- *

:array_class - *
Defaults to Array. If another type is provided, it will be used - * instead of Hash to represent JSON arrays. The type must respond to - * new without arguments, and return an object that respond to <<. - *

- *

:decimal_class - *
Specifies which class to use instead of the default (Float) when - * parsing decimal numbers. This class must accept a single string argument - * in its constructor. - *
- */ - - @JRubyMethod(name = "new", meta = true) - public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, Block block) { - ParserConfig config = (ParserConfig)((RubyClass)clazz).allocate(); - - config.callInit(arg0, block); - - return config; - } - - @JRubyMethod(name = "new", meta = true) - public static IRubyObject newInstance(IRubyObject clazz, IRubyObject arg0, IRubyObject arg1, Block block) { - ParserConfig config = (ParserConfig)((RubyClass)clazz).allocate(); - - config.callInit(arg0, arg1, block); - - return config; - } - - @JRubyMethod(visibility = Visibility.PRIVATE) - public IRubyObject initialize(ThreadContext context, IRubyObject options) { - Ruby runtime = context.runtime; - - OptionsReader opts = new OptionsReader(context, options); - this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); - this.allowNaN = opts.getBool("allow_nan", false); - this.allowTrailingComma = opts.getBool("allow_trailing_comma", false); - this.symbolizeNames = opts.getBool("symbolize_names", false); - this.freeze = opts.getBool("freeze", false); - this.createId = opts.getString("create_id", getCreateId(context)); - - IRubyObject additions = opts.get("create_additions"); - this.createAdditions = false; - this.deprecatedCreateAdditions = false; - - if (additions != null) { - if (additions.isNil()) { - this.createAdditions = true; - this.deprecatedCreateAdditions = true; - } else { - this.createAdditions = opts.getBool("create_additions", false); - } - } - - this.objectClass = opts.getClass("object_class", runtime.getHash()); - this.arrayClass = opts.getClass("array_class", runtime.getArray()); - this.decimalClass = opts.getClass("decimal_class", null); - this.match_string = opts.getHash("match_string"); - - if (decimalClass == null) { - this.decimalFactory = this::createFloat; - } else if (decimalClass == runtime.getClass("BigDecimal")) { - this.decimalFactory = this::createBigDecimal; - } else { - this.decimalFactory = this::createCustomDecimal; - } - - if(symbolizeNames && createAdditions) { - throw runtime.newArgumentError("options :symbolize_names and :create_additions cannot be used in conjunction"); - } - - return this; - } - - /** - * Checks the given string's encoding. If a non-UTF-8 encoding is detected, - * a converted copy is returned. - * Returns the source string if no conversion is needed. - */ - private RubyString convertEncoding(ThreadContext context, RubyString source) { - Encoding encoding = source.getEncoding(); - if (encoding == ASCIIEncoding.INSTANCE) { - source = (RubyString) source.dup(); - source.setEncoding(UTF8Encoding.INSTANCE); - source.clearCodeRange(); - } else if (encoding != UTF8Encoding.INSTANCE) { - source = (RubyString) source.encode(context, context.runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); - } - return source; - } - - /** - * Parser#parse() - * - *

Parses the current JSON text source and returns the - * complete data structure as a result. - */ - @JRubyMethod - public IRubyObject parse(ThreadContext context, IRubyObject source) { - return new ParserSession(this, convertEncoding(context, source.convertToString()), context, info).parse(context); - } - - /** - * Queries JSON.create_id. Returns null if it is - * set to nil or false, and a String if not. - */ - private RubyString getCreateId(ThreadContext context) { - IRubyObject v = info.jsonModule.get().callMethod(context, "create_id"); - return v.isTrue() ? v.convertToString() : null; - } - - private RubyFloat createFloat(final ThreadContext context, final ByteList num) { - return RubyFloat.newFloat(context.runtime, new DoubleConverter().parse(num, true, true)); - } - - private IRubyObject createBigDecimal(final ThreadContext context, final ByteList num) { - final Ruby runtime = context.runtime; - return runtime.getKernel().callMethod(context, "BigDecimal", runtime.newString(num)); - } - - private IRubyObject createCustomDecimal(final ThreadContext context, final ByteList num) { - return decimalClass.newInstance(context, context.runtime.newString(num), Block.NULL_BLOCK); - } - - /** - * A string parsing session. - * - *

Once a ParserSession is instantiated, the source string should not - * change until the parsing is complete. The ParserSession object assumes - * the source {@link RubyString} is still associated to its original - * {@link ByteList}, which in turn must still be bound to the same - * byte[] value (and on the same offset). - */ - // Ragel uses lots of fall-through - @SuppressWarnings("fallthrough") - private static class ParserSession { - private final ParserConfig config; - private final RuntimeInfo info; - private final ByteList byteList; - private final ByteList view; - private final byte[] data; - private final StringDecoder decoder; - private int currentNesting = 0; - - private ParserSession(ParserConfig config, RubyString source, ThreadContext context, RuntimeInfo info) { - this.config = config; - this.info = info; - this.byteList = source.getByteList(); - this.data = byteList.unsafeBytes(); - this.view = new ByteList(data, false); - this.decoder = new StringDecoder(); - } - - private RaiseException unexpectedToken(ThreadContext context, int absStart, int absEnd) { - RubyString msg = context.runtime.newString("unexpected token at '") - .cat(data, absStart, Math.min(absEnd - absStart, 32)) - .cat((byte)'\''); - return newException(context, Utils.M_PARSER_ERROR, msg); - } - - %%{ - machine JSON_common; - - cr = '\n'; - cr_neg = [^\n]; - ws = [ \t\r\n]; - c_comment = '/*' ( any* - (any* '*/' any* ) ) '*/'; - cpp_comment = '//' cr_neg* cr; - comment = c_comment | cpp_comment; - ignore = ws | comment; - name_separator = ':'; - value_separator = ','; - Vnull = 'null'; - Vfalse = 'false'; - Vtrue = 'true'; - VNaN = 'NaN'; - VInfinity = 'Infinity'; - VMinusInfinity = '-Infinity'; - begin_value = [nft"\-[{NI] | digit; - begin_object = '{'; - end_object = '}'; - begin_array = '['; - end_array = ']'; - begin_string = '"'; - begin_name = begin_string; - begin_number = digit | '-'; - }%% - - %%{ - machine JSON_value; - include JSON_common; - - write data; - - action parse_null { - result = context.nil; - } - action parse_false { - result = context.fals; - } - action parse_true { - result = context.tru; - } - action parse_nan { - if (config.allowNaN) { - result = getConstant(CONST_NAN); - } else { - throw unexpectedToken(context, p - 2, pe); - } - } - action parse_infinity { - if (config.allowNaN) { - result = getConstant(CONST_INFINITY); - } else { - throw unexpectedToken(context, p - 7, pe); - } - } - action parse_number { - if (pe > fpc + 8 && - absSubSequence(fpc, fpc + 9).equals(JSON_MINUS_INFINITY)) { - - if (config.allowNaN) { - result = getConstant(CONST_MINUS_INFINITY); - fexec p + 10; - fhold; - fbreak; - } else { - throw unexpectedToken(context, p, pe); - } - } - parseFloat(context, res, fpc, pe); - if (res.result != null) { - result = res.result; - fexec res.p; - } - parseInteger(context, res, fpc, pe); - if (res.result != null) { - result = res.result; - fexec res.p; - } - fhold; - fbreak; - } - action parse_string { - parseString(context, res, fpc, pe); - if (res.result == null) { - fhold; - fbreak; - } else { - result = res.result; - fexec res.p; - } - } - action parse_array { - currentNesting++; - parseArray(context, res, fpc, pe); - currentNesting--; - if (res.result == null) { - fhold; - fbreak; - } else { - result = res.result; - fexec res.p; - } - } - action parse_object { - currentNesting++; - parseObject(context, res, fpc, pe); - currentNesting--; - if (res.result == null) { - fhold; - fbreak; - } else { - result = res.result; - fexec res.p; - } - } - action exit { - fhold; - fbreak; - } - - main := ( Vnull @parse_null | - Vfalse @parse_false | - Vtrue @parse_true | - VNaN @parse_nan | - VInfinity @parse_infinity | - begin_number >parse_number | - begin_string >parse_string | - begin_array >parse_array | - begin_object >parse_object - ) %*exit; - }%% - - void parseValue(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - IRubyObject result = null; - - %% write init; - %% write exec; - - if (cs >= JSON_value_first_final && result != null) { - if (config.freeze) { - result.setFrozen(true); - } - res.update(result, p); - } else { - res.update(null, p); - } - } - - %%{ - machine JSON_integer; - - write data; - - action exit { - fhold; - fbreak; - } - - main := '-'? ( '0' | [1-9][0-9]* ) ( ^[0-9]? @exit ); - }%% - - void parseInteger(ThreadContext context, ParserResult res, int p, int pe) { - int new_p = parseIntegerInternal(p, pe); - if (new_p == -1) { - res.update(null, p); - return; - } - RubyInteger number = createInteger(context, p, new_p); - res.update(number, new_p + 1); - } - - int parseIntegerInternal(int p, int pe) { - int cs; - - %% write init; - int memo = p; - %% write exec; - - if (cs < JSON_integer_first_final) { - return -1; - } - - return p; - } - - RubyInteger createInteger(ThreadContext context, int p, int new_p) { - Ruby runtime = context.runtime; - ByteList num = absSubSequence(p, new_p); - return bytesToInum(runtime, num); - } - - RubyInteger bytesToInum(Ruby runtime, ByteList num) { - return ConvertBytes.byteListToInum(runtime, num, 10, true); - } - - %%{ - machine JSON_float; - include JSON_common; - - write data; - - action exit { - fhold; - fbreak; - } - - main := '-'? - ( ( ( '0' | [1-9][0-9]* ) '.' [0-9]+ ( [Ee] [+\-]?[0-9]+ )? ) - | ( ( '0' | [1-9][0-9]* ) ( [Ee] [+\-]? [0-9]+ ) ) ) - ( ^[0-9Ee.\-]? @exit ); - }%% - - void parseFloat(ThreadContext context, ParserResult res, int p, int pe) { - int new_p = parseFloatInternal(p, pe); - if (new_p == -1) { - res.update(null, p); - return; - } - final ByteList num = absSubSequence(p, new_p); - IRubyObject number = config.decimalFactory.apply(context, num); - - res.update(number, new_p + 1); - } - - int parseFloatInternal(int p, int pe) { - int cs; - - %% write init; - int memo = p; - %% write exec; - - if (cs < JSON_float_first_final) { - return -1; - } - - return p; - } - - %%{ - machine JSON_string; - include JSON_common; - - write data; - - action parse_string { - int offset = byteList.begin(); - ByteList decoded = decoder.decode(context, byteList, memo + 1 - offset, - p - offset); - result = context.runtime.newString(decoded); - if (result == null) { - fhold; - fbreak; - } else { - fexec p + 1; - } - } - - action exit { - fhold; - fbreak; - } - - main := '"' - ( ( ^(["\\]|0..0x1f) - | '\\'["\\/bfnrt] - | '\\u'[0-9a-fA-F]{4} - | '\\'^(["\\/bfnrtu]|0..0x1f) - )* %parse_string - ) '"' @exit; - }%% - - void parseString(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - IRubyObject result = null; - - %% write init; - int memo = p; - %% write exec; - - if (config.createAdditions) { - RubyHash matchString = config.match_string; - if (matchString != null) { - final IRubyObject[] memoArray = { result, null }; - try { - matchString.visitAll(context, MATCH_VISITOR, memoArray); - } catch (JumpException e) { } - if (memoArray[1] != null) { - RubyClass klass = (RubyClass) memoArray[1]; - if (klass.respondsTo("json_creatable?") && - klass.callMethod(context, "json_creatable?").isTrue()) { - if (config.deprecatedCreateAdditions) { - context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); - } - result = klass.callMethod(context, "json_create", result); - } - } - } - } - - if (cs >= JSON_string_first_final && result != null) { - if (result instanceof RubyString) { - RubyString string = (RubyString)result; - string.setEncoding(UTF8Encoding.INSTANCE); - string.clearCodeRange(); - if (config.freeze) { - string.setFrozen(true); - string = context.runtime.freezeAndDedupString(string); - } - res.update(string, p + 1); - } else { - res.update(result, p + 1); - } - } else { - res.update(null, p + 1); - } - } - - %%{ - machine JSON_array; - include JSON_common; - - write data; - - action allow_trailing_comma { config.allowTrailingComma } - - action parse_value { - parseValue(context, res, fpc, pe); - if (res.result == null) { - fhold; - fbreak; - } else { - if (config.arrayClass == context.runtime.getArray()) { - ((RubyArray)result).append(res.result); - } else { - result.callMethod(context, "<<", res.result); - } - fexec res.p; - } - } - - action exit { - fhold; - fbreak; - } - - next_element = value_separator ignore* begin_value >parse_value; - - main := begin_array - ignore* - ( ( begin_value >parse_value - ignore* ) - ( ignore* - next_element - ignore* )* ( (value_separator ignore*) when allow_trailing_comma )? )? - ignore* - end_array @exit; - }%% - - void parseArray(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - - if (config.maxNesting > 0 && currentNesting > config.maxNesting) { - throw newException(context, Utils.M_NESTING_ERROR, - "nesting of " + currentNesting + " is too deep"); - } - - IRubyObject result; - if (config.arrayClass == context.runtime.getArray()) { - result = RubyArray.newArray(context.runtime); - } else { - result = config.arrayClass.newInstance(context, - IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); - } - - %% write init; - %% write exec; - - if (cs >= JSON_array_first_final) { - res.update(result, p + 1); - } else { - throw unexpectedToken(context, p, pe); - } - } - - %%{ - machine JSON_object; - include JSON_common; - - write data; - - action allow_trailing_comma { config.allowTrailingComma } - - action parse_value { - parseValue(context, res, fpc, pe); - if (res.result == null) { - fhold; - fbreak; - } else { - if (config.objectClass == context.runtime.getHash()) { - ((RubyHash)result).op_aset(context, lastName, res.result); - } else { - Helpers.invoke(context, result, "[]=", lastName, res.result); - } - fexec res.p; - } - } - - action parse_name { - parseString(context, res, fpc, pe); - if (res.result == null) { - fhold; - fbreak; - } else { - RubyString name = (RubyString)res.result; - if (config.symbolizeNames) { - lastName = name.intern(); - } else { - lastName = name; - } - fexec res.p; - } - } - - action exit { - fhold; - fbreak; - } - - pair = ignore* begin_name >parse_name ignore* name_separator - ignore* begin_value >parse_value; - next_pair = ignore* value_separator pair; - - main := ( - begin_object - (pair (next_pair)*((ignore* value_separator) when allow_trailing_comma)?)? ignore* - end_object - ) @exit; - }%% - - void parseObject(ThreadContext context, ParserResult res, int p, int pe) { - int cs; - IRubyObject lastName = null; - boolean objectDefault = true; - - if (config.maxNesting > 0 && currentNesting > config.maxNesting) { - throw newException(context, Utils.M_NESTING_ERROR, - "nesting of " + currentNesting + " is too deep"); - } - - // this is guaranteed to be a RubyHash due to the earlier - // allocator test at OptionsReader#getClass - IRubyObject result; - if (config.objectClass == context.runtime.getHash()) { - result = RubyHash.newHash(context.runtime); - } else { - objectDefault = false; - result = config.objectClass.newInstance(context, - IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); - } - - %% write init; - %% write exec; - - if (cs < JSON_object_first_final) { - res.update(null, p + 1); - return; - } - - IRubyObject returnedResult = result; - - // attempt to de-serialize object - if (config.createAdditions) { - IRubyObject vKlassName; - if (objectDefault) { - vKlassName = ((RubyHash)result).op_aref(context, config.createId); - } else { - vKlassName = result.callMethod(context, "[]", config.createId); - } - - if (!vKlassName.isNil()) { - // might throw ArgumentError, we let it propagate - IRubyObject klass = config.info.jsonModule.get(). - callMethod(context, "deep_const_get", vKlassName); - if (klass.respondsTo("json_creatable?") && - klass.callMethod(context, "json_creatable?").isTrue()) { - if (config.deprecatedCreateAdditions) { - context.runtime.getWarnings().warn("JSON.load implicit support for `create_additions: true` is deprecated and will be removed in 3.0, use JSON.unsafe_load or explicitly pass `create_additions: true`"); - } - - returnedResult = klass.callMethod(context, "json_create", result); - } - } - } - res.update(returnedResult, p + 1); - } - - %%{ - machine JSON; - include JSON_common; - - write data; - - action parse_value { - parseValue(context, res, fpc, pe); - if (res.result == null) { - fhold; - fbreak; - } else { - result = res.result; - fexec res.p; - } - } - - main := ignore* - ( begin_value >parse_value) - ignore*; - }%% - - public IRubyObject parseImplementation(ThreadContext context) { - int cs; - int p, pe; - IRubyObject result = null; - ParserResult res = new ParserResult(); - - %% write init; - p = byteList.begin(); - pe = p + byteList.length(); - %% write exec; - - if (cs >= JSON_first_final && p == pe) { - return result; - } else { - throw unexpectedToken(context, p, pe); - } - } - - public IRubyObject parse(ThreadContext context) { - return parseImplementation(context); - } - - /** - * Updates the "view" bytelist with the new offsets and returns it. - * @param absStart - * @param absEnd - */ - private ByteList absSubSequence(int absStart, int absEnd) { - view.setBegin(absStart); - view.setRealSize(absEnd - absStart); - return view; - } - - /** - * Retrieves a constant directly descended from the JSON module. - * @param name The constant name - */ - private IRubyObject getConstant(String name) { - return config.info.jsonModule.get().getConstant(name); - } - - private RaiseException newException(ThreadContext context, String className, String message) { - return Utils.newException(context, className, message); - } - - private RaiseException newException(ThreadContext context, String className, RubyString message) { - return Utils.newException(context, className, message); - } - - RubyHash.VisitorWithState MATCH_VISITOR = new RubyHash.VisitorWithState() { - @Override - public void visit(ThreadContext context, RubyHash self, IRubyObject pattern, IRubyObject klass, int index, IRubyObject[] state) { - if (pattern.callMethod(context, "===", state[0]).isTrue()) { - state[1] = klass; - throw JumpException.SPECIAL_JUMP; - } - } - }; - } -} diff --git a/java/src/json/ext/ParserService.java b/java/src/json/ext/ParserService.java index 88aa9674b..2d49bd1a4 100644 --- a/java/src/json/ext/ParserService.java +++ b/java/src/json/ext/ParserService.java @@ -27,8 +27,8 @@ public boolean basicLoad(Ruby runtime) throws IOException { RubyModule jsonExtModule = info.jsonModule.get().defineModuleUnder("Ext"); RubyClass parserConfigClass = jsonExtModule.defineClassUnder("ParserConfig", runtime.getObject(), - ParserConfig.ALLOCATOR); - parserConfigClass.defineAnnotatedMethods(ParserConfig.class); + Parser.ALLOCATOR); + parserConfigClass.defineAnnotatedMethods(Parser.class); return true; } } diff --git a/java/src/json/ext/RuntimeInfo.java b/java/src/json/ext/RuntimeInfo.java index f712dc251..9906a5d95 100644 --- a/java/src/json/ext/RuntimeInfo.java +++ b/java/src/json/ext/RuntimeInfo.java @@ -30,8 +30,6 @@ final class RuntimeInfo { WeakReference jsonModule; /** JSON::Ext::Generator::State */ WeakReference generatorStateClass; - /** JSON::SAFE_STATE_PROTOTYPE */ - WeakReference safeStatePrototype; private RuntimeInfo() { } @@ -67,15 +65,4 @@ public static RuntimeInfo forRuntime(Ruby runtime) { return cache; } } - - public GeneratorState getSafeStatePrototype(ThreadContext context) { - if (safeStatePrototype == null) { - IRubyObject value = jsonModule.get().getConstant("SAFE_STATE_PROTOTYPE"); - if (!(value instanceof GeneratorState)) { - throw context.runtime.newTypeError(value, generatorStateClass.get()); - } - safeStatePrototype = new WeakReference<>((GeneratorState) value); - } - return safeStatePrototype.get(); - } } diff --git a/java/src/json/ext/Ryu.java b/java/src/json/ext/Ryu.java new file mode 100644 index 000000000..570cd24c4 --- /dev/null +++ b/java/src/json/ext/Ryu.java @@ -0,0 +1,770 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +// +// --- +// +// Apache License +// Version 2.0, January 2004 +// http://www.apache.org/licenses/ +// +// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +// +// 1. Definitions. +// +// "License" shall mean the terms and conditions for use, reproduction, +// and distribution as defined by Sections 1 through 9 of this document. +// +// "Licensor" shall mean the copyright owner or entity authorized by +// the copyright owner that is granting the License. +// +// "Legal Entity" shall mean the union of the acting entity and all +// other entities that control, are controlled by, or are under common +// control with that entity. For the purposes of this definition, +// "control" means (i) the power, direct or indirect, to cause the +// direction or management of such entity, whether by contract or +// otherwise, or (ii) ownership of fifty percent (50%) or more of the +// outstanding shares, or (iii) beneficial ownership of such entity. +// +// "You" (or "Your") shall mean an individual or Legal Entity +// exercising permissions granted by this License. +// +// "Source" form shall mean the preferred form for making modifications, +// including but not limited to software source code, documentation +// source, and configuration files. +// +// "Object" form shall mean any form resulting from mechanical +// transformation or translation of a Source form, including but +// not limited to compiled object code, generated documentation, +// and conversions to other media types. +// +// "Work" shall mean the work of authorship, whether in Source or +// Object form, made available under the License, as indicated by a +// copyright notice that is included in or attached to the work +// (an example is provided in the Appendix below). +// +// "Derivative Works" shall mean any work, whether in Source or Object +// form, that is based on (or derived from) the Work and for which the +// editorial revisions, annotations, elaborations, or other modifications +// represent, as a whole, an original work of authorship. For the purposes +// of this License, Derivative Works shall not include works that remain +// separable from, or merely link (or bind by name) to the interfaces of, +// the Work and Derivative Works thereof. +// +// "Contribution" shall mean any work of authorship, including +// the original version of the Work and any modifications or additions +// to that Work or Derivative Works thereof, that is intentionally +// submitted to Licensor for inclusion in the Work by the copyright owner +// or by an individual or Legal Entity authorized to submit on behalf of +// the copyright owner. For the purposes of this definition, "submitted" +// means any form of electronic, verbal, or written communication sent +// to the Licensor or its representatives, including but not limited to +// communication on electronic mailing lists, source code control systems, +// and issue tracking systems that are managed by, or on behalf of, the +// Licensor for the purpose of discussing and improving the Work, but +// excluding communication that is conspicuously marked or otherwise +// designated in writing by the copyright owner as "Not a Contribution." +// +// "Contributor" shall mean Licensor and any individual or Legal Entity +// on behalf of whom a Contribution has been received by Licensor and +// subsequently incorporated within the Work. +// +// 2. Grant of Copyright License. Subject to the terms and conditions of +// this License, each Contributor hereby grants to You a perpetual, +// worldwide, non-exclusive, no-charge, royalty-free, irrevocable +// copyright license to reproduce, prepare Derivative Works of, +// publicly display, publicly perform, sublicense, and distribute the +// Work and such Derivative Works in Source or Object form. +// +// 3. Grant of Patent License. Subject to the terms and conditions of +// this License, each Contributor hereby grants to You a perpetual, +// worldwide, non-exclusive, no-charge, royalty-free, irrevocable +// (except as stated in this section) patent license to make, have made, +// use, offer to sell, sell, import, and otherwise transfer the Work, +// where such license applies only to those patent claims licensable +// by such Contributor that are necessarily infringed by their +// Contribution(s) alone or by combination of their Contribution(s) +// with the Work to which such Contribution(s) was submitted. If You +// institute patent litigation against any entity (including a +// cross-claim or counterclaim in a lawsuit) alleging that the Work +// or a Contribution incorporated within the Work constitutes direct +// or contributory patent infringement, then any patent licenses +// granted to You under this License for that Work shall terminate +// as of the date such litigation is filed. +// +// 4. Redistribution. You may reproduce and distribute copies of the +// Work or Derivative Works thereof in any medium, with or without +// modifications, and in Source or Object form, provided that You +// meet the following conditions: +// +// (a) You must give any other recipients of the Work or +// Derivative Works a copy of this License; and +// +// (b) You must cause any modified files to carry prominent notices +// stating that You changed the files; and +// +// (c) You must retain, in the Source form of any Derivative Works +// that You distribute, all copyright, patent, trademark, and +// attribution notices from the Source form of the Work, +// excluding those notices that do not pertain to any part of +// the Derivative Works; and +// +// (d) If the Work includes a "NOTICE" text file as part of its +// distribution, then any Derivative Works that You distribute must +// include a readable copy of the attribution notices contained +// within such NOTICE file, excluding those notices that do not +// pertain to any part of the Derivative Works, in at least one +// of the following places: within a NOTICE text file distributed +// as part of the Derivative Works; within the Source form or +// documentation, if provided along with the Derivative Works; or, +// within a display generated by the Derivative Works, if and +// wherever such third-party notices normally appear. The contents +// of the NOTICE file are for informational purposes only and +// do not modify the License. You may add Your own attribution +// notices within Derivative Works that You distribute, alongside +// or as an addendum to the NOTICE text from the Work, provided +// that such additional attribution notices cannot be construed +// as modifying the License. +// +// You may add Your own copyright statement to Your modifications and +// may provide additional or different license terms and conditions +// for use, reproduction, or distribution of Your modifications, or +// for any such Derivative Works as a whole, provided Your use, +// reproduction, and distribution of the Work otherwise complies with +// the conditions stated in this License. +// +// 5. Submission of Contributions. Unless You explicitly state otherwise, +// any Contribution intentionally submitted for inclusion in the Work +// by You to the Licensor shall be under the terms and conditions of +// this License, without any additional terms or conditions. +// Notwithstanding the above, nothing herein shall supersede or modify +// the terms of any separate license agreement you may have executed +// with Licensor regarding such Contributions. +// +// 6. Trademarks. This License does not grant permission to use the trade +// names, trademarks, service marks, or product names of the Licensor, +// except as required for reasonable and customary use in describing the +// origin of the Work and reproducing the content of the NOTICE file. +// +// 7. Disclaimer of Warranty. Unless required by applicable law or +// agreed to in writing, Licensor provides the Work (and each +// Contributor provides its Contributions) on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied, including, without limitation, any warranties or conditions +// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +// PARTICULAR PURPOSE. You are solely responsible for determining the +// appropriateness of using or redistributing the Work and assume any +// risks associated with Your exercise of permissions under this License. +// +// 8. Limitation of Liability. In no event and under no legal theory, +// whether in tort (including negligence), contract, or otherwise, +// unless required by applicable law (such as deliberate and grossly +// negligent acts) or agreed to in writing, shall any Contributor be +// liable to You for damages, including any direct, indirect, special, +// incidental, or consequential damages of any character arising as a +// result of this License or out of the use or inability to use the +// Work (including but not limited to damages for loss of goodwill, +// work stoppage, computer failure or malfunction, or any and all +// other commercial damages or losses), even if such Contributor +// has been advised of the possibility of such damages. +// +// 9. Accepting Warranty or Additional Liability. While redistributing +// the Work or Derivative Works thereof, You may choose to offer, +// and charge a fee for, acceptance of support, warranty, indemnity, +// or other liability obligations and/or rights consistent with this +// License. However, in accepting such obligations, You may act only +// on Your own behalf and on Your sole responsibility, not on behalf +// of any other Contributor, and only if You agree to indemnify, +// defend, and hold each Contributor harmless for any liability +// incurred by, or claims asserted against, such Contributor by reason +// of your accepting any such warranty or additional liability. +// +// END OF TERMS AND CONDITIONS +// +// APPENDIX: How to apply the Apache License to your work. +// +// To apply the Apache License to your work, attach the following +// boilerplate notice, with the fields enclosed by brackets "[]" +// replaced with your own identifying information. (Don't include +// the brackets!) The text should be enclosed in the appropriate +// comment syntax for the file format. We also recommend that a +// file or class name and description of purpose be included on the +// same "printed page" as the copyright notice for easier +// identification within third-party archives. +// +// Copyright [yyyy] [name of copyright owner] +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// --- +// +// Boost Software License - Version 1.0 - August 17th, 2003 +// +// Permission is hereby granted, free of charge, to any person or organization +// obtaining a copy of the software and accompanying documentation covered by +// this license (the "Software") to use, reproduce, display, distribute, +// execute, and transmit the Software, and to prepare derivative works of the +// Software, and to permit third-parties to whom the Software is furnished to +// do so, all subject to the following: +// +// The copyright notices in the Software and this entire statement, including +// the above license grant, this restriction and the following disclaimer, +// must be included in all copies of the Software, in whole or in part, and +// all derivative works of the Software, unless such copies or derivative +// works are solely in the form of machine-executable object code generated by +// a source language processor. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// --- +// Minimal Ryu implementation adapted for Ruby JSON gem by Josef Šimánek +// Optimized for pre-extracted mantissa/exponent from JSON parsing +// This is a stripped-down version containing only what's needed for +// converting decimal mantissa+exponent to IEEE 754 double precision. +package json.ext; + +// Note: This is manually translated from ext/json/ext/vendor/ryu.h +public class Ryu { + static final int DOUBLE_POW5_INV_BITCOUNT = 125; + static final int DOUBLE_POW5_BITCOUNT = 125; + static long[][] DOUBLE_POW5_INV_SPLIT = { + { 1L, 2305843009213693952L}, { -7378697629483820646L, 1844674407370955161L}, + { 5165088340638674453L, 1475739525896764129L}, { 7821419487252849886L, 1180591620717411303L}, + { 8824922364862649494L, 1888946593147858085L}, { 7059937891890119595L, 1511157274518286468L}, + { -5420096130713635294L, 1208925819614629174L}, { -8672153809141816470L, 1934281311383406679L}, + { -6937723047313453176L, 1547425049106725343L}, { -1860829623108852217L, 1237940039285380274L}, + { -2977327396974163548L, 1980704062856608439L}, { -2381861917579330838L, 1584563250285286751L}, + { 9162556910162266299L, 1267650600228229401L}, { 7281393426775805432L, 2028240960365167042L}, + { -1553582888063176301L, 1622592768292133633L}, { 2446482504291369283L, 1298074214633706907L}, + { 7603720821608101175L, 2076918743413931051L}, { 2393627842544570617L, 1661534994731144841L}, + { -1774446540706253830L, 1329227995784915872L}, { -6528463279871916451L, 2126764793255865396L}, + { 5845275820328197809L, 1701411834604692317L}, { -2702476973221262399L, 1361129467683753853L}, + { 3054734472329800808L, 2177807148294006166L}, { -1245561236878069677L, 1742245718635204932L}, + { 6382248639981364905L, 1393796574908163946L}, { 2832900194486363201L, 2230074519853062314L}, + { 5955668970331000884L, 1784059615882449851L}, { 1075186361522890384L, 1427247692705959881L}, + { -5658399451047196032L, 2283596308329535809L}, { -4526719560837756825L, 1826877046663628647L}, + { 3757321980813615186L, 1461501637330902918L}, { -8062188859574838821L, 1169201309864722334L}, + { 5547241898389809503L, 1870722095783555735L}, { 4437793518711847602L, 1496577676626844588L}, + { -7517811629256252888L, 1197262141301475670L}, { -960452162584273651L, 1915619426082361072L}, + { 6610335899416401726L, 1532495540865888858L}, { -5779777724692609589L, 1225996432692711086L}, + { -5558295544766265019L, 1961594292308337738L}, { -757287621071101692L, 1569275433846670190L}, + { -4295178911598791677L, 1255420347077336152L}, { 7885109000409574610L, 2008672555323737844L}, + { -8449308058639981605L, 1606938044258990275L}, { 7997948812055656009L, 1285550435407192220L}, + { -5650025974420502002L, 2056880696651507552L}, { 2858676849947419045L, 1645504557321206042L}, + { -5091756149525885410L, 1316403645856964833L}, { -768112209757596011L, 2106245833371143733L}, + { 3074859046935833515L, 1684996666696914987L}, { -4918810391935153834L, 1347997333357531989L}, + { -7870096627096246135L, 2156795733372051183L}, { -2606728486935086585L, 1725436586697640946L}, + { 8982663654677661702L, 1380349269358112757L}, { -385133411483382570L, 2208558830972980411L}, + { -7686804358670526703L, 1766847064778384329L}, { -6149443486936421362L, 1413477651822707463L}, + { -2460411949614453533L, 2261564242916331941L}, { 9099716884534168143L, 1809251394333065553L}, + { -3788272936598396455L, 1447401115466452442L}, { 4348079280205103483L, 1157920892373161954L}, + { -4111119595897565398L, 1852673427797059126L}, { 7779150767507678651L, 1482138742237647301L}, + { 2533971799264232598L, 1185710993790117841L}, { -3324342750661048490L, 1897137590064188545L}, + { -6348823015270749115L, 1517710072051350836L}, { 5988988032009131678L, 1214168057641080669L}, + { -1485665593011120286L, 1942668892225729070L}, { -4877881289150806552L, 1554135113780583256L}, + { 7165741412905085728L, 1243308091024466605L}, { -6981557813061414451L, 1989292945639146568L}, + { -1895897435707221237L, 1591434356511317254L}, { -1516717948565776990L, 1273147485209053803L}, + { 4951948911778577463L, 2037035976334486086L}, { 272210314680951647L, 1629628781067588869L}, + { 3907117066486671641L, 1303703024854071095L}, { 6251387306378674625L, 2085924839766513752L}, + { -2377587784380880946L, 1668739871813211001L}, { 9165976216721026213L, 1334991897450568801L}, + { 7286864317269821294L, 2135987035920910082L}, { -1549206175667963611L, 1708789628736728065L}, + { -4928713755276281212L, 1367031702989382452L}, { 6871453250525591353L, 2187250724783011924L}, + { 9186511415162383406L, 1749800579826409539L}, { -7408186126837734568L, 1399840463861127631L}, + { -8163748988198464986L, 2239744742177804210L}, { 8226396068408869304L, 1791795793742243368L}, + { -4486929589498635526L, 1433436634993794694L}, { -7179087343197816842L, 2293498615990071511L}, + { 5324776569667477496L, 1834798892792057209L}, { 7949170070475892320L, 1467839114233645767L}, + { -1019361573103106790L, 1174271291386916613L}, { 5747719112518849781L, 1878834066219066582L}, + { -2780522339468740821L, 1503067252975253265L}, { -5913766686316902980L, 1202453802380202612L}, + { 5295368560860596524L, 1923926083808324180L}, { 4236294848688477220L, 1539140867046659344L}, + { 7078384693692692099L, 1231312693637327475L}, { -7121328563801244258L, 1970100309819723960L}, + { 9060332407926645887L, 1576080247855779168L}, { -3819780517884414260L, 1260864198284623334L}, + { -6111648828615062817L, 2017382717255397335L}, { -8578667877633960576L, 1613906173804317868L}, + { -3173585487365258138L, 1291124939043454294L}, { -5077736779784413021L, 2065799902469526871L}, + { 7005857020398200553L, 1652639921975621497L}, { -1774012013165260204L, 1322111937580497197L}, + { -6527768035806326650L, 2115379100128795516L}, { 5845832015580669650L, 1692303280103036413L}, + { -6391380831761195250L, 1353842624082429130L}, { 841837113407818570L, 2166148198531886609L}, + { 4362818505468165179L, 1732918558825509287L}, { -3888442825109288503L, 1386334847060407429L}, + { -6221508520174861605L, 2218135755296651887L}, { 2401490813343931363L, 1774508604237321510L}, + { 1921192650675145090L, 1419606883389857208L}, { -615440573661678179L, 2271371013423771532L}, + { 6886345170554478103L, 1817096810739017226L}, { 1819727321701672159L, 1453677448591213781L}, + { -2233566957380572596L, 1162941958872971024L}, { -3573707131808916153L, 1860707134196753639L}, + { -2858965705447132922L, 1488565707357402911L}, { 8780873879868024632L, 1190852565885922329L}, + { 2981351763563108441L, 1905364105417475727L}, { -4993616218633333894L, 1524291284333980581L}, + { 7073153469319063855L, 1219433027467184465L}, { -7129698522799049449L, 1951092843947495144L}, + { -5703758818239239559L, 1560874275157996115L}, { -8252355869333301970L, 1248699420126396892L}, + { 1553625868034358140L, 1997919072202235028L}, { 8621598323911307159L, 1598335257761788022L}, + { -481418970354774919L, 1278668206209430417L}, { -4459619167309550194L, 2045869129935088668L}, + { 121653480894270168L, 1636695303948070935L}, { 97322784715416134L, 1309356243158456748L}, + { -3533632359197244509L, 2094969989053530796L}, { 8241140556867935363L, 1675975991242824637L}, + { -785785183989472356L, 1340780792994259709L}, { -1257256294383155770L, 2145249268790815535L}, + { -4695153850248434939L, 1716199415032652428L}, { -66774265456837628L, 1372959532026121942L}, + { -3796187639472850528L, 2196735251241795108L}, { 652398703163629901L, 1757388200993436087L}, + { -6856778666952916726L, 1405910560794748869L}, { 7475898206584884855L, 2249456897271598191L}, + { 2291369750525997561L, 1799565517817278553L}, { 9211793429904618695L, 1439652414253822842L}, + { -18525771120251381L, 2303443862806116547L}, { 7363877012587619542L, 1842755090244893238L}, + { -5176944834155635336L, 1474204072195914590L}, { -7830904682066418592L, 1179363257756731672L}, + { 2227947767661371545L, 1886981212410770676L}, { -1906990600612813087L, 1509584969928616540L}, + { -5214941295232160793L, 1207667975942893232L}, { 6413489186596184024L, 1932268761508629172L}, + { -2247906280206873427L, 1545815009206903337L}, { 5580372605318321905L, 1236652007365522670L}, + { 8928596168509315048L, 1978643211784836272L}, { -235820694676368608L, 1582914569427869017L}, + { 7190041073742725760L, 1266331655542295214L}, { 436019273762630246L, 2026130648867672343L}, + { 7727513048493924843L, 1620904519094137874L}, { -8575384820172501418L, 1296723615275310299L}, + { 4726128361433549347L, 2074757784440496479L}, { 7470251503888749801L, 1659806227552397183L}, + { -5091845241114731129L, 1327844982041917746L}, { -4457603571041659483L, 2124551971267068394L}, + { -3566082856833327587L, 1699641577013654715L}, { -6542215100208572392L, 1359713261610923772L}, + { 4289851098633925465L, 2175541218577478036L}, { -257467935834769951L, 1740432974861982428L}, + { 3483374466074094362L, 1392346379889585943L}, { 1884050330976640656L, 2227754207823337509L}, + { 5196589079523222848L, 1782203366258670007L}, { -3221426365865242368L, 1425762693006936005L}, + { 5913764258841343181L, 2281220308811097609L}, { 8420360221814984868L, 1824976247048878087L}, + { -642409452031832752L, 1459980997639102469L}, { -513927561625466201L, 1167984798111281975L}, + { -8200981728084566569L, 1868775676978051161L}, { 4507261061758077715L, 1495020541582440929L}, + { 7295157664148372495L, 1196016433265952743L}, { 7982903447895485668L, 1913626293225524389L}, + { -8371072500651252758L, 1530901034580419511L}, { 4371188443704728763L, 1224720827664335609L}, + { -4074144934298164949L, 1959553324262936974L}, { -3259315947438531959L, 1567642659410349579L}, + { -2607452757950825567L, 1254114127528279663L}, { 3206773216762499739L, 2006582604045247462L}, + { -4813279056073820855L, 1605266083236197969L}, { -3850623244859056684L, 1284212866588958375L}, + { 4907049252451240275L, 2054740586542333401L}, { 236290587219081897L, 1643792469233866721L}, + { -3500316344966644806L, 1315033975387093376L}, { -1911157337204721366L, 2104054360619349402L}, + { 5849771759720043554L, 1683243488495479522L}, { -2698880221707785803L, 1346594790796383617L}, + { -8007557169474367609L, 2154551665274213788L}, { -2716696920837583764L, 1723641332219371030L}, + { -5862706351411977334L, 1378913065775496824L}, { 9066413911450387881L, 2206260905240794919L}, + { -7504264129807330988L, 1765008724192635935L}, { 8753983955121776503L, 1412006979354108748L}, + { -8129718560256619535L, 2259211166966573997L}, { 874922781278525018L, 1807368933573259198L}, + { 8078635854506640661L, 1445895146858607358L}, { -4605137760620418441L, 1156716117486885886L}, + { -3678871602250759182L, 1850745787979017418L}, { 746251532941302978L, 1480596630383213935L}, + { 597001226353042382L, 1184477304306571148L}, { -2734146852577042512L, 1895163686890513836L}, + { 8880728962164096960L, 1516130949512411069L}, { -7652812089236363725L, 1212904759609928855L}, + { -1176452898552450990L, 1940647615375886168L}, { 2748186495899949531L, 1552518092300708935L}, + { 2198549196719959625L, 1242014473840567148L}, { -171670099989974923L, 1987223158144907436L}, + { -7516033709475800585L, 1589778526515925949L}, { -6012826967580640468L, 1271822821212740759L}, + { 8826220925580526867L, 2034916513940385215L}, { 7060976740464421494L, 1627933211152308172L}, + { -1729916237112283451L, 1302346568921846537L}, { -6457214794121563846L, 2083754510274954460L}, + { -8855120650039161400L, 1667003608219963568L}, { -3394747705289418796L, 1333602886575970854L}, + { -5431596328463070074L, 2133764618521553367L}, { 3033420566713364587L, 1707011694817242694L}, + { 6116085268112601993L, 1365609355853794155L}, { -8661007644729388428L, 2184974969366070648L}, + { -3239457301041600419L, 1747979975492856518L}, { 1097782973908629988L, 1398383980394285215L}, + { 1756452758253807981L, 2237414368630856344L}, { 5094511021344956708L, 1789931494904685075L}, + { 4075608817075965366L, 1431945195923748060L}, { 6520974107321544586L, 2291112313477996896L}, + { 1527430471115325346L, 1832889850782397517L}, { -6156753252591560370L, 1466311880625918013L}, + { -1236053787331337972L, 1173049504500734410L}, { 9090360384495590213L, 1876879207201175057L}, + { -106409321887348476L, 1501503365760940045L}, { -3774476272251789104L, 1201202692608752036L}, + { -2349813220860952243L, 1921924308174003258L}, { 1809498238053148529L, 1537539446539202607L}, + { -5931099039041301823L, 1230031557231362085L}, { 1578287981759648052L, 1968050491570179337L}, + { -6116067244076102204L, 1574440393256143469L}, { -4892853795260881763L, 1259552314604914775L}, + { 3239480371808320148L, 2015283703367863641L}, { -1097764517295254205L, 1612226962694290912L}, + { 6500486015647617283L, 1289781570155432730L}, { -8045966448673363964L, 2063650512248692368L}, + { -2747424344196780848L, 1650920409798953894L}, { -2197939475357424678L, 1320736327839163115L}, + { 7551343283653851484L, 2113178124542660985L}, { 6041074626923081187L, 1690542499634128788L}, + { -6235186742687266020L, 1352433999707303030L}, { 1091747655926105338L, 2163894399531684849L}, + { 4562746939482794594L, 1731115519625347879L}, { 7339546366328145998L, 1384892415700278303L}, + { 8053925371383123274L, 2215827865120445285L}, { 6443140297106498619L, 1772662292096356228L}, + { -5913534206540532074L, 1418129833677084982L}, { 5295740528502789974L, 2269007733883335972L}, + { -3142105206681588667L, 1815206187106668777L}, { 4865013464138549713L, 1452164949685335022L}, + { -3486686858172980876L, 1161731959748268017L}, { 9178696285890871890L, 1858771135597228828L}, + { -3725089415513033457L, 1487016908477783062L}, { 4398626097073393881L, 1189613526782226450L}, + { 7037801755317430209L, 1903381642851562320L}, { 5630241404253944167L, 1522705314281249856L}, + { 814844308661245011L, 1218164251424999885L}, { 1303750893857992017L, 1949062802279999816L}, + { -2646348099655516710L, 1559250241823999852L}, { 5261619149759407279L, 1247400193459199882L}, + { -6338804619352589647L, 1995840309534719811L}, { 5997002748743659252L, 1596672247627775849L}, + { 8486951013736837725L, 1277337798102220679L}, { 2511075177753209390L, 2043740476963553087L}, + { -5369837487281253134L, 1634992381570842469L}, { -4295869989825002507L, 1307993905256673975L}, + { 4194654460505726958L, 2092790248410678361L}, { -333625246337328757L, 1674232198728542688L}, + { 3422448617672047318L, 1339385758982834151L}, { -1902779841208544938L, 2143017214372534641L}, + { -8900921502450656597L, 1714413771498027713L}, { -3431388387218614954L, 1371531017198422170L}, + { 5577825024675947042L, 2194449627517475473L}, { -6605786424484973336L, 1755559702013980378L}, + { -1595280324846068345L, 1404447761611184302L}, { -6241797334495619676L, 2247116418577894884L}, + { -4993437867596495741L, 1797693134862315907L}, { 3383947335406624054L, 1438154507889852726L}, + { -1964381892833222160L, 2301047212623764361L}, { -8950203143750398374L, 1840837770099011489L}, + { -7160162515000318699L, 1472670216079209191L}, { 5339916432225476010L, 1178136172863367353L}, + { 4854517476818851293L, 1885017876581387765L}, { 3883613981455081034L, 1508014301265110212L}, + { -4271806444319755819L, 1206411441012088169L}, { -6834890310911609310L, 1930258305619341071L}, + { 5600134195496443521L, 1544206644495472857L}, { -2898590273086665829L, 1235365315596378285L}, + { 6430302007287065643L, 1976584504954205257L}, { -2234456023654168132L, 1581267603963364205L}, + { -5476913633665244829L, 1265014083170691364L}, { -8763061813864391727L, 2024022533073106183L}, + { -3321100636349603058L, 1619218026458484946L}, { 8411165935146048523L, 1295374421166787957L}, + { -1299529762733963656L, 2072599073866860731L}, { -8418321439670991571L, 1658079259093488585L}, + { 8022738107230848036L, 1326463407274790868L}, { 9147032156827446534L, 2122341451639665389L}, + { -7439769533505684065L, 1697873161311732311L}, { 5116230817421183718L, 1358298529049385849L}, + { -2882077136351837022L, 2173277646479017358L}, { 1383687105660440706L, 1738622117183213887L}, + { -6271747944955468082L, 1390897693746571109L}, { 8411947361780802685L, 2225436309994513775L}, + { 6729557889424642148L, 1780349047995611020L}, { 5383646311539713719L, 1424279238396488816L}, + { 1235136468979721303L, 2278846781434382106L}, { -2701239639558133281L, 1823077425147505684L}, + { -2160991711646506624L, 1458461940118004547L}, { 5649904260166615347L, 1166769552094403638L}, + { 5350498001524674232L, 1866831283351045821L}, { 591049586477829062L, 1493465026680836657L}, + { -6905857960301557397L, 1194772021344669325L}, { 18673707743239135L, 1911635234151470921L}, + { -3674409848547319015L, 1529308187321176736L}, { 8128518565387875758L, 1223446549856941389L}, + { 1937583260394870242L, 1957514479771106223L}, { 8928764237799716840L, 1566011583816884978L}, + { -3925035053985957497L, 1252809267053507982L}, { 8477339172590109297L, 2004494827285612772L}, + { -596826291411733209L, 1603595861828490217L}, { 6901236596354434079L, 1282876689462792174L}, + { -26067890058636443L, 2052602703140467478L}, { 3668494502695001169L, 1642082162512373983L}, + { -8133250842069730034L, 1313665730009899186L}, { 9122891541139893884L, 2101865168015838698L}, + { -3769733211313815862L, 1681492134412670958L}, { 673562245690857633L, 1345193707530136767L}, + }; + static long[][] DOUBLE_POW5_SPLIT = { + { 0L, 1152921504606846976L}, { 0L, 1441151880758558720L}, + { 0L, 1801439850948198400L}, { 0L, 2251799813685248000L}, + { 0L, 1407374883553280000L}, { 0L, 1759218604441600000L}, + { 0L, 2199023255552000000L}, { 0L, 1374389534720000000L}, + { 0L, 1717986918400000000L}, { 0L, 2147483648000000000L}, + { 0L, 1342177280000000000L}, { 0L, 1677721600000000000L}, + { 0L, 2097152000000000000L}, { 0L, 1310720000000000000L}, + { 0L, 1638400000000000000L}, { 0L, 2048000000000000000L}, + { 0L, 1280000000000000000L}, { 0L, 1600000000000000000L}, + { 0L, 2000000000000000000L}, { 0L, 1250000000000000000L}, + { 0L, 1562500000000000000L}, { 0L, 1953125000000000000L}, + { 0L, 1220703125000000000L}, { 0L, 1525878906250000000L}, + { 0L, 1907348632812500000L}, { 0L, 1192092895507812500L}, + { 0L, 1490116119384765625L}, { 4611686018427387904L, 1862645149230957031L}, + { -8646911284551352320L, 1164153218269348144L}, { -6196953087261802496L, 1455191522836685180L}, + { -3134505340649865216L, 1818989403545856475L}, { -3918131675812331520L, 2273736754432320594L}, + { -4754675306596401152L, 1421085471520200371L}, { -5943344133245501440L, 1776356839400250464L}, + { -2817494148129488896L, 2220446049250313080L}, { -8678462870222012416L, 1387778780781445675L}, + { 7598665485932036096L, 1734723475976807094L}, { 274959820560269312L, 2168404344971008868L}, + { -9051522149004607488L, 1355252715606880542L}, { 2520655369026404352L, 1694065894508600678L}, + { -6072552825571770368L, 2117582368135750847L}, { -3795345515982356480L, 1323488980084844279L}, + { -4744181894977945600L, 1654361225106055349L}, { 3293144668132343808L, 2067951531382569187L}, + { -247627591630979072L, 1292469707114105741L}, { 8913837547316051968L, 1615587133892632177L}, + { -2692761121137098752L, 2019483917365790221L}, { -6294661719138074624L, 1262177448353618888L}, + { -3256641130495205376L, 1577721810442023610L}, { -8682487431546394624L, 1972152263052529513L}, + { -814868626289108736L, 1232595164407830945L}, { 8204786253993389888L, 1540743955509788682L}, + { 1032610780636961552L, 1925929944387235853L}, { 2951224747111794922L, 1203706215242022408L}, + { 3689030933889743652L, 1504632769052528010L}, { -4612083369492596243L, 1880790961315660012L}, + { -576709096719178700L, 1175494350822287507L}, { -720886370898973375L, 1469367938527859384L}, + { 3710578054803671186L, 1836709923159824231L}, { 26536550077201078L, 2295887403949780289L}, + { -6900943683842831182L, 1434929627468612680L}, { -4014493586376151074L, 1793662034335765850L}, + { 8816941072311974870L, 2242077542919707313L}, { -1406940857446097563L, 1401298464324817070L}, + { -6370362090235009857L, 1751623080406021338L}, { 5872105442488401391L, 2189528850507526673L}, + { -3247463126085830987L, 1368455531567204170L}, { -8671014926034676638L, 1710569414459005213L}, + { -1615396620688569989L, 2138211768073756516L}, { 1296220121283337709L, 1336382355046097823L}, + { -2991410866823215768L, 1670477943807622278L}, { -8350949601956407614L, 2088097429759527848L}, + { 6309871544845715001L, 1305060893599704905L}, { -5947718624225019960L, 1631326116999631131L}, + { -7434648280281274950L, 2039157646249538914L}, { -6952498184389490796L, 1274473528905961821L}, + { 532749306367912313L, 1593091911132452277L}, { 5277622651387278295L, 1991364888915565346L}, + { 7910200175544436838L, 1244603055572228341L}, { -3947307835851617664L, 1555753819465285426L}, + { 8900923260467641632L, 1944692274331606783L}, { -5966138008276193740L, 1215432671457254239L}, + { -7457672510345242175L, 1519290839321567799L}, { 9124653435777998898L, 1899113549151959749L}, + { 8008751406574943263L, 1186945968219974843L}, { 5399253239791291175L, 1483682460274968554L}, + { -2474305487115661840L, 1854603075343710692L}, { 759402079766405302L, 1159126922089819183L}, + { -3662433418719381276L, 1448908652612273978L}, { -9189727791826614499L, 1811135815765342473L}, + { -2263787702928492316L, 2263919769706678091L}, { 7808504722524468110L, 1414949856066673807L}, + { 5148944884728197234L, 1768687320083342259L}, { 1824495087482858639L, 2210859150104177824L}, + { 1140309429676786649L, 1381786968815111140L}, { 1425386787095983311L, 1727233711018888925L}, + { 6393419502297367043L, 2159042138773611156L}, { -5227484847918921406L, 1349401336733506972L}, + { -1922670041471263854L, 1686751670916883715L}, { -2403337551839079817L, 2108439588646104644L}, + { 803757039314269066L, 1317774742903815403L}, { -3606989719284551571L, 1647218428629769253L}, + { 4714634887749086344L, 2059023035787211567L}, { -8582568241225290795L, 1286889397367007229L}, + { -1504838264676837686L, 1608611746708759036L}, { 2730638187581340797L, 2010764683385948796L}, + { -7516723169616437810L, 1256727927116217997L}, { -172531925165771454L, 1570909908895272496L}, + { 4396021111970173586L, 1963637386119090621L}, { 5053356204195052443L, 1227273366324431638L}, + { -2906676781610960254L, 1534091707905539547L}, { -3633345977013700317L, 1917614634881924434L}, + { -4576684244847256650L, 1198509146801202771L}, { -5720855306059070813L, 1498136433501503464L}, + { -2539383114146450612L, 1872670541876879330L}, { -3892957455555225585L, 1170419088673049581L}, + { 4357175217410743827L, 1463023860841311977L}, { -8388589033518733928L, 1828779826051639971L}, + { 7961007781811134206L, 2285974782564549964L}, { -4247742173222816929L, 1428734239102843727L}, + { -5309677716528521161L, 1785917798878554659L}, { -6637097145660651452L, 2232397248598193324L}, + { -1842342706824213205L, 1395248280373870827L}, { -2302928383530266507L, 1744060350467338534L}, + { -7490346497840221037L, 2180075438084173168L}, { 6847748484918331612L, 1362547148802608230L}, + { -663686430706861293L, 1703183936003260287L}, { -829608038383576617L, 2128979920004075359L}, + { -518505023989735386L, 1330612450002547099L}, { -648131279987169232L, 1663265562503183874L}, + { -5421850118411349444L, 2079081953128979843L}, { 5834715712847682405L, 1299426220705612402L}, + { -1929977395795172801L, 1624282775882015502L}, { -7024157763171353905L, 2030353469852519378L}, + { -6695941611195790143L, 1268970918657824611L}, { -8369927013994737679L, 1586213648322280764L}, + { -5850722749066034194L, 1982767060402850955L}, { 5566670318688504437L, 1239229412751781847L}, + { 2346651879933242642L, 1549036765939727309L}, { 7545000868343941206L, 1936295957424659136L}, + { 4715625542714963254L, 1210184973390411960L}, { 5894531928393704067L, 1512731216738014950L}, + { -1855207126362645724L, 1890914020922518687L}, { -1159504453976653577L, 1181821263076574179L}, + { -1449380567470816972L, 1477276578845717724L}, { 2799960309088866689L, 1846595723557147156L}, + { -7473396843674234127L, 1154122327223216972L}, { -4730060036165404755L, 1442652909029021215L}, + { -5912575045206755944L, 1803316136286276519L}, { -7390718806508444929L, 2254145170357845649L}, + { -7513235640390177L, 1408840731473653530L}, { -4621077562977875625L, 1761050914342066913L}, + { 3447025083132431277L, 2201313642927583642L}, { 6766076695385157452L, 1375821026829739776L}, + { 8457595869231446815L, 1719776283537174720L}, { -7874749237170243097L, 2149720354421468400L}, + { 6607496772837067824L, 1343575221513417750L}, { -964001070808441028L, 1679469026891772187L}, + { -1205001338510551285L, 2099336283614715234L}, { -3058968845782788505L, 1312085177259197021L}, + { 5399660979626290177L, 1640106471573996277L}, { -7085481830749300991L, 2050133089467495346L}, + { -6734269153432007072L, 1281333180917184591L}, { -8417836441790008839L, 1601666476146480739L}, + { 7924448521472040567L, 2002083095183100924L}, { -4270591710934750454L, 1251301934489438077L}, + { 3885132398186337741L, 1564127418111797597L}, { -8978642557549241536L, 1955159272639746996L}, + { -3305808589254582008L, 1221974545399841872L}, { 479425281859160394L, 1527468181749802341L}, + { 5210967620751338397L, 1909335227187252926L}, { -1354831255457801406L, 1193334516992033078L}, + { -6305225087749639662L, 1491668146240041348L}, { -3269845341259661673L, 1864585182800051685L}, + { -6655339356714676450L, 1165365739250032303L}, { -8319174195893345562L, 1456707174062540379L}, + { 8047776328842869663L, 1820883967578175474L}, { 836348374198811271L, 2276104959472719343L}, + { 7440246761515338900L, 1422565599670449589L}, { -4534749603387990086L, 1778206999588061986L}, + { 8166621051047176104L, 2222758749485077483L}, { 2798295147690791113L, 1389224218428173427L}, + { -1113817083813899013L, 1736530273035216783L}, { -1392271354767373766L, 2170662841294020979L}, + { 8353202440125167204L, 1356664275808763112L}, { -8005241023553092611L, 1695830344760953890L}, + { 3828506775840797949L, 2119787930951192363L}, { 86973725686804766L, 1324867456844495227L}, + { -4502968861318881947L, 1656084321055619033L}, { 3594660960206173375L, 2070105401319523792L}, + { 2246663100128858359L, 1293815875824702370L}, { -6415043161693702859L, 1617269844780877962L}, + { 5816254103165035138L, 2021587305976097453L}, { 5941001823691840913L, 1263492066235060908L}, + { 7426252279614801142L, 1579365082793826135L}, { 4671129331091113523L, 1974206353492282669L}, + { 5225298841145639904L, 1233878970932676668L}, { 6531623551432049880L, 1542348713665845835L}, + { 3552843420862674446L, 1927935892082307294L}, { -2391158880388216375L, 1204959932551442058L}, + { -7600634618912658373L, 1506199915689302573L}, { -277421236786047158L, 1882749894611628216L}, + { -7090917300632361330L, 1176718684132267635L}, { -8863646625790451662L, 1470898355165334544L}, + { -6467872263810676674L, 1838622943956668180L}, { -3473154311335957938L, 2298278679945835225L}, + { 2440964573842414192L, 1436424174966147016L}, { 3051205717303017741L, 1795530218707683770L}, + { -5409364890226003632L, 2244412773384604712L}, { 8148361989677217490L, 1402757983365377945L}, + { -3649605568185641850L, 1753447479206722431L}, { -4562006960232052312L, 2191809349008403039L}, + { -2851254350145032695L, 1369880843130251899L}, { -3564067937681290869L, 1712351053912814874L}, + { -9066770940529001490L, 2140438817391018593L}, { -1055045819403238027L, 1337774260869386620L}, + { 3292878744173340370L, 1672217826086733276L}, { 4116098430216675462L, 2090272282608416595L}, + { 266718509671728212L, 1306420176630260372L}, { 333398137089660265L, 1633025220787825465L}, + { 5028433689789463235L, 2041281525984781831L}, { -8386443989950055238L, 1275800953740488644L}, + { -5871368969010181144L, 1594751192175610805L}, { 1884160825592049379L, 1993438990219513507L}, + { -1128242493218663091L, 1245899368887195941L}, { 7813068920331446945L, 1557374211108994927L}, + { 5154650131986920777L, 1946717763886243659L}, { 915813323278131534L, 1216698602428902287L}, + { -3466919364329723487L, 1520873253036127858L}, { -8945335223839542262L, 1901091566295159823L}, + { -5590834514899713914L, 1188182228934474889L}, { 2234828893230133415L, 1485227786168093612L}, + { 2793536116537666769L, 1856534732710117015L}, { 8663489100477123587L, 1160334207943823134L}, + { 1605989338741628675L, 1450417759929778918L}, { -7215885363427739964L, 1813022199912223647L}, + { -9019856704284674954L, 2266277749890279559L}, { -5637410440177921847L, 1416423593681424724L}, + { -2435077031795014404L, 1770529492101780905L}, { 6179525747111007803L, 2213161865127226132L}, + { -5361168444910395931L, 1383226165704516332L}, { -2089774537710607010L, 1729032707130645415L}, + { -2612218172138258762L, 2161290883913306769L}, { 2979049660840976177L, 1350806802445816731L}, + { -887873942376167682L, 1688508503057270913L}, { 8113529608884566205L, 2110635628821588642L}, + { -8764102049729309834L, 1319147268013492901L}, { -1731755525306861484L, 1648934085016866126L}, + { -6776380425060964759L, 2061167606271082658L}, { -6541080774876796927L, 1288229753919426661L}, + { 1047021068258779650L, 1610287192399283327L}, { -3302909683103913342L, 2012858990499104158L}, + { 4853210475701136017L, 1258036869061940099L}, { 1454827076199032118L, 1572546086327425124L}, + { 1818533845248790147L, 1965682607909281405L}, { 3442426662494187794L, 1228551629943300878L}, + { -4920338708737041066L, 1535689537429126097L}, { 3072948650933474476L, 1919611921786407622L}, + { -2691093111593966357L, 1199757451116504763L}, { -3363866389492457946L, 1499696813895630954L}, + { -8816519005292960336L, 1874621017369538693L}, { 8324733676974063502L, 1171638135855961683L}, + { 5794231077790191473L, 1464547669819952104L}, { 7242788847237739342L, 1830684587274940130L}, + { -169885977807601630L, 2288355734093675162L}, { -2412021745343444971L, 1430222333808546976L}, + { 1596658836748081690L, 1787777917260683721L}, { 6607509564362490017L, 2234722396575854651L}, + { 1823850468512862308L, 1396701497859909157L}, { 6891499104068465790L, 1745876872324886446L}, + { -608998156769193571L, 2182346090406108057L}, { 4231062170446641922L, 1363966306503817536L}, + { 5288827713058302403L, 1704957883129771920L}, { 6611034641322878003L, 2131197353912214900L}, + { -5091475386027977056L, 1331998346195134312L}, { -1752658214107583416L, 1664997932743917890L}, + { -6802508786061867174L, 2081247415929897363L}, { 4971804045566108824L, 1300779634956185852L}, + { 6214755056957636030L, 1625974543695232315L}, { 3156757802769657134L, 2032468179619040394L}, + { 6584659645158423613L, 1270292612261900246L}, { -992547480406746292L, 1587865765327375307L}, + { -1240684350508432865L, 1984832206659219134L}, { 6142101308573311315L, 1240520129162011959L}, + { 3065940617289251240L, 1550650161452514949L}, { 8444111790038951954L, 1938312701815643686L}, + { 665883850346957067L, 1211445438634777304L}, { 832354812933696334L, 1514306798293471630L}, + { -8182928520687655390L, 1892883497866839537L}, { -502644307002396715L, 1183052186166774710L}, + { -5239991402180383798L, 1478815232708468388L}, { -1938303234298091843L, 1848519040885585485L}, + { -5823125539863695306L, 1155324400553490928L}, { -2667220906402231229L, 1444155500691863660L}, + { 1277659885424598868L, 1805194375864829576L}, { 1597074856780748586L, 2256492969831036970L}, + { 5609857803915355770L, 1410308106144398106L}, { -2211049781960581095L, 1762885132680497632L}, + { 1847873790976661535L, 2203606415850622041L}, { -5762607908280668397L, 1377254009906638775L}, + { -7203259885350835496L, 1721567512383298469L}, { 219297180166231438L, 2151959390479123087L}, + { 7054589765244976505L, 1344974619049451929L}, { -5016820848725943081L, 1681218273811814911L}, + { -6271026060907428851L, 2101522842264768639L}, { -3919391288067143032L, 1313451776415480399L}, + { -4899239110083928790L, 1641814720519350499L}, { -6124048887604910988L, 2052268400649188124L}, + { -1521687545539375415L, 1282667750405742577L}, { 7321262604930556539L, 1603334688007178222L}, + { -71793780691580134L, 2004168360008972777L}, { 4566814905495150320L, 1252605225005607986L}, + { -3514853404985837908L, 1565756531257009982L}, { -9005252774659685289L, 1957195664071262478L}, + { 1289246043478778550L, 1223247290044539049L}, { 6223243572775861092L, 1529059112555673811L}, + { 3167368447542438461L, 1911323890694592264L}, { 1979605279714024038L, 1194577431684120165L}, + { 7086192618069917952L, 1493221789605150206L}, { -365631264267378368L, 1866527237006437757L}, + { -4840205558594499384L, 1166579523129023598L}, { 7784801107039039482L, 1458224403911279498L}, + { 507629346944023544L, 1822780504889099373L}, { 5246222702107417334L, 2278475631111374216L}, + { 3278889188817135834L, 1424047269444608885L}, { 8710297504448807696L, 1780059086805761106L}, + }; + + // IEEE 754 double precision constants + static final int DOUBLE_MANTISSA_BITS = 52; + static final int DOUBLE_EXPONENT_BITS = 11; + static final int DOUBLE_EXPONENT_BIAS = 1023; + + // Helper: floor(log2(value)) using ryu_leading_zeros64 + static int floorLog2(long value) { + return 63 - Long.numberOfLeadingZeros(value); + } + + // Helper: log2(5^e) approximation + static int log2pow5(int e) { + return (e * 1217359) >> 19; + } + + // Helper: ceil(log2(5^e)) + static int ceilLog2pow5(int e) { + return log2pow5(e) + 1; + } + + // Helper: convert uint64 bits to double + static double int64Bits2Double(long bits) { + return Double.longBitsToDouble(bits); + } + + // Check if value is multiple of 2^p + static boolean multipleOfPowerOf2(long value) { + return (value & (value - 1)) == 0; + } + + // Count how many times value is divisible by 5 + // Uses modular inverse to avoid expensive division + static int pow5Factor(long value) { + final long mInv5 = -3689348814741910323L; + final long nDiv5 = 3689348814741910323L; + int count = 0; + for (;;) { + value *= mInv5; + if (Long.compareUnsigned(value, nDiv5) > 0) { + break; + } + ++count; + } + return count; + } + + // Check if value is multiple of 5^p + // Optimized: uses modular inverse instead of division + static boolean multipleOfPowerOf5(long value, int p) { + return pow5Factor(value) >= p; + } + + // Fallback for systems without 128-bit integers + static long umul128(long a, long b, long[] productHi) { + long aLo = Integer.toUnsignedLong((int)a); + long aHi = Integer.toUnsignedLong((int)(a >> 32)); + long bLo = Integer.toUnsignedLong((int)b); + long bHi = Integer.toUnsignedLong((int)(b >> 32)); + + long b00 = aLo * bLo; + long b01 = aLo * bHi; + long b10 = aHi * bLo; + long b11 = aHi * bHi; + + long b00Lo = Integer.toUnsignedLong((int)b00); + long b00Hi = Integer.toUnsignedLong((int)(b00 >> 32)); + + long mid1 = b10 + b00Hi; + long mid1Lo = Integer.toUnsignedLong((int)(mid1)); + long mid1Hi = Integer.toUnsignedLong((int)(mid1 >> 32)); + + long mid2 = b01 + mid1Lo; + long mid2Lo = Integer.toUnsignedLong((int)(mid2)); + long mid2Hi = Integer.toUnsignedLong((int)(mid2 >> 32)); + + long pHi = b11 + mid1Hi + mid2Hi; + long pLo = (mid2Lo << 32) | b00Lo; + + productHi[0] = pHi; + return pLo; + } + + static long shiftright128(long lo, long hi, int dist) { + return (hi << (64 - dist)) | (lo >>> dist); + } + + static long mulShift64(long m, long[] mul, int j) { + long[] high1Buf = new long[1]; + long low1 = umul128(m, mul[1], high1Buf); + long high1 = high1Buf[0]; + long[] high0Buf = high1Buf; // reuse alloc since we extracted high1 into its own long + umul128(m, mul[0], high0Buf); + long high0 = high0Buf[0]; + long sum = high0 + low1; + if (Long.compareUnsigned(sum, high0) < 0) { + ++high1; + } + return shiftright128(sum, high1, j - 64); + } + + // Precomputed powers of ten from 10^0 to 10^22. These + // can be represented exactly using the double type. + private static final double[] POWER_OF_TEN = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22}; + + // Main conversion function: decimal mantissa+exponent to IEEE 754 double + // Optimized for JSON parsing with fast paths for edge cases + public static double ryuS2dFromParts(long m10, int m10digits, int e10, boolean signedM) { + // From Daniel Lemire's fast_double_parser which is an extraction of part of 'How to Read + // Floating Point Numbers Accurately' by Willian D. Clinger: https://www.cs.uoregon.edu/Reports/TR-1990-001.pdf + if (-22 <= e10 && e10 <= 22 && Long.compareUnsigned(m10, 9007199254740991L) <= 0) { + double value = (double) m10; // Lossless conversion for 0 <= i <= 2^53 - 1. + + if (e10 < 0) { + value /= POWER_OF_TEN[-e10]; + } else { + value *= POWER_OF_TEN[e10]; + } + + return signedM ? -value : value; + } + + // Fast path: handle zero explicitly (e.g., "0.0", "0e0") or overflow/underflow early + if (m10 == 0 || m10digits + e10 <= -324) return signedM ? -0.0 : 0.0; + + // Overflow to infinity + if (m10digits + e10 >= 310) return Double.POSITIVE_INFINITY; + + // Convert decimal to binary: m10 * 10^e10 = m2 * 2^e2 + int e2; + long m2; + boolean trailingZeros; + + if (e10 >= 0) { + // Positive exponent: multiply by 5^e10 and adjust binary exponent + e2 = floorLog2(m10) + e10 + log2pow5(e10) - (DOUBLE_MANTISSA_BITS + 1); + int j = e2 - e10 - ceilLog2pow5(e10) + DOUBLE_POW5_BITCOUNT; + m2 = mulShift64(m10, DOUBLE_POW5_SPLIT[e10], j); + trailingZeros = e2 < e10 || (e2 - e10 < 64 && multipleOfPowerOf2(m10)); + } else { + // Negative exponent: divide by 5^(-e10) + e2 = floorLog2(m10) + e10 - ceilLog2pow5(-e10) - (DOUBLE_MANTISSA_BITS + 1); + int j = e2 - e10 + ceilLog2pow5(-e10) - 1 + DOUBLE_POW5_INV_BITCOUNT; + m2 = mulShift64(m10, DOUBLE_POW5_INV_SPLIT[-e10], j); + trailingZeros = multipleOfPowerOf5(m10, -e10); + } + + // Compute IEEE 754 exponent + int ieee_e2 = Math.max(0, e2 + DOUBLE_EXPONENT_BIAS + floorLog2(m2)); + int signedML = signedM ? 1 : 0; + + if (ieee_e2 > 0x7fe) { + // Overflow to infinity + return int64Bits2Double((signedML << 63) | 0x7ff0000000000000L); + } + + // Compute shift amount for rounding + int shift = (ieee_e2 == 0 ? 1 : ieee_e2) - e2 - DOUBLE_EXPONENT_BIAS - DOUBLE_MANTISSA_BITS; + + // IEEE 754 round-to-even (banker's rounding) + trailingZeros &= (m2 & ((1L << (shift - 1)) - 1)) == 0; + long lastRemovedBit = (m2 >> (shift - 1)) & 1; + int roundUp = (lastRemovedBit != 0) && (!trailingZeros || (((m2 >> shift) & 1) != 0)) ? 1 : 0; + + long ieee_m2 = (m2 >> shift) + roundUp; + ieee_m2 &= (1L << DOUBLE_MANTISSA_BITS) - 1; + + if (ieee_m2 == 0 && roundUp == 1) { + ieee_e2++; + } + + // Pack sign, exponent, and mantissa into IEEE 754 format + // Match original Ryu: group sign+exponent, then shift and add mantissa + long ieee = (((signedML << DOUBLE_EXPONENT_BITS) | (long)ieee_e2) << DOUBLE_MANTISSA_BITS) | ieee_m2; + return int64Bits2Double(ieee); + } +} diff --git a/java/src/json/ext/SWARBasicStringEncoder.java b/java/src/json/ext/SWARBasicStringEncoder.java new file mode 100644 index 000000000..bd0d1a70f --- /dev/null +++ b/java/src/json/ext/SWARBasicStringEncoder.java @@ -0,0 +1,91 @@ +package json.ext; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.jruby.util.ByteList; + +public class SWARBasicStringEncoder extends StringEncoder { + + public SWARBasicStringEncoder() { + super(ESCAPE_TABLE); + } + + @Override + void encode(ByteList src) throws IOException { + byte[] hexdig = HEX; + byte[] scratch = aux; + + byte[] ptrBytes = src.unsafeBytes(); + int ptr = src.begin(); + int len = src.realSize(); + + int beg = 0; + int pos = 0; + + // There are optimizations in JRuby where ptr > 0 will be true. For example, if we + // slice a string, the underlying byte array is the same, but the + // begin index and real size are different. When reading from the ptrBytes + // array, we need to always add ptr to the index. + ByteBuffer bb = ByteBuffer.wrap(ptrBytes, 0, ptr + len); + + while (pos + 8 <= len) { + long x = bb.getLong(ptr + pos); + if (skipChunk(x)) { + pos += 8; + continue; + } + int chunkEnd = ptr + pos + 8; + while (ptr + pos < chunkEnd) { + int ch = Byte.toUnsignedInt(ptrBytes[ptr + pos]); + int ch_len = ESCAPE_TABLE[ch]; + if (ch_len > 0) { + beg = pos = flushPos(pos, beg, ptrBytes, ptr, 1); + escapeAscii(ch, scratch, hexdig); + } else { + pos++; + } + } + } + + if (pos + 4 <= len) { + int x = bb.getInt(ptr + pos); + if (skipChunk(x)) { + pos += 4; + } + } + + while (pos < len) { + int ch = Byte.toUnsignedInt(ptrBytes[ptr + pos]); + int ch_len = ESCAPE_TABLE[ch]; + if (ch_len > 0) { + beg = pos = flushPos(pos, beg, ptrBytes, ptr, 1); + escapeAscii(ch, scratch, hexdig); + } else { + pos++; + } + } + + if (beg < len) { + append(ptrBytes, ptr + beg, len - beg); + } + } + + boolean skipChunk(long x) { + long is_ascii = 0x8080808080808080L & ~x; + long xor2 = x ^ 0x0202020202020202L; + long lt32_or_eq34 = xor2 - 0x2121212121212121L; + long sub92 = x ^ 0x5C5C5C5C5C5C5C5CL; + long eq92 = (sub92 - 0x0101010101010101L); + return ((lt32_or_eq34 | eq92) & is_ascii) == 0; + } + + boolean skipChunk(int x) { + int is_ascii = 0x80808080 & ~x; + int xor2 = x ^ 0x02020202; + int lt32_or_eq34 = xor2 - 0x21212121; + int sub92 = x ^ 0x5C5C5C5C; + int eq92 = (sub92 - 0x01010101); + return ((lt32_or_eq34 | eq92) & is_ascii) == 0; + } +} diff --git a/java/src/json/ext/SegmentedByteListDirectOutputStream.java b/java/src/json/ext/SegmentedByteListDirectOutputStream.java new file mode 100644 index 000000000..0e89e50df --- /dev/null +++ b/java/src/json/ext/SegmentedByteListDirectOutputStream.java @@ -0,0 +1,84 @@ +package json.ext; + +import org.jcodings.Encoding; +import org.jruby.util.ByteList; + +import java.io.IOException; + +public class SegmentedByteListDirectOutputStream extends AbstractByteListDirectOutputStream { + private static final int DEFAULT_CAPACITY = 1024; + + private int totalLength; + // Why 21? The minimum segment size is 1024 bytes. If we double the segment size each time + // we need a new segment, we only need 21 segments to reach the maximum array size in Java. + private byte[][] segments = new byte[21][]; + private int currentSegmentIndex; + private int currentSegmentLength; + private byte[] currentSegment; + + SegmentedByteListDirectOutputStream(int size) { + currentSegment = new byte[Math.max(size, DEFAULT_CAPACITY)]; + segments[0] = currentSegment; + } + + public ByteList toByteListDirect(Encoding encoding) { + byte[] buffer = new byte[totalLength]; + int pos = 0; + // We handle the current segment separately. + for (int i = 0; i < currentSegmentIndex; i++) { + byte[] segment = segments[i]; + System.arraycopy(segment, 0, buffer, pos, segment.length); + pos += segment.length; + } + System.arraycopy(currentSegment, 0, buffer, pos, currentSegmentLength); + return new ByteList(buffer, 0, totalLength, encoding, false); + } + + @Override + public void write(int b) throws IOException { + if (currentSegmentLength == currentSegment.length) { + if (totalLength + 1 < 0) { + throw new IOException("Total length exceeds maximum length of an array."); + } + currentSegmentIndex++; + int capacity = currentSegment.length * 2; + capacity = (capacity < 0) ? DEFAULT_CAPACITY : capacity; + currentSegment = new byte[capacity]; + currentSegmentLength = 0; + segments[currentSegmentIndex] = currentSegment; + } + currentSegment[currentSegmentLength++] = (byte) b; + totalLength++; + } + + @Override + public void write(byte[] bytes, int start, int length) throws IOException { + int remaining = length; + + while (remaining > 0) { + if (currentSegmentLength == currentSegment.length) { + if (totalLength + remaining < 0) { + throw new IOException("Total length exceeds maximum length of an array."); + } + currentSegmentIndex++; + int capacity = currentSegment.length << 1; + capacity = (capacity < 0) ? DEFAULT_CAPACITY : capacity; + capacity = (capacity < remaining) ? remaining : capacity; + currentSegment = new byte[capacity]; + currentSegmentLength = 0; + segments[currentSegmentIndex] = currentSegment; + } + int toWrite = Math.min(remaining, currentSegment.length - currentSegmentLength); + System.arraycopy(bytes, start, currentSegment, currentSegmentLength, toWrite); + currentSegmentLength += toWrite; + start += toWrite; + remaining -= toWrite; + } + totalLength += length; + } + + @Override + public void write(byte[] bytes) throws IOException { + write(bytes, 0, bytes.length); + } +} diff --git a/java/src/json/ext/StringDecoder.java b/java/src/json/ext/StringDecoder.java index bf616c1e9..362a0d685 100644 --- a/java/src/json/ext/StringDecoder.java +++ b/java/src/json/ext/StringDecoder.java @@ -10,6 +10,7 @@ import org.jruby.util.ByteList; import java.io.IOException; +import java.nio.ByteBuffer; /** * A decoder that reads a JSON-encoded string from the given sources and @@ -22,12 +23,21 @@ final class StringDecoder extends ByteListTranscoder { * or -1 when not. */ private int surrogatePairStart = -1; + private boolean allowControlCharacters = false; + private boolean allowInvalidEscape = false; + + private final StringScanner scanner = StringScanner.getInstance(); private ByteList out; // Array used for writing multibyte characters into the buffer at once private final byte[] aux = new byte[4]; + public StringDecoder(boolean allowControlCharacters, boolean allowInvalidEscape) { + this.allowControlCharacters = allowControlCharacters; + this.allowInvalidEscape = allowInvalidEscape; + } + ByteList decode(ThreadContext context, ByteList src, int start, int end) { try { init(src, start, end); @@ -42,6 +52,58 @@ ByteList decode(ThreadContext context, ByteList src, int start, int end) { } } + // Decodes strings with no UTF-8 validation. It is assumed the string is either ASCII-only + // or UTF-8 avlidation has been disabled. + ByteList decodeNoValidate(ThreadContext context, ByteList src, int start, int end, ByteBuffer chunks) { + try { + init(src, start, end); + this.out = new ByteList(end - start); + final byte[] data = src.unsafeBytes(); + final int base = src.begin(); + final int absEnd = base + srcEnd; + int cursor = base + pos; + int runStart = cursor; + while (cursor < absEnd) { + cursor = scanner.scanEscape(data, chunks, cursor, absEnd); + if (cursor >= absEnd) { + break; + } + int b = Byte.toUnsignedInt(data[cursor]); + if (b == '\\') { + if (cursor > runStart) { + append(data, runStart, cursor - runStart); + } + charStart = cursor - base; + pos = charStart + 1; + handleEscapeSequence(context); + cursor = base + pos; + runStart = cursor; + } else { + if (!allowControlCharacters) { + charStart = cursor - base; + throw invalidControlChar(context); + } + cursor++; + } + } + if (absEnd > runStart) { + append(data, runStart, absEnd - runStart); + } + return out; + } catch (IOException e) { + throw context.runtime.newIOErrorFromException(e); + } + } + + @Override + protected int readUtf8Char(ThreadContext context) { + int c = super.readUtf8Char(context); + if (c < 0x20 && !allowControlCharacters) { + throw invalidControlChar(context); + } + return c; + } + private void handleChar(ThreadContext context, int c) throws IOException { if (c == '\\') { quoteStop(charStart); @@ -53,7 +115,8 @@ private void handleChar(ThreadContext context, int c) throws IOException { private void handleEscapeSequence(ThreadContext context) throws IOException { ensureMin(context, 1); - switch (readUtf8Char(context)) { + int character = readUtf8Char(context); + switch (character) { case 'b': append('\b'); break; @@ -69,6 +132,15 @@ private void handleEscapeSequence(ThreadContext context) throws IOException { case 't': append('\t'); break; + case '/': + append('/'); + break; + case '"': + append('"'); + break; + case '\\': + append('\\'); + break; case 'u': ensureMin(context, 4); int cp = readHex(context); @@ -81,8 +153,12 @@ private void handleEscapeSequence(ThreadContext context) throws IOException { writeUtf8Char(cp); } break; - default: // '\\', '"', '/'... - quoteStart(); + default: + if (allowInvalidEscape) { + writeUtf8Char(character); + } else { + throw invalidEscape(context); + } } } @@ -156,8 +232,7 @@ private int readHex(ThreadContext context) { } else if (digit >= 'A' && digit <= 'F') { digitValue = 10 + digit - 'A'; } else { - throw new NumberFormatException("Invalid base 16 number " - + src.subSequence(numberStart, numberStart + length)); + throw invalidUnicodeEscape(context, numberStart); } result = result * 16 + digitValue; } @@ -174,4 +249,30 @@ protected RaiseException invalidUtf8(ThreadContext context) { return Utils.newException(context, Utils.M_PARSER_ERROR, context.runtime.newString(message)); } + + protected RaiseException invalidControlChar(ThreadContext context) { + ByteList message = new ByteList( + ByteList.plain("invalid ASCII control character in string: ")); + message.append(src, charStart, srcEnd - charStart); + return Utils.newException(context, Utils.M_PARSER_ERROR, + context.runtime.newString(message)); + } + + protected RaiseException invalidEscape(ThreadContext context) { + ByteList message = new ByteList( + ByteList.plain("invalid escape character in string: ")); + message.append(src, charStart, srcEnd - charStart); + return Utils.newException(context, Utils.M_PARSER_ERROR, + context.runtime.newString(message)); + } + + protected RaiseException invalidUnicodeEscape(ThreadContext context, int escapeStart) { + ByteList message = new ByteList( + ByteList.plain("incomplete unicode character escape sequence at ")); + // Point at the backslash-u that introduced the escape. + int start = Math.max(escapeStart - 2, 0); + message.append(src, start, srcEnd - start); + return Utils.newException(context, Utils.M_PARSER_ERROR, + context.runtime.newString(message)); + } } diff --git a/java/src/json/ext/StringEncoder.java b/java/src/json/ext/StringEncoder.java index d178d0bd8..347635cad 100644 --- a/java/src/json/ext/StringEncoder.java +++ b/java/src/json/ext/StringEncoder.java @@ -5,8 +5,14 @@ */ package json.ext; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; + import org.jcodings.Encoding; -import org.jcodings.specific.ASCIIEncoding; import org.jcodings.specific.USASCIIEncoding; import org.jcodings.specific.UTF8Encoding; import org.jruby.Ruby; @@ -17,10 +23,6 @@ import org.jruby.util.ByteList; import org.jruby.util.StringSupport; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; - /** * An encoder that reads from the given source and outputs its representation * to another ByteList. The source string is fully checked for UTF-8 validity, @@ -114,6 +116,39 @@ class StringEncoder extends ByteListTranscoder { protected final byte[] escapeTable; + private static final String VECTORIZED_STRING_ENCODER_CLASS = "json.ext.VectorizedStringEncoder"; + private static final String USE_VECTORIZED_BASIC_ENCODER_PROP = "jruby.json.useVectorizedBasicEncoder"; + private static final String USE_VECTORIZED_BASIC_ENCODER_DEFAULT = "false"; + private static final boolean USE_VECTORIZED_BASIC_ENCODER; + private static final StringEncoder VECTORIZED_SCANNER; + + private static final String USE_SWAR_BASIC_ENCODER_PROP = "jruby.json.useSWARBasicEncoder"; + private static final String USE_SWAR_BASIC_ENCODER_DEFAULT = "true"; + private static final boolean USE_BASIC_SWAR_ENCODER; + + static { + String enableVectorizedScanner = System.getProperty(USE_VECTORIZED_BASIC_ENCODER_PROP, USE_VECTORIZED_BASIC_ENCODER_DEFAULT); + if ("true".equalsIgnoreCase(enableVectorizedScanner) || "1".equalsIgnoreCase(enableVectorizedScanner)) { + StringEncoder scanner; + try { + Class vectorizedStringEncoderClass = StringEncoder.class.getClassLoader().loadClass(VECTORIZED_STRING_ENCODER_CLASS); + Constructor vectorizedStringEncoderConstructor = vectorizedStringEncoderClass.getDeclaredConstructor(); + scanner = (StringEncoder) vectorizedStringEncoderConstructor.newInstance(); + } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + // Fallback to the StringEncoder if we cannot load the VectorizedStringEncoder. + scanner = null; + } + VECTORIZED_SCANNER = scanner; + USE_VECTORIZED_BASIC_ENCODER = scanner != null; + } else { + VECTORIZED_SCANNER = null; + USE_VECTORIZED_BASIC_ENCODER = false; + } + + USE_BASIC_SWAR_ENCODER = Boolean.parseBoolean( + System.getProperty(USE_SWAR_BASIC_ENCODER_PROP, USE_SWAR_BASIC_ENCODER_DEFAULT)); + } + OutputStream out; // Escaped characters will reuse this array, to avoid new allocations @@ -138,15 +173,32 @@ class StringEncoder extends ByteListTranscoder { this.escapeTable = escapeTable; } + @Override + public StringEncoder clone() { + return new StringEncoder(escapeTable); + } + + static StringEncoder createBasicEncoder() { + if (USE_VECTORIZED_BASIC_ENCODER) { + return (StringEncoder) VECTORIZED_SCANNER.clone(); + } else if (USE_BASIC_SWAR_ENCODER) { + return new SWARBasicStringEncoder(); + } else { + return new StringEncoder(false); + } + } + // C: generate_json_string void generate(ThreadContext context, RubyString object, OutputStream buffer) throws IOException { object = ensureValidEncoding(context, object); - ByteList byteList = object.getByteList(); + // IO writes can mutate the original string while we are reading it. + RubyString source = buffer instanceof BufferedOutputStream ? object.newFrozen() : object; + ByteList byteList = source.getByteList(); init(byteList); out = buffer; append('"'); - switch (object.scanForCodeRange()) { + switch (source.scanForCodeRange()) { case StringSupport.CR_7BIT: case StringSupport.CR_VALID: encode(byteList); @@ -158,6 +210,17 @@ void generate(ThreadContext context, RubyString object, OutputStream buffer) thr append('"'); } + static boolean hasValidEncoding(RubyString str) { + switch (str.scanForCodeRange()) { + case StringSupport.CR_7BIT: + return true; + case StringSupport.CR_VALID: + return str.getEncoding() == UTF8Encoding.INSTANCE || str.getEncoding() == USASCIIEncoding.INSTANCE; + default: + return false; + } + } + static RubyString ensureValidEncoding(ThreadContext context, RubyString str) { Encoding encoding = str.getEncoding(); @@ -165,28 +228,12 @@ static RubyString ensureValidEncoding(ThreadContext context, RubyString str) { return str; } - return tryWeirdEncodings(context, str, encoding); + return tryWeirdEncodings(context, str); } - private static RubyString tryWeirdEncodings(ThreadContext context, RubyString str, Encoding encoding) { + private static RubyString tryWeirdEncodings(ThreadContext context, RubyString str) { Ruby runtime = context.runtime; - RubyString utf8String; - - if (encoding == ASCIIEncoding.INSTANCE) { - utf8String = str.strDup(runtime); - utf8String.setEncoding(UTF8Encoding.INSTANCE); - switch (utf8String.getCodeRange()) { - case StringSupport.CR_7BIT: - return utf8String; - case StringSupport.CR_VALID: - // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work. - // TODO: Raise in 3.0.0 - runtime.getWarnings().warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0"); - return utf8String; - } - } - try { str = (RubyString) str.encode(context, runtime.getEncodingService().convertEncodingToRubyEncoding(UTF8Encoding.INSTANCE)); } catch (RaiseException re) { @@ -198,8 +245,40 @@ private static RubyString tryWeirdEncodings(ThreadContext context, RubyString st return str; } + void encodeBasic(ByteList src) throws IOException { + byte[] hexdig = HEX; + byte[] scratch = aux; + + byte[] ptrBytes = src.unsafeBytes(); + int ptr = src.begin(); + int len = src.realSize(); + + int beg = 0; + int pos = 0; + + while (pos < len) { + int ch = Byte.toUnsignedInt(ptrBytes[ptr + pos]); + int ch_len = ESCAPE_TABLE[ch]; + if (ch_len > 0) { + beg = pos = flushPos(pos, beg, ptrBytes, ptr, 1); + escapeAscii(ch, scratch, hexdig); + } else { + pos++; + } + } + + if (beg < len) { + append(ptrBytes, ptr + beg, len - beg); + } + } + // C: convert_UTF8_to_JSON void encode(ByteList src) throws IOException { + if (escapeTable == ESCAPE_TABLE) { + encodeBasic(src); + return; + } + byte[] hexdig = HEX; byte[] scratch = aux; byte[] escapeTable = this.escapeTable; diff --git a/java/src/json/ext/StringScanner.java b/java/src/json/ext/StringScanner.java new file mode 100644 index 000000000..605c0ad97 --- /dev/null +++ b/java/src/json/ext/StringScanner.java @@ -0,0 +1,243 @@ +package json.ext; + +import java.nio.ByteBuffer; + +/** + * Scans the body of a JSON string for its closing quote, reporting whether the + * body can be copied verbatim (the "plain" fast path) or must be handed to + * {@link StringDecoder} for escape expansion and UTF-8/control validation. + * + *

The default implementation is SWAR (8 bytes per step). A vectorized + * subclass ({@code VectorizedStringScanner}) is loaded reflectively when the + * {@code jruby.json.useVectorizedParser} system property is set and the + * {@code jdk.incubator.vector} module is available; otherwise this SWAR + * implementation is used. Instances are stateless and therefore shared. + */ +class StringScanner { + /** Set in the returned bits when the whole body is plain printable ASCII. */ + static final long PLAIN_BIT = 1L << 32; + static final long ASCII_BIT = 1L << 33; + + /** Returned when no closing quote is found before {@code end}. */ + static final long NOT_FOUND = -1L; + + private static final long HIGH_BITS = 0x8080808080808080L; + private static final long ONES = 0x0101010101010101L; + + private static final long SPACES = 0x2020202020202020L; + private static final long DOUBLE_QUOTES = 0x2222222222222222L; + private static final long BACKSLASHES = 0x5C5C5C5C5C5C5C5CL; + + private static final String VECTORIZED_SCANNER_CLASS = "json.ext.VectorizedStringScanner"; + private static final String USE_VECTORIZED_PARSER_PROP = "jruby.json.useVectorizedParser"; + private static final String USE_VECTORIZED_PARSER_DEFAULT = "false"; + + private static final StringScanner INSTANCE; + + static { + StringScanner scanner = new StringScanner(); + String enable = System.getProperty(USE_VECTORIZED_PARSER_PROP, USE_VECTORIZED_PARSER_DEFAULT); + if ("true".equalsIgnoreCase(enable) || "1".equals(enable)) { + try { + Class vectorized = StringScanner.class.getClassLoader() + .loadClass(VECTORIZED_SCANNER_CLASS); + scanner = (StringScanner) vectorized.getDeclaredConstructor().newInstance(); + } catch (Throwable t) { + scanner = new StringScanner(); + } + } + INSTANCE = scanner; + } + + static StringScanner getInstance() { + return INSTANCE; + } + + /** + * Scans {@code data[start..end)} for a closing quote. This method will + * also report if there are any interesting bytes within the range + * {@link data[start..end)}. An interestin byte is defined as control + * characters, backslashes or bytes with the high bit set. + * + * @param chunks a little-endian {@link ByteBuffer} over {@code data} + * + * @return packed result: the low 32 bits hold the index of the closing + * quote, or {@code -1} ({@link #NOT_FOUND}) when none is found + * before {@code end}; {@link #PLAIN_BIT} is set when the entire body + * is plain printable ASCII (no escape, no ASCII control character, + * and no non-ASCII byte) and can be copied verbatim; + * {@link #ASCII_BIT} is set when the body contains no non-ASCII byte + * (it may still hold escapes or control characters) + */ + long scan(byte[] data, ByteBuffer chunks, int start, int end, boolean validateUtf8) { + int p = start; + boolean plain = true; + boolean ascii = true; + + outer: + while (true) { + while (p + 8 <= end) { + long x = chunks.getLong(p); + // Pick the cheapest mask that still observes every transition we + // still care about: + // plain -> control, quote, backslash and high bytes + // non-plain, ASCII -> quote, backslash and high bytes, so the + // first non-ASCII byte still stops us and + // clears ASCII_BIT + // non-plain, !ASCII -> quote and backslash only; multi-byte + // UTF-8 is skipped eight bytes at a time + // + // When UTF-8 validation is disabled, non-ASCII bytes are copied + // verbatim, so they stay on the plain fast path and the high-bit + // term drops out of every mask: plain scans for control, quote + // and backslash; non-plain scans for quote and backslash only. + long m; + if (validateUtf8) { + m = plain ? stringScanMask(x) + : ascii ? quoteBackslashHighMask(x) + : quoteBackslashMask(x); + } else { + m = plain ? controlQuoteBackslashMask(x) + : quoteBackslashMask(x); + } + if (m == 0) { + p += 8; + } else { + p += Long.numberOfTrailingZeros(m) >>> 3; + break; + } + } + // If we match on a byte above and/or tail handling for <8 remaining bytes. + while (p < end) { + int b = data[p] & 0xFF; + if (b == '"') { + return (((long) p) | (plain ? PLAIN_BIT : 0L) | (ascii ? ASCII_BIT : 0L)); + } + if (b == '\\') { + plain = false; + p += 2; // skip the backslash and the escaped byte + continue outer; + } + if (b < 0x20) { + plain = false; + p++; + continue outer; + } + if (b >= 0x80) { + ascii = false; + if (validateUtf8) { + plain = false; + } + p++; + continue outer; + } + p++; + } + return NOT_FOUND; + } + } + + + /** + * Scans {@code data[start..end)} for the next backslash or control character. + * + *

The caller must guarantee the {@link data[start..end]} contains + * no non-ASCII bytes that need to be decoded.

+ * + * @return the index of the first backslash or control byte, or {@code end} + * when none is found. + */ + int scanEscape(byte[] data, ByteBuffer chunks, int start, int end) { + int p = start; + while (p + 8 <= end) { + long x = chunks.getLong(p); + long m = backslashControlMask(x); + if (m == 0) { + p += 8; + } else { + return p + (Long.numberOfTrailingZeros(m) >>> 3); + } + } + while (p < end) { + int b = data[p] & 0xFF; + if (b == '\\' || b < 0x20) { + return p; + } + p++; + } + return end; + } + + /** + * Returns a mask whose high bit (0x80) is set in every lane of {@code x} + * that needs scalar attention: an ASCII control character (< 0x20), a + * double quote, a backslash, or a non-ASCII byte (high bit set). Returns 0 + * when the whole 8-byte chunk is printable ASCII copyable verbatim. + */ + private static long stringScanMask(long x) { + long control = (x - SPACES) & ~x; // bytes < 0x20 (ASCII) + long high = x; // bit 0x80 set iff non-ASCII + long q = x ^ DOUBLE_QUOTES; + long quote = (q - ONES) & ~q; + long s = x ^ BACKSLASHES; + long bslash = (s - ONES) & ~s; + return (control | high | quote | bslash) & HIGH_BITS; + } + + /** + * Like {@link #quoteBackslashMask} but for the ASCII-only decode fast path: + * flags backslashes and ASCII control characters (< 0x20). The control + * test relies on every byte being ASCII (< 0x80), which the caller + * guarantees, so no high-bit cleanup is needed. + */ + private static long backslashControlMask(long x) { + long control = (x - SPACES) & ~x; // bytes < 0x20 (ASCII) + long s = x ^ BACKSLASHES; + long bslash = (s - ONES) & ~s; + return (control | bslash) & HIGH_BITS; + } + + /** + * Like {@link #stringScanMask} but omits the non-ASCII (high-bit) term: + * flags double quotes, backslashes and ASCII control characters (< 0x20) + * only. Used as the starting mask when UTF-8 validation is disabled, where + * non-ASCII bytes are copied verbatim and so stay on the plain fast path. + */ + private static long controlQuoteBackslashMask(long x) { + long control = (x - SPACES) & ~x; // bytes < 0x20 (ASCII) + long q = x ^ DOUBLE_QUOTES; + long quote = (q - ONES) & ~q; + long s = x ^ BACKSLASHES; + long bslash = (s - ONES) & ~s; + return (control | quote | bslash) & HIGH_BITS; + } + + /** + * Like {@link #stringScanMask} but only flags double quotes and backslashes. + * Used once a string is known to require the decoder and to already + * contain non-ASCII bytes, so the remaining scan for the closing quote skips + * clean chunks (including multi-byte UTF-8) eight bytes at a time. + */ + private static long quoteBackslashMask(long x) { + long q = x ^ DOUBLE_QUOTES; + long quote = (q - ONES) & ~q; + long s = x ^ BACKSLASHES; + long bslash = (s - ONES) & ~s; + return (quote | bslash) & HIGH_BITS; + } + + /** + * Like {@link #quoteBackslashMask} but also flags non-ASCII bytes. Used once + * a string is known to require the decoder but is still ASCII-only, so the + * scan keeps skipping printable runs eight bytes at a time yet still stops on + * the first high byte and clears {@link #ASCII_BIT}. + */ + private static long quoteBackslashHighMask(long x) { + long high = x; // bit 0x80 set iff non-ASCII + long q = x ^ DOUBLE_QUOTES; + long quote = (q - ONES) & ~q; + long s = x ^ BACKSLASHES; + long bslash = (s - ONES) & ~s; + return (high | quote | bslash) & HIGH_BITS; + } +} diff --git a/java/src/json/ext/VectorizedStringEncoder.java b/java/src/json/ext/VectorizedStringEncoder.java new file mode 100644 index 000000000..14b3c8d7c --- /dev/null +++ b/java/src/json/ext/VectorizedStringEncoder.java @@ -0,0 +1,104 @@ +package json.ext; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.jruby.util.ByteList; + +import jdk.incubator.vector.ByteVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +class VectorizedStringEncoder extends SWARBasicStringEncoder { + private static final VectorSpecies SP = ByteVector.SPECIES_PREFERRED; + private static final ByteVector ZERO = ByteVector.zero(SP); + private static final ByteVector TWO = ByteVector.broadcast(SP, 2); + private static final ByteVector THIRTY_THREE = ByteVector.broadcast(SP, 33); + private static final ByteVector BACKSLASH = ByteVector.broadcast(SP, '\\'); + + @Override + public StringEncoder clone() { + return new VectorizedStringEncoder(); + } + + @Override + void encode(ByteList src) throws IOException { + byte[] ptrBytes = src.unsafeBytes(); + int ptr = src.begin(); + int len = src.realSize(); + int beg = 0; + int pos = ptr; + + while ((pos + SP.length() <= len)) { + ByteVector chunk = ByteVector.fromArray(SP, ptrBytes, ptr + pos); + // bytes are signed in java, so we need to remove negative values + VectorMask negative = chunk.lt(ZERO); + VectorMask tooLowOrDblQuote = chunk.lanewise(VectorOperators.XOR, TWO).lt(THIRTY_THREE).andNot(negative); + VectorMask needsEscape = chunk.eq(BACKSLASH).or(tooLowOrDblQuote); + if (needsEscape.anyTrue()) { + int chunkStart = pos; + long mask = needsEscape.toLong(); + + while(mask > 0) { + // nextMatch inlined + int index = Long.numberOfTrailingZeros(mask); + mask &= (mask - 1); + pos = chunkStart + index; + int ch = Byte.toUnsignedInt(ptrBytes[ptr + pos]); + + beg = pos = flushPos(pos, beg, ptrBytes, ptr, 1); + escapeAscii(ch, aux, HEX); + } + + // Skip over any remaining characters in the current chunk + pos = chunkStart + SP.length(); + continue; + } + + pos += SP.length(); + } + + ByteBuffer bb = ByteBuffer.wrap(ptrBytes, ptr, len); + if (pos + 8 <= len) { + long x = bb.getLong(ptr + pos); + if (skipChunk(x)) { + pos += 8; + } else { + int chunkEnd = ptr + pos + 8; + while (pos < chunkEnd) { + int ch = Byte.toUnsignedInt(ptrBytes[ptr + pos]); + int ch_len = ESCAPE_TABLE[ch]; + if (ch_len > 0) { + beg = pos = flushPos(pos, beg, ptrBytes, ptr, 1); + escapeAscii(ch, aux, HEX); + } else { + pos++; + } + } + } + } + + if (pos + 4 <= len) { + int x = bb.getInt(ptr + pos); + if (skipChunk(x)) { + pos += 4; + } + } + + while (pos < len) { + int ch = Byte.toUnsignedInt(ptrBytes[ptr + pos]); + int ch_len = ESCAPE_TABLE[ch]; + if (ch_len > 0) { + beg = pos = flushPos(pos, beg, ptrBytes, ptr, 1); + escapeAscii(ch, aux, HEX); + } else { + pos++; + } + } + + if (beg < len) { + append(ptrBytes, ptr + beg, len - beg); + } + } +} diff --git a/java/src/json/ext/VectorizedStringScanner.java b/java/src/json/ext/VectorizedStringScanner.java new file mode 100644 index 000000000..b63544fea --- /dev/null +++ b/java/src/json/ext/VectorizedStringScanner.java @@ -0,0 +1,131 @@ +package json.ext; + +import java.nio.ByteBuffer; + +import jdk.incubator.vector.ByteVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +final class VectorizedStringScanner extends StringScanner { + private static final VectorSpecies SP = ByteVector.SPECIES_PREFERRED; + private static final ByteVector ZERO = ByteVector.zero(SP); + private static final ByteVector TWO = ByteVector.broadcast(SP, 2); + private static final ByteVector THIRTY_THREE = ByteVector.broadcast(SP, 33); + private static final ByteVector SPACE = ByteVector.broadcast(SP, 0x20); + private static final ByteVector BACKSLASH = ByteVector.broadcast(SP, '\\'); + private static final ByteVector DQUOTE = ByteVector.broadcast(SP, '"'); + + @Override + long scan(byte[] data, ByteBuffer chunks, int start, int end, boolean validateUtf8) { + final int width = SP.length(); + int p = start; + boolean plain = true; + boolean ascii = true; + + // The same structure as the StringEncoder. The logic is + // duplicated for maximum inlining. + // + // When UTF-8 validation is disabled, non-ASCII lanes are copied verbatim + // and stay on the plain fast path, so the high-bit term drops out: plain + // scans for control, quote and backslash; non-plain for quote and + // backslash only. + outer: + while (true) { + while (p + width <= end) { + ByteVector chunk = ByteVector.fromArray(SP, data, p); + VectorMask interesting; + if (validateUtf8) { + interesting = plain ? interestingLanes(chunk) + : ascii ? quoteBackslashOrHighLanes(chunk) + : quoteOrBackslashLanes(chunk); + } else { + interesting = plain ? controlQuoteBackslashLanes(chunk) + : quoteOrBackslashLanes(chunk); + } + if (interesting.anyTrue()) { + p += interesting.firstTrue(); + break; + } + p += width; + } + while (p < end) { + int b = data[p] & 0xFF; + if (b == '"') { + return ((long) p) | (plain ? PLAIN_BIT : 0L) | (ascii ? ASCII_BIT : 0L); + } + if (b == '\\') { + plain = false; + p += 2; // skip the backslash and the escaped byte + continue outer; + } + if (b >= 0x80) { + ascii = false; + if (validateUtf8) { + plain = false; + } + p++; + continue outer; + } + if (b < 0x20) { + plain = false; + p++; + continue outer; + } + p++; + } + return NOT_FOUND; + } + } + + @Override + int scanEscape(byte[] data, ByteBuffer chunks, int start, int end) { + final int width = SP.length(); + if (start + width > end) { + return super.scanEscape(data, chunks, start, end); + } + int p = start; + while (p + width <= end) { + ByteVector chunk = ByteVector.fromArray(SP, data, p); + VectorMask interesting = escapeOrControlLanes(chunk); + if (interesting.anyTrue()) { + return p + interesting.firstTrue(); + } + p += width; + } + return super.scanEscape(data, chunks, p, end); + } + + private static VectorMask escapeOrControlLanes(ByteVector chunk) { + VectorMask negative = chunk.lt(ZERO); + VectorMask control = chunk.lt(SPACE).andNot(negative); + return control.or(chunk.eq(BACKSLASH)); + } + + private static VectorMask interestingLanes(ByteVector chunk) { + VectorMask negative = chunk.lt(ZERO); + VectorMask lowOrQuote = chunk.lanewise(VectorOperators.XOR, TWO) + .lt(THIRTY_THREE) + .andNot(negative); + return lowOrQuote.or(chunk.eq(BACKSLASH)).or(negative); + } + + private static VectorMask quoteOrBackslashLanes(ByteVector chunk) { + return chunk.eq(DQUOTE).or(chunk.eq(BACKSLASH)); + } + + // Like interestingLanes but without the non-ASCII (high-bit) term: flags + // ASCII control characters, double quotes and backslashes only. Used as the + // plain-path mask when UTF-8 validation is disabled. + private static VectorMask controlQuoteBackslashLanes(ByteVector chunk) { + VectorMask negative = chunk.lt(ZERO); + VectorMask lowOrQuote = chunk.lanewise(VectorOperators.XOR, TWO) + .lt(THIRTY_THREE) + .andNot(negative); + return lowOrQuote.or(chunk.eq(BACKSLASH)); + } + + private static VectorMask quoteBackslashOrHighLanes(ByteVector chunk) { + return chunk.eq(DQUOTE).or(chunk.eq(BACKSLASH)).or(chunk.lt(ZERO)); + } +} diff --git a/json.gemspec b/json.gemspec index 943c78aab..557573102 100644 --- a/json.gemspec +++ b/json.gemspec @@ -44,15 +44,14 @@ spec = Gem::Specification.new do |s| "LEGAL", "README.md", "json.gemspec", - *Dir["lib/**/*.rb"], - ] + ] + Dir.glob("lib/**/*.rb", base: File.expand_path("..", __FILE__)) if java_ext s.platform = 'java' s.files += Dir["lib/json/ext/**/*.jar"] else s.extensions = Dir["ext/json/**/extconf.rb"] - s.files += Dir["ext/json/**/*.{c,h}"] + s.files += Dir["ext/json/**/*.{c,h,rb}"] end end diff --git a/lib/json.rb b/lib/json.rb index dfd9b7dfc..1969b026d 100644 --- a/lib/json.rb +++ b/lib/json.rb @@ -6,6 +6,15 @@ # # \JSON is a lightweight data-interchange format. # +# \JSON is easy for us humans to read and write, +# and equally simple for machines to read (parse) and write (generate). +# +# \JSON is language-independent, making it an ideal interchange format +# for applications in differing programming languages +# and on differing operating systems. +# +# == \JSON Values +# # A \JSON value is one of the following: # - Double-quoted text: "foo". # - Number: +1+, +1.0+, +2.0e2+. @@ -35,13 +44,13 @@ # # You can parse a \String containing \JSON data using # either of two methods: -# - JSON.parse(source, opts) -# - JSON.parse!(source, opts) +# - JSON.parse(source, **opts) +# - JSON.parse!(source, **opts) # # where # - +source+ is a Ruby object. -# - +opts+ is a \Hash object containing options -# that control both input allowed and output formatting. +# - +opts+ are keyword arguments that control both input +# allowed and output formatting. # # The difference between the two methods # is that JSON.parse! omits some checks @@ -93,7 +102,7 @@ # ruby # => 1.0 # ruby.class # => Float # ruby = JSON.parse('2.0e2') -# ruby # => 200 +# ruby # => 200.0 # ruby.class # => Float # Boolean: # ruby = JSON.parse('true') @@ -112,18 +121,32 @@ # ====== Input Options # # Option +max_nesting+ (\Integer) specifies the maximum nesting depth allowed; -# defaults to +100+; specify +false+ to disable depth checking. +# defaults to +100+; +# You can set it to +false+ to disable depth checking entirely, but that is dangerous +# when parsing untrusted input. # -# With the default, +false+: +# With the default, +100+: # source = '[0, [1, [2, [3]]]]' # ruby = JSON.parse(source) # ruby # => [0, [1, [2, [3]]]] # Too deep: # # Raises JSON::NestingError (nesting of 2 is too deep): -# JSON.parse(source, {max_nesting: 1}) +# JSON.parse(source, max_nesting: 1) # Bad value: -# # Raises TypeError (wrong argument type Symbol (expected Fixnum)): -# JSON.parse(source, {max_nesting: :foo}) +# # Raises TypeError (no implicit conversion of Symbol into Integer): +# JSON.parse(source, max_nesting: :foo) +# +# --- +# +# Option +allow_duplicate_key+ specifies whether duplicate keys in objects +# should be ignored or cause an error to be raised: +# +# When set to +false+, the default: +# JSON.parse('{"a": 1, "a": 2}') # duplicate key "a" at line 1 column 1 (JSON::ParserError) +# +# When set to +true+: +# # The last value is used. +# JSON.parse('{"a": 1, "a": 2}', allow_duplicate_key: true) # => {"a" => 2} # # --- # @@ -132,19 +155,70 @@ # defaults to +false+. # # With the default, +false+: -# # Raises JSON::ParserError (225: unexpected token at '[NaN]'): +# # Raises JSON::ParserError (unexpected token 'NaN]' at line 1 column 2): # JSON.parse('[NaN]') -# # Raises JSON::ParserError (232: unexpected token at '[Infinity]'): +# # Raises JSON::ParserError (unexpected token 'Infinity]' at line 1 column 2): # JSON.parse('[Infinity]') -# # Raises JSON::ParserError (248: unexpected token at '[-Infinity]'): +# # Raises JSON::ParserError (invalid number: '-Infinity]' at line 1 column 2): # JSON.parse('[-Infinity]') # Allow: # source = '[NaN, Infinity, -Infinity]' -# ruby = JSON.parse(source, {allow_nan: true}) +# ruby = JSON.parse(source, allow_nan: true) # ruby # => [NaN, Infinity, -Infinity] # +# --- +# +# Option +allow_trailing_comma+ (boolean) specifies whether to allow +# trailing commas in objects and arrays; +# defaults to +false+. +# +# With the default, +false+: +# JSON.parse('[1,]') # unexpected character: ']' at line 1 column 4 (JSON::ParserError) +# +# When enabled: +# JSON.parse('[1,]', allow_trailing_comma: true) # => [1] +# +# --- +# +# Option +allow_comments+ (boolean) specifies whether to allow +# JavaScript style comments (either // comment or /* comment */); +# defaults to +false+. +# +# When set to +false+, the default: +# JSON.parse('/* comment */ {"a": 1, "a": 2}') # unexpected token '/*' at line 1 column 1 (JSON::ParserError) +# +# When set to +true+, comments are ignored: +# JSON.parse('/* comment */ {"a": 1} // more comment', allow_comments: true) # => {"a" => 1} +# +# --- +# +# Option +allow_control_characters+ (boolean) specifies whether to allow +# unescaped ASCII control characters, such as newlines, in strings; +# defaults to +false+. +# +# With the default, +false+: +# JSON.parse(%{"Hello\nWorld"}) # invalid ASCII control character in string: \nWorld" at line 2 column 0 (JSON::ParserError) +# +# When enabled: +# JSON.parse(%{"Hello\nWorld"}, allow_control_characters: true) # => "Hello\nWorld" +# +# --- +# +# Option +allow_invalid_escape+ (boolean) specifies whether to ignore backslahes that are followed +# by an invalid escape character in strings; +# defaults to +false+. +# +# With the default, +false+: +# JSON.parse('"Hell\o"') # invalid escape character in string: '\o"' at line 1 column 6 (JSON::ParserError) +# +# When enabled: +# JSON.parse('"Hell\o"', allow_invalid_escape: true) # => "Hello" +# # ====== Output Options # +# Option +freeze+ (boolean) specifies whether the returned objects will be frozen; +# defaults to +false+. +# # Option +symbolize_names+ (boolean) specifies whether returned \Hash keys # should be Symbols; # defaults to +false+ (use Strings). @@ -154,8 +228,8 @@ # ruby = JSON.parse(source) # ruby # => {"a"=>"foo", "b"=>1.0, "c"=>true, "d"=>false, "e"=>nil} # Use Symbols: -# ruby = JSON.parse(source, {symbolize_names: true}) -# ruby # => {:a=>"foo", :b=>1.0, :c=>true, :d=>false, :e=>nil} +# ruby = JSON.parse(source, symbolize_names: true) +# ruby # => {a: "foo", b: 1.0, c: true, d: false, e: nil} # # --- # @@ -168,7 +242,7 @@ # ruby = JSON.parse(source) # ruby.class # => Hash # Use class \OpenStruct: -# ruby = JSON.parse(source, {object_class: OpenStruct}) +# ruby = JSON.parse(source, object_class: OpenStruct) # ruby # => # # # --- @@ -182,13 +256,8 @@ # ruby = JSON.parse(source) # ruby.class # => Array # Use class \Set: -# ruby = JSON.parse(source, {array_class: Set}) -# ruby # => # -# -# --- -# -# Option +create_additions+ (boolean) specifies whether to use \JSON additions in parsing. -# See {\JSON Additions}[#module-JSON-label-JSON+Additions]. +# ruby = JSON.parse(source, array_class: Set) +# ruby # => Set["foo", 1.0, true, false, nil] # # === Generating \JSON # @@ -250,27 +319,41 @@ # a \String containing a \JSON string representation of the source: # JSON.generate(:foo) # => '"foo"' # JSON.generate(Complex(0, 0)) # => '"0+0i"' -# JSON.generate(Dir.new('.')) # => '"#"' +# JSON.generate(Dir.new('.')) # => '"#"' # # ==== Generating Options # # ====== Input Options # # Option +allow_nan+ (boolean) specifies whether -# +NaN+, +Infinity+, and -Infinity may be generated; +# +NaN+, +Infinity+, and +-Infinity+ may be generated; # defaults to +false+. # # With the default, +false+: -# # Raises JSON::GeneratorError (920: NaN not allowed in JSON): +# # Raises JSON::GeneratorError (NaN not allowed in JSON): # JSON.generate(JSON::NaN) -# # Raises JSON::GeneratorError (917: Infinity not allowed in JSON): +# # Raises JSON::GeneratorError (Infinity not allowed in JSON): # JSON.generate(JSON::Infinity) -# # Raises JSON::GeneratorError (917: -Infinity not allowed in JSON): +# # Raises JSON::GeneratorError (-Infinity not allowed in JSON): # JSON.generate(JSON::MinusInfinity) # # Allow: -# ruby = [Float::NaN, Float::Infinity, Float::MinusInfinity] -# JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,-Infinity]' +# ruby = [Float::NAN, Float::INFINITY, JSON::NaN, JSON::Infinity, JSON::MinusInfinity] +# JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,NaN,Infinity,-Infinity]' +# +# --- +# +# Option +allow_duplicate_key+ (boolean) specifies whether +# hashes with duplicate keys should be allowed or produce an error. +# Defaults to +false+, which raises an error. +# +# With the default, +false+: +# JSON.generate({foo: 1, "foo" => 2}) +# # detected duplicate key "foo" in {foo: 1, "foo" => 2} (JSON::GeneratorError) +# +# With +true+: +# JSON.generate({foo: 1, "foo" => 2}, allow_duplicate_key: true) +# # => '{"foo":1,"foo":2}' # # --- # @@ -282,9 +365,19 @@ # JSON.generate(obj) # => '[[[[[[0]]]]]]' # # Too deep: -# # Raises JSON::NestingError (nesting of 2 is too deep): +# # Raises JSON::NestingError (nesting of 2 is too deep. Did you try to serialize objects with circular references?): # JSON.generate(obj, max_nesting: 2) # +# With +false+: +# obj = [] +# obj[0] = obj +# # Raises SystemStackError (stack level too deep): +# JSON.generate(obj, max_nesting: false) +# +# Setting +max_nesting+ to +false+ or a very large number can lead to a stack overflow +# which may leave the process in an unrecoverable state. +# It is highly discouraged. +# # ====== Escaping Options # # Options +script_safe+ (boolean) specifies wether '\u2028', '\u2029' @@ -309,7 +402,6 @@ # to be inserted after each \JSON object; defaults to the empty \String, ''. # - Option +indent+ (\String) specifies the string (usually spaces) to be # used for indentation; defaults to the empty \String, ''; -# defaults to the empty \String, ''; # has no effect unless options +array_nl+ or +object_nl+ specify newlines. # - Option +space+ (\String) specifies a string (usually a space) to be # inserted after the colon in each \JSON object's pair; @@ -317,6 +409,19 @@ # - Option +space_before+ (\String) specifies a string (usually a space) to be # inserted before the colon in each \JSON object's pair; # defaults to the empty \String, ''. +# - Option +sort_keys+ (boolean or \Proc) controls whether and how the keys of a +# hash are sorted when generating the output; defaults to +false+. +# When +true+, keys are sorted lexicographically. When a \Proc, it receives +# the entire \Hash and must return a \Hash with its pairs in the desired +# order, allowing for arbitrary sort orders. +# - Option +rfc8785+ (boolean) controls whether the generated JSON will be canonicalized +# as defined in RFC8785. +# Integers are converted to Float before formatting and may be rounded. +# Integers outside the finite Float range raise JSON::GeneratorError. +# Nonempty +indent+, +space+, +space_before+, +object_nl+, or +array_nl+, +# and enabled +ascii_only+, +script_safe+, or +allow_nan+ raise ArgumentError. +# JSON.dump defaults +allow_nan+ to +false+ when +rfc8785+ is enabled. +# JSON::Fragment contents are inserted as is; the caller must ensure they are canonical. # # In this example, +obj+ is used first to generate the shortest # \JSON data (no whitespace), then again with all formatting options @@ -342,245 +447,13 @@ # "foo" : [ # "bar", # "baz" -# ], +# ], # "bat" : { # "bam" : 0, # "bad" : 1 # } # } # -# == \JSON Additions -# -# When you "round trip" a non-\String object from Ruby to \JSON and back, -# you have a new \String, instead of the object you began with: -# ruby0 = Range.new(0, 2) -# json = JSON.generate(ruby0) -# json # => '0..2"' -# ruby1 = JSON.parse(json) -# ruby1 # => '0..2' -# ruby1.class # => String -# -# You can use \JSON _additions_ to preserve the original object. -# The addition is an extension of a ruby class, so that: -# - \JSON.generate stores more information in the \JSON string. -# - \JSON.parse, called with option +create_additions+, -# uses that information to create a proper Ruby object. -# -# This example shows a \Range being generated into \JSON -# and parsed back into Ruby, both without and with -# the addition for \Range: -# ruby = Range.new(0, 2) -# # This passage does not use the addition for Range. -# json0 = JSON.generate(ruby) -# ruby0 = JSON.parse(json0) -# # This passage uses the addition for Range. -# require 'json/add/range' -# json1 = JSON.generate(ruby) -# ruby1 = JSON.parse(json1, create_additions: true) -# # Make a nice display. -# display = <<~EOT -# Generated JSON: -# Without addition: #{json0} (#{json0.class}) -# With addition: #{json1} (#{json1.class}) -# Parsed JSON: -# Without addition: #{ruby0.inspect} (#{ruby0.class}) -# With addition: #{ruby1.inspect} (#{ruby1.class}) -# EOT -# puts display -# -# This output shows the different results: -# Generated JSON: -# Without addition: "0..2" (String) -# With addition: {"json_class":"Range","a":[0,2,false]} (String) -# Parsed JSON: -# Without addition: "0..2" (String) -# With addition: 0..2 (Range) -# -# The \JSON module includes additions for certain classes. -# You can also craft custom additions. -# See {Custom \JSON Additions}[#module-JSON-label-Custom+JSON+Additions]. -# -# === Built-in Additions -# -# The \JSON module includes additions for certain classes. -# To use an addition, +require+ its source: -# - BigDecimal: require 'json/add/bigdecimal' -# - Complex: require 'json/add/complex' -# - Date: require 'json/add/date' -# - DateTime: require 'json/add/date_time' -# - Exception: require 'json/add/exception' -# - OpenStruct: require 'json/add/ostruct' -# - Range: require 'json/add/range' -# - Rational: require 'json/add/rational' -# - Regexp: require 'json/add/regexp' -# - Set: require 'json/add/set' -# - Struct: require 'json/add/struct' -# - Symbol: require 'json/add/symbol' -# - Time: require 'json/add/time' -# -# To reduce punctuation clutter, the examples below -# show the generated \JSON via +puts+, rather than the usual +inspect+, -# -# \BigDecimal: -# require 'json/add/bigdecimal' -# ruby0 = BigDecimal(0) # 0.0 -# json = JSON.generate(ruby0) # {"json_class":"BigDecimal","b":"27:0.0"} -# ruby1 = JSON.parse(json, create_additions: true) # 0.0 -# ruby1.class # => BigDecimal -# -# \Complex: -# require 'json/add/complex' -# ruby0 = Complex(1+0i) # 1+0i -# json = JSON.generate(ruby0) # {"json_class":"Complex","r":1,"i":0} -# ruby1 = JSON.parse(json, create_additions: true) # 1+0i -# ruby1.class # Complex -# -# \Date: -# require 'json/add/date' -# ruby0 = Date.today # 2020-05-02 -# json = JSON.generate(ruby0) # {"json_class":"Date","y":2020,"m":5,"d":2,"sg":2299161.0} -# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02 -# ruby1.class # Date -# -# \DateTime: -# require 'json/add/date_time' -# ruby0 = DateTime.now # 2020-05-02T10:38:13-05:00 -# json = JSON.generate(ruby0) # {"json_class":"DateTime","y":2020,"m":5,"d":2,"H":10,"M":38,"S":13,"of":"-5/24","sg":2299161.0} -# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02T10:38:13-05:00 -# ruby1.class # DateTime -# -# \Exception (and its subclasses including \RuntimeError): -# require 'json/add/exception' -# ruby0 = Exception.new('A message') # A message -# json = JSON.generate(ruby0) # {"json_class":"Exception","m":"A message","b":null} -# ruby1 = JSON.parse(json, create_additions: true) # A message -# ruby1.class # Exception -# ruby0 = RuntimeError.new('Another message') # Another message -# json = JSON.generate(ruby0) # {"json_class":"RuntimeError","m":"Another message","b":null} -# ruby1 = JSON.parse(json, create_additions: true) # Another message -# ruby1.class # RuntimeError -# -# \OpenStruct: -# require 'json/add/ostruct' -# ruby0 = OpenStruct.new(name: 'Matz', language: 'Ruby') # # -# json = JSON.generate(ruby0) # {"json_class":"OpenStruct","t":{"name":"Matz","language":"Ruby"}} -# ruby1 = JSON.parse(json, create_additions: true) # # -# ruby1.class # OpenStruct -# -# \Range: -# require 'json/add/range' -# ruby0 = Range.new(0, 2) # 0..2 -# json = JSON.generate(ruby0) # {"json_class":"Range","a":[0,2,false]} -# ruby1 = JSON.parse(json, create_additions: true) # 0..2 -# ruby1.class # Range -# -# \Rational: -# require 'json/add/rational' -# ruby0 = Rational(1, 3) # 1/3 -# json = JSON.generate(ruby0) # {"json_class":"Rational","n":1,"d":3} -# ruby1 = JSON.parse(json, create_additions: true) # 1/3 -# ruby1.class # Rational -# -# \Regexp: -# require 'json/add/regexp' -# ruby0 = Regexp.new('foo') # (?-mix:foo) -# json = JSON.generate(ruby0) # {"json_class":"Regexp","o":0,"s":"foo"} -# ruby1 = JSON.parse(json, create_additions: true) # (?-mix:foo) -# ruby1.class # Regexp -# -# \Set: -# require 'json/add/set' -# ruby0 = Set.new([0, 1, 2]) # # -# json = JSON.generate(ruby0) # {"json_class":"Set","a":[0,1,2]} -# ruby1 = JSON.parse(json, create_additions: true) # # -# ruby1.class # Set -# -# \Struct: -# require 'json/add/struct' -# Customer = Struct.new(:name, :address) # Customer -# ruby0 = Customer.new("Dave", "123 Main") # # -# json = JSON.generate(ruby0) # {"json_class":"Customer","v":["Dave","123 Main"]} -# ruby1 = JSON.parse(json, create_additions: true) # # -# ruby1.class # Customer -# -# \Symbol: -# require 'json/add/symbol' -# ruby0 = :foo # foo -# json = JSON.generate(ruby0) # {"json_class":"Symbol","s":"foo"} -# ruby1 = JSON.parse(json, create_additions: true) # foo -# ruby1.class # Symbol -# -# \Time: -# require 'json/add/time' -# ruby0 = Time.now # 2020-05-02 11:28:26 -0500 -# json = JSON.generate(ruby0) # {"json_class":"Time","s":1588436906,"n":840560000} -# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02 11:28:26 -0500 -# ruby1.class # Time -# -# -# === Custom \JSON Additions -# -# In addition to the \JSON additions provided, -# you can craft \JSON additions of your own, -# either for Ruby built-in classes or for user-defined classes. -# -# Here's a user-defined class +Foo+: -# class Foo -# attr_accessor :bar, :baz -# def initialize(bar, baz) -# self.bar = bar -# self.baz = baz -# end -# end -# -# Here's the \JSON addition for it: -# # Extend class Foo with JSON addition. -# class Foo -# # Serialize Foo object with its class name and arguments -# def to_json(*args) -# { -# JSON.create_id => self.class.name, -# 'a' => [ bar, baz ] -# }.to_json(*args) -# end -# # Deserialize JSON string by constructing new Foo object with arguments. -# def self.json_create(object) -# new(*object['a']) -# end -# end -# -# Demonstration: -# require 'json' -# # This Foo object has no custom addition. -# foo0 = Foo.new(0, 1) -# json0 = JSON.generate(foo0) -# obj0 = JSON.parse(json0) -# # Lood the custom addition. -# require_relative 'foo_addition' -# # This foo has the custom addition. -# foo1 = Foo.new(0, 1) -# json1 = JSON.generate(foo1) -# obj1 = JSON.parse(json1, create_additions: true) -# # Make a nice display. -# display = <<~EOT -# Generated JSON: -# Without custom addition: #{json0} (#{json0.class}) -# With custom addition: #{json1} (#{json1.class}) -# Parsed JSON: -# Without custom addition: #{obj0.inspect} (#{obj0.class}) -# With custom addition: #{obj1.inspect} (#{obj1.class}) -# EOT -# puts display -# -# Output: -# -# Generated JSON: -# Without custom addition: "#" (String) -# With custom addition: {"json_class":"Foo","a":[0,1]} (String) -# Parsed JSON: -# Without custom addition: "#" (String) -# With custom addition: # (Foo) -# module JSON require 'json/version' require 'json/ext' diff --git a/lib/json/add/bigdecimal.rb b/lib/json/add/bigdecimal.rb deleted file mode 100644 index 5dbc12c07..000000000 --- a/lib/json/add/bigdecimal.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end -begin - require 'bigdecimal' -rescue LoadError -end - -class BigDecimal - - # See #as_json. - def self.json_create(object) - BigDecimal._load object['b'] - end - - # Methods BigDecimal#as_json and +BigDecimal.json_create+ may be used - # to serialize and deserialize a \BigDecimal object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method BigDecimal#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/bigdecimal' - # x = BigDecimal(2).as_json # => {"json_class"=>"BigDecimal", "b"=>"27:0.2e1"} - # y = BigDecimal(2.0, 4).as_json # => {"json_class"=>"BigDecimal", "b"=>"36:0.2e1"} - # z = BigDecimal(Complex(2, 0)).as_json # => {"json_class"=>"BigDecimal", "b"=>"27:0.2e1"} - # - # \Method +JSON.create+ deserializes such a hash, returning a \BigDecimal object: - # - # BigDecimal.json_create(x) # => 0.2e1 - # BigDecimal.json_create(y) # => 0.2e1 - # BigDecimal.json_create(z) # => 0.2e1 - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'b' => _dump.force_encoding(Encoding::UTF_8), - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/bigdecimal' - # puts BigDecimal(2).to_json - # puts BigDecimal(2.0, 4).to_json - # puts BigDecimal(Complex(2, 0)).to_json - # - # Output: - # - # {"json_class":"BigDecimal","b":"27:0.2e1"} - # {"json_class":"BigDecimal","b":"36:0.2e1"} - # {"json_class":"BigDecimal","b":"27:0.2e1"} - # - def to_json(*args) - as_json.to_json(*args) - end -end if defined?(::BigDecimal) diff --git a/lib/json/add/complex.rb b/lib/json/add/complex.rb deleted file mode 100644 index a69002eff..000000000 --- a/lib/json/add/complex.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Complex - - # See #as_json. - def self.json_create(object) - Complex(object['r'], object['i']) - end - - # Methods Complex#as_json and +Complex.json_create+ may be used - # to serialize and deserialize a \Complex object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Complex#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/complex' - # x = Complex(2).as_json # => {"json_class"=>"Complex", "r"=>2, "i"=>0} - # y = Complex(2.0, 4).as_json # => {"json_class"=>"Complex", "r"=>2.0, "i"=>4} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Complex object: - # - # Complex.json_create(x) # => (2+0i) - # Complex.json_create(y) # => (2.0+4i) - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'r' => real, - 'i' => imag, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/complex' - # puts Complex(2).to_json - # puts Complex(2.0, 4).to_json - # - # Output: - # - # {"json_class":"Complex","r":2,"i":0} - # {"json_class":"Complex","r":2.0,"i":4} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/core.rb b/lib/json/add/core.rb deleted file mode 100644 index 485f097ff..000000000 --- a/lib/json/add/core.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true -# This file requires the implementations of ruby core's custom objects for -# serialisation/deserialisation. - -require 'json/add/date' -require 'json/add/date_time' -require 'json/add/exception' -require 'json/add/range' -require 'json/add/regexp' -require 'json/add/struct' -require 'json/add/symbol' -require 'json/add/time' diff --git a/lib/json/add/date.rb b/lib/json/add/date.rb deleted file mode 100644 index 66965d491..000000000 --- a/lib/json/add/date.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end -require 'date' - -class Date - - # See #as_json. - def self.json_create(object) - civil(*object.values_at('y', 'm', 'd', 'sg')) - end - - alias start sg unless method_defined?(:start) - - # Methods Date#as_json and +Date.json_create+ may be used - # to serialize and deserialize a \Date object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Date#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/date' - # x = Date.today.as_json - # # => {"json_class"=>"Date", "y"=>2023, "m"=>11, "d"=>21, "sg"=>2299161.0} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Date object: - # - # Date.json_create(x) - # # => # - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'y' => year, - 'm' => month, - 'd' => day, - 'sg' => start, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/date' - # puts Date.today.to_json - # - # Output: - # - # {"json_class":"Date","y":2023,"m":11,"d":21,"sg":2299161.0} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/date_time.rb b/lib/json/add/date_time.rb deleted file mode 100644 index 569f6ec17..000000000 --- a/lib/json/add/date_time.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end -require 'date' - -class DateTime - - # See #as_json. - def self.json_create(object) - args = object.values_at('y', 'm', 'd', 'H', 'M', 'S') - of_a, of_b = object['of'].split('/') - if of_b and of_b != '0' - args << Rational(of_a.to_i, of_b.to_i) - else - args << of_a - end - args << object['sg'] - civil(*args) - end - - alias start sg unless method_defined?(:start) - - # Methods DateTime#as_json and +DateTime.json_create+ may be used - # to serialize and deserialize a \DateTime object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method DateTime#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/datetime' - # x = DateTime.now.as_json - # # => {"json_class"=>"DateTime", "y"=>2023, "m"=>11, "d"=>21, "sg"=>2299161.0} - # - # \Method +JSON.create+ deserializes such a hash, returning a \DateTime object: - # - # DateTime.json_create(x) # BUG? Raises Date::Error "invalid date" - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'y' => year, - 'm' => month, - 'd' => day, - 'H' => hour, - 'M' => min, - 'S' => sec, - 'of' => offset.to_s, - 'sg' => start, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/datetime' - # puts DateTime.now.to_json - # - # Output: - # - # {"json_class":"DateTime","y":2023,"m":11,"d":21,"sg":2299161.0} - # - def to_json(*args) - as_json.to_json(*args) - end -end - - diff --git a/lib/json/add/exception.rb b/lib/json/add/exception.rb deleted file mode 100644 index 5338ff83d..000000000 --- a/lib/json/add/exception.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Exception - - # See #as_json. - def self.json_create(object) - result = new(object['m']) - result.set_backtrace object['b'] - result - end - - # Methods Exception#as_json and +Exception.json_create+ may be used - # to serialize and deserialize a \Exception object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Exception#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/exception' - # x = Exception.new('Foo').as_json # => {"json_class"=>"Exception", "m"=>"Foo", "b"=>nil} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Exception object: - # - # Exception.json_create(x) # => # - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'm' => message, - 'b' => backtrace, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/exception' - # puts Exception.new('Foo').to_json - # - # Output: - # - # {"json_class":"Exception","m":"Foo","b":null} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/ostruct.rb b/lib/json/add/ostruct.rb deleted file mode 100644 index 534403b78..000000000 --- a/lib/json/add/ostruct.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end -begin - require 'ostruct' -rescue LoadError -end - -class OpenStruct - - # See #as_json. - def self.json_create(object) - new(object['t'] || object[:t]) - end - - # Methods OpenStruct#as_json and +OpenStruct.json_create+ may be used - # to serialize and deserialize a \OpenStruct object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method OpenStruct#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/ostruct' - # x = OpenStruct.new('name' => 'Rowdy', :age => nil).as_json - # # => {"json_class"=>"OpenStruct", "t"=>{:name=>'Rowdy', :age=>nil}} - # - # \Method +JSON.create+ deserializes such a hash, returning a \OpenStruct object: - # - # OpenStruct.json_create(x) - # # => # - # - def as_json(*) - klass = self.class.name - klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!" - { - JSON.create_id => klass, - 't' => table, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/ostruct' - # puts OpenStruct.new('name' => 'Rowdy', :age => nil).to_json - # - # Output: - # - # {"json_class":"OpenStruct","t":{'name':'Rowdy',"age":null}} - # - def to_json(*args) - as_json.to_json(*args) - end -end if defined?(::OpenStruct) diff --git a/lib/json/add/range.rb b/lib/json/add/range.rb deleted file mode 100644 index eb4b29a8e..000000000 --- a/lib/json/add/range.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Range - - # See #as_json. - def self.json_create(object) - new(*object['a']) - end - - # Methods Range#as_json and +Range.json_create+ may be used - # to serialize and deserialize a \Range object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Range#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/range' - # x = (1..4).as_json # => {"json_class"=>"Range", "a"=>[1, 4, false]} - # y = (1...4).as_json # => {"json_class"=>"Range", "a"=>[1, 4, true]} - # z = ('a'..'d').as_json # => {"json_class"=>"Range", "a"=>["a", "d", false]} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Range object: - # - # Range.json_create(x) # => 1..4 - # Range.json_create(y) # => 1...4 - # Range.json_create(z) # => "a".."d" - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'a' => [ first, last, exclude_end? ] - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/range' - # puts (1..4).to_json - # puts (1...4).to_json - # puts ('a'..'d').to_json - # - # Output: - # - # {"json_class":"Range","a":[1,4,false]} - # {"json_class":"Range","a":[1,4,true]} - # {"json_class":"Range","a":["a","d",false]} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/rational.rb b/lib/json/add/rational.rb deleted file mode 100644 index 1eb231474..000000000 --- a/lib/json/add/rational.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Rational - - # See #as_json. - def self.json_create(object) - Rational(object['n'], object['d']) - end - - # Methods Rational#as_json and +Rational.json_create+ may be used - # to serialize and deserialize a \Rational object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Rational#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/rational' - # x = Rational(2, 3).as_json - # # => {"json_class"=>"Rational", "n"=>2, "d"=>3} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Rational object: - # - # Rational.json_create(x) - # # => (2/3) - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'n' => numerator, - 'd' => denominator, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/rational' - # puts Rational(2, 3).to_json - # - # Output: - # - # {"json_class":"Rational","n":2,"d":3} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/regexp.rb b/lib/json/add/regexp.rb deleted file mode 100644 index f033dd1de..000000000 --- a/lib/json/add/regexp.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Regexp - - # See #as_json. - def self.json_create(object) - new(object['s'], object['o']) - end - - # Methods Regexp#as_json and +Regexp.json_create+ may be used - # to serialize and deserialize a \Regexp object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Regexp#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/regexp' - # x = /foo/.as_json - # # => {"json_class"=>"Regexp", "o"=>0, "s"=>"foo"} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Regexp object: - # - # Regexp.json_create(x) # => /foo/ - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'o' => options, - 's' => source, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/regexp' - # puts /foo/.to_json - # - # Output: - # - # {"json_class":"Regexp","o":0,"s":"foo"} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/set.rb b/lib/json/add/set.rb deleted file mode 100644 index c521d8b90..000000000 --- a/lib/json/add/set.rb +++ /dev/null @@ -1,48 +0,0 @@ -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end -defined?(::Set) or require 'set' - -class Set - - # See #as_json. - def self.json_create(object) - new object['a'] - end - - # Methods Set#as_json and +Set.json_create+ may be used - # to serialize and deserialize a \Set object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Set#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/set' - # x = Set.new(%w/foo bar baz/).as_json - # # => {"json_class"=>"Set", "a"=>["foo", "bar", "baz"]} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Set object: - # - # Set.json_create(x) # => # - # - def as_json(*) - { - JSON.create_id => self.class.name, - 'a' => to_a, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/set' - # puts Set.new(%w/foo bar baz/).to_json - # - # Output: - # - # {"json_class":"Set","a":["foo","bar","baz"]} - # - def to_json(*args) - as_json.to_json(*args) - end -end - diff --git a/lib/json/add/struct.rb b/lib/json/add/struct.rb deleted file mode 100644 index 98c38d326..000000000 --- a/lib/json/add/struct.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Struct - - # See #as_json. - def self.json_create(object) - new(*object['v']) - end - - # Methods Struct#as_json and +Struct.json_create+ may be used - # to serialize and deserialize a \Struct object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Struct#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/struct' - # Customer = Struct.new('Customer', :name, :address, :zip) - # x = Struct::Customer.new.as_json - # # => {"json_class"=>"Struct::Customer", "v"=>[nil, nil, nil]} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Struct object: - # - # Struct::Customer.json_create(x) - # # => # - # - def as_json(*) - klass = self.class.name - klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!" - { - JSON.create_id => klass, - 'v' => values, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/struct' - # Customer = Struct.new('Customer', :name, :address, :zip) - # puts Struct::Customer.new.to_json - # - # Output: - # - # {"json_class":"Struct","t":{'name':'Rowdy',"age":null}} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/add/symbol.rb b/lib/json/add/symbol.rb deleted file mode 100644 index 20dd59485..000000000 --- a/lib/json/add/symbol.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Symbol - - # Methods Symbol#as_json and +Symbol.json_create+ may be used - # to serialize and deserialize a \Symbol object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Symbol#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/symbol' - # x = :foo.as_json - # # => {"json_class"=>"Symbol", "s"=>"foo"} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Symbol object: - # - # Symbol.json_create(x) # => :foo - # - def as_json(*) - { - JSON.create_id => self.class.name, - 's' => to_s, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/symbol' - # puts :foo.to_json - # - # Output: - # - # # {"json_class":"Symbol","s":"foo"} - # - def to_json(state = nil, *a) - state = ::JSON::State.from_state(state) - if state.strict? - super - else - as_json.to_json(state, *a) - end - end - - # See #as_json. - def self.json_create(o) - o['s'].to_sym - end -end diff --git a/lib/json/add/time.rb b/lib/json/add/time.rb deleted file mode 100644 index 05a1f242f..000000000 --- a/lib/json/add/time.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true -unless defined?(::JSON::JSON_LOADED) and ::JSON::JSON_LOADED - require 'json' -end - -class Time - - # See #as_json. - def self.json_create(object) - if usec = object.delete('u') # used to be tv_usec -> tv_nsec - object['n'] = usec * 1000 - end - at(object['s'], Rational(object['n'], 1000)) - end - - # Methods Time#as_json and +Time.json_create+ may be used - # to serialize and deserialize a \Time object; - # see Marshal[https://docs.ruby-lang.org/en/master/Marshal.html]. - # - # \Method Time#as_json serializes +self+, - # returning a 2-element hash representing +self+: - # - # require 'json/add/time' - # x = Time.now.as_json - # # => {"json_class"=>"Time", "s"=>1700931656, "n"=>472846644} - # - # \Method +JSON.create+ deserializes such a hash, returning a \Time object: - # - # Time.json_create(x) - # # => 2023-11-25 11:00:56.472846644 -0600 - # - def as_json(*) - { - JSON.create_id => self.class.name, - 's' => tv_sec, - 'n' => tv_nsec, - } - end - - # Returns a JSON string representing +self+: - # - # require 'json/add/time' - # puts Time.now.to_json - # - # Output: - # - # {"json_class":"Time","s":1700931678,"n":980650786} - # - def to_json(*args) - as_json.to_json(*args) - end -end diff --git a/lib/json/common.rb b/lib/json/common.rb index 3f1e5ce96..bf96f4137 100644 --- a/lib/json/common.rb +++ b/lib/json/common.rb @@ -3,10 +3,41 @@ require 'json/version' module JSON - autoload :GenericObject, 'json/generic_object' + module ParserOptions # :nodoc: + class << self + def on_load(on_load, object_class, array_class) + on_load = object_class_proc(object_class, on_load) if object_class + on_load = array_class_proc(array_class, on_load) if array_class + on_load + end + + private + + def object_class_proc(object_class, on_load) + ->(obj) do + if Hash === obj + object = object_class.new + obj.each { |k, v| object[k] = v } + obj = object + end + on_load.nil? ? obj : on_load.call(obj) + end + end + + def array_class_proc(array_class, on_load) + ->(obj) do + if Array === obj + array = array_class.new + obj.each { |v| array << v } + obj = array + end + on_load.nil? ? obj : on_load.call(obj) + end + end + end + end - NOT_SET = Object.new.freeze - private_constant :NOT_SET + private_constant :ParserOptions class << self # :call-seq: @@ -20,13 +51,15 @@ class << self # Otherwise, calls JSON.generate with +object+ and +opts+ (see method #generate): # ruby = [0, 1, nil] # JSON[ruby] # => '[0,1,null]' - def [](object, opts = {}) + def [](object, opts = nil) + opts ||= {}.freeze + if object.is_a?(String) - return JSON.parse(object, opts) + return JSON.parse(object, **opts) elsif object.respond_to?(:to_str) str = object.to_str if str.is_a?(String) - return JSON.parse(str, opts) + return JSON.parse(str, **opts) end end @@ -43,105 +76,124 @@ def parser=(parser) # :nodoc: const_set :Parser, parser end - # Return the constant located at _path_. The format of _path_ has to be - # either ::A::B::C or A::B::C. In any case, A has to be located at the top - # level (absolute namespace path?). If there doesn't exist a constant at - # the given path, an ArgumentError is raised. - def deep_const_get(path) # :nodoc: - Object.const_get(path) - rescue NameError => e - raise ArgumentError, "can't get const #{path}: #{e}" - end - # Set the module _generator_ to be used by JSON. def generator=(generator) # :nodoc: old, $VERBOSE = $VERBOSE, nil + @generator = generator - generator_methods = generator::GeneratorMethods - for const in generator_methods.constants - klass = const_get(const) - modul = generator_methods.const_get(const) - klass.class_eval do - instance_methods(false).each do |m| - m.to_s == 'to_json' and remove_method m + if generator.const_defined?(:GeneratorMethods) + generator_methods = generator::GeneratorMethods + for const in generator_methods.constants + klass = const_get(const) + modul = generator_methods.const_get(const) + klass.class_eval do + instance_methods(false).each do |m| + m.to_s == 'to_json' and remove_method m + end + include modul end - include modul end end self.state = generator::State - const_set :State, self.state - const_set :SAFE_STATE_PROTOTYPE, State.new # for JRuby - const_set :FAST_STATE_PROTOTYPE, create_fast_state - const_set :PRETTY_STATE_PROTOTYPE, create_pretty_state + const_set :State, state ensure $VERBOSE = old end - def create_fast_state - State.new( - :indent => '', - :space => '', - :object_nl => "", - :array_nl => "", - :max_nesting => false - ) - end - - def create_pretty_state - State.new( - :indent => ' ', - :space => ' ', - :object_nl => "\n", - :array_nl => "\n" - ) - end - # Returns the JSON generator module that is used by JSON. attr_reader :generator # Sets or Returns the JSON generator state class that is used by JSON. attr_accessor :state - end - # Sets create identifier, which is used to decide if the _json_create_ - # hook of a class should be called; initial value is +json_class+: - # JSON.create_id # => 'json_class' - def self.create_id=(new_value) - Thread.current[:"JSON.create_id"] = new_value.dup.freeze - end + private - # Returns the current create identifier. - # See also JSON.create_id=. - def self.create_id - Thread.current[:"JSON.create_id"] || 'json_class' + # Called from the extension when a hash has both string and symbol keys + def on_mixed_keys_hash(hash) + set = {} + hash.each_key do |key| + key_str = key.to_s + + if set[key_str] + raise GeneratorError, "detected duplicate key #{key_str.inspect} in #{hash.inspect}" + else + set[key_str] = true + end + end + end end - NaN = 0.0/0 + NaN = Float::NAN - Infinity = 1.0/0 + Infinity = Float::INFINITY MinusInfinity = -Infinity # The base exception for JSON errors. - class JSONError < StandardError - def self.wrap(exception) - obj = new("Wrapped(#{exception.class}): #{exception.message.inspect}") - obj.set_backtrace exception.backtrace - obj - end - end + class JSONError < StandardError; end # This exception is raised if a parser error occurs. - class ParserError < JSONError; end + class ParserError < JSONError + # Line number where the parser encountered an error. + # Is +nil+ when raised by JSON::ResumableParser. + attr_reader :line + + # One-based column number where the parser encountered an error, counted in Unicode codepoints. + # Is +nil+ when raised by JSON::ResumableParser. + attr_reader :column + + # Returns a best effort JSONPath string representing where in the document + # the parser encountered an error: + # + # begin + # JSON.parse('{"articles": [ { "title": invalid } ]}') + # rescue JSON::ParserError => error + # error.json_path # => "$.articles[0].title" + # end + def json_path + return @json_path if String === @json_path + + if Array === @json_path + path = build_json_path(@json_path) + @json_path = path unless frozen? + return path + end + end + + private + + def build_json_path(segments) + error = false + path = segments.filter_map do |segment| + next if error + + case segment + when Integer + "[#{segment}]" + when String, Symbol + segment = segment.to_s + if !segment.valid_encoding? + segment = segment.b.gsub(/["\\]/n, { '"' => '\\"', '\\' => '\\\\' }).force_encoding(Encoding::UTF_8) + %{["#{segment}"]} + elsif segment.match?(/\A[a-zA-Z\$\_][a-zA-Z\$\_0-9]*\z/) + ".#{segment}" + else + segment = segment.gsub(/["\\]/, { '"' => '\\"', '\\' => '\\\\' }) + %{["#{segment}"]} + end + else + error = true + nil + end + end.join + "$#{path}".freeze + end + end # This exception is raised if the nesting of parsed data structures is too # deep. class NestingError < ParserError; end - # :stopdoc: - class CircularDatastructure < NestingError; end - # :startdoc: - # This exception is raised if a generator or unparser error occurs. class GeneratorError < JSONError attr_reader :invalid_object @@ -152,21 +204,17 @@ def initialize(message, invalid_object = nil) end def detailed_message(...) + # Exception#detailed_message doesn't exist until Ruby 3.2 + super_message = defined?(super) ? super : message + if @invalid_object.nil? - super + super_message else - "#{super}\nInvalid object: #{@invalid_object.inspect}" + "#{super_message}\nInvalid object: #{@invalid_object.inspect}" end end end - # For backwards compatibility - UnparserError = GeneratorError # :nodoc: - - # This exception is raised if the required unicode support is missing on the - # system. Usually this means that the iconv library is not installed. - class MissingUnicodeSupport < JSONError; end - # Fragment of JSON document that is to be included as is: # fragment = JSON::Fragment.new("[1, 2, 3]") # JSON.generate({ count: 3, items: fragments }) @@ -176,11 +224,11 @@ class MissingUnicodeSupport < JSONError; end # to string interpolation. # # Note: no validation is performed on the provided string. It is the - # responsability of the caller to ensure the string contains valid JSON. + # responsibility of the caller to ensure the string contains valid JSON. Fragment = Struct.new(:json) do def initialize(json) unless string = String.try_convert(json) - raise TypeError, " no implicit conversion of #{json.class} into String" + raise TypeError, "no implicit conversion of #{json.class} into String" end super(string) @@ -238,11 +286,16 @@ def to_json(state = nil, *) # --- # # Raises an exception if +source+ is not valid JSON: - # # Raises JSON::ParserError (783: unexpected token at ''): - # JSON.parse('') + # # Raises JSON::ParserError (unexpected character: 'invalid' at line 1 column 1): + # JSON.parse('invalid') # - def parse(source, opts = nil) - Parser.parse(source, opts) + def parse(source, on_load: nil, object_class: nil, array_class: nil, **options) + if object_class || array_class + on_load = ParserOptions.on_load(on_load, object_class, array_class) + end + + options[:on_load] = on_load if on_load + Parser.parse(source, options) end # :call-seq: @@ -256,35 +309,30 @@ def parse(source, opts = nil) # - Option +max_nesting+, if not provided, defaults to +false+, # which disables checking for nesting depth. # - Option +allow_nan+, if not provided, defaults to +true+. - def parse!(source, opts = nil) - options = { - :max_nesting => false, - :allow_nan => true - } - options.merge!(opts) if opts - Parser.new(source, options).parse + def parse!(source, **options) + parse(source, max_nesting: false, allow_nan: true, **options) end # :call-seq: - # JSON.load_file(path, opts={}) -> object + # JSON.load_file(path, **) -> object # # Calls: - # parse(File.read(path), opts) + # parse(File.read(path), **) # # See method #parse. - def load_file(filespec, opts = nil) - parse(File.read(filespec, encoding: Encoding::UTF_8), opts) + def load_file(filespec, **options) + parse(File.read(filespec, encoding: Encoding::UTF_8), **options) end # :call-seq: - # JSON.load_file!(path, opts = {}) + # JSON.load_file!(path, **) # # Calls: - # JSON.parse!(File.read(path, opts)) + # JSON.parse!(File.read(path), **) # # See method #parse! - def load_file!(filespec, opts = nil) - parse!(File.read(filespec, encoding: Encoding::UTF_8), opts) + def load_file!(filespec, **options) + parse!(File.read(filespec, encoding: Encoding::UTF_8), **options) end # :call-seq: @@ -292,7 +340,7 @@ def load_file!(filespec, opts = nil) # # Returns a \String containing the generated \JSON data. # - # See also JSON.fast_generate, JSON.pretty_generate. + # See also JSON.pretty_generate. # # Argument +obj+ is the Ruby object to be converted to \JSON. # @@ -320,51 +368,24 @@ def load_file!(filespec, opts = nil) # # Raises an exception if +obj+ contains circular references: # a = []; b = []; a.push(b); b.push(a) - # # Raises JSON::NestingError (nesting of 100 is too deep): + # # Raises JSON::NestingError (nesting of 100 is too deep. Did you try to serialize objects with circular references?): # JSON.generate(a) # def generate(obj, opts = nil) if State === opts opts.generate(obj) else - State.generate(obj, opts, nil) - end - end - - # :stopdoc: - # I want to deprecate these later, so I'll first be silent about them, and - # later delete them. - alias unparse generate - module_function :unparse - # :startdoc: - - # :call-seq: - # JSON.fast_generate(obj, opts) -> new_string - # - # Arguments +obj+ and +opts+ here are the same as - # arguments +obj+ and +opts+ in JSON.generate. - # - # By default, generates \JSON data without checking - # for circular references in +obj+ (option +max_nesting+ set to +false+, disabled). - # - # Raises an exception if +obj+ contains circular references: - # a = []; b = []; a.push(b); b.push(a) - # # Raises SystemStackError (stack level too deep): - # JSON.fast_generate(a) - def fast_generate(obj, opts = nil) - if State === opts - state = opts - else - state = JSON.create_fast_state.configure(opts) + State.generate(obj, opts.frozen? ? opts : opts.dup, nil) end - state.generate(obj) end - # :stopdoc: - # I want to deprecate these later, so I'll first be silent about them, and later delete them. - alias fast_unparse fast_generate - module_function :fast_unparse - # :startdoc: + PRETTY_GENERATE_OPTIONS = { + indent: ' ', + space: ' ', + object_nl: "\n", + array_nl: "\n", + }.freeze + private_constant :PRETTY_GENERATE_OPTIONS # :call-seq: # JSON.pretty_generate(obj, opts = nil) -> new_string @@ -397,62 +418,34 @@ def fast_generate(obj, opts = nil) # } # def pretty_generate(obj, opts = nil) - if State === opts - state, opts = opts, nil - else - state = JSON.create_pretty_state - end + return opts.generate(obj) if State === opts + + options = PRETTY_GENERATE_OPTIONS + if opts - if opts.respond_to? :to_hash - opts = opts.to_hash - elsif opts.respond_to? :to_h - opts = opts.to_h - else - raise TypeError, "can't convert #{opts.class} into Hash" + unless opts.is_a?(Hash) + if opts.respond_to? :to_hash + opts = opts.to_hash + elsif opts.respond_to? :to_h + opts = opts.to_h + else + raise TypeError, "can't convert #{opts.class} into Hash" + end end - state.configure(opts) - end - state.generate(obj) - end - # :stopdoc: - # I want to deprecate these later, so I'll first be silent about them, and later delete them. - alias pretty_unparse pretty_generate - module_function :pretty_unparse - # :startdoc: + options = options.merge(opts) + end - class << self - # Sets or returns default options for the JSON.unsafe_load method. - # Initially: - # opts = JSON.load_default_options - # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true} - attr_accessor :unsafe_load_default_options + State.generate(obj, options, nil) end - self.unsafe_load_default_options = { - :max_nesting => false, - :allow_nan => true, - :allow_blank => true, - :create_additions => true, - } - class << self - # Sets or returns default options for the JSON.load method. - # Initially: - # opts = JSON.load_default_options - # opts # => {:max_nesting=>false, :allow_nan=>true, :allow_blank=>true, :create_additions=>true} - attr_accessor :load_default_options - end - self.load_default_options = { - :allow_nan => true, - :allow_blank => true, - :create_additions => nil, - } # :call-seq: + # JSON.unsafe_load(source, options = {}) -> object # JSON.unsafe_load(source, proc = nil, options = {}) -> object # # Returns the Ruby objects created by parsing the given +source+. # - # BEWARE: This method is meant to serialise data from trusted user input, + # BEWARE: This method is meant to deserialise data from trusted user input, # like from your own database server or clients under your control, it could # be dangerous to allow untrusted users to pass JSON sources into it. # @@ -472,7 +465,6 @@ class << self # See details below. # - Argument +opts+, if given, contains a \Hash of options for the parsing. # See {Parsing Options}[#module-JSON-label-Parsing+Options]. - # The default options can be changed via method JSON.unsafe_load_default_options=. # # --- # @@ -555,6 +547,7 @@ class << self # when Array # obj.map! {|v| deserialize_obj v } # end + # obj # }) # pp ruby # Output: @@ -576,46 +569,16 @@ class << self # #"Admin", "password"=>"0wn3d"}>} # - def unsafe_load(source, proc = nil, options = nil) - opts = if options.nil? - unsafe_load_default_options - else - unsafe_load_default_options.merge(options) - end - - unless source.is_a?(String) - if source.respond_to? :to_str - source = source.to_str - elsif source.respond_to? :to_io - source = source.to_io.read - elsif source.respond_to?(:read) - source = source.read - end - end - - if opts[:allow_blank] && (source.nil? || source.empty?) - source = 'null' - end - result = parse(source, opts) - recurse_proc(result, &proc) if proc - result + def unsafe_load(source, proc = nil, **options) + load(source, proc, max_nesting: false, **options) end # :call-seq: + # JSON.load(source, options = {}) -> object # JSON.load(source, proc = nil, options = {}) -> object # # Returns the Ruby objects created by parsing the given +source+. # - # BEWARE: This method is meant to serialise data from trusted user input, - # like from your own database server or clients under your control, it could - # be dangerous to allow untrusted users to pass JSON sources into it. - # If you must use it, use JSON.unsafe_load instead to make it clear. - # - # Since JSON version 2.8.0, `load` emits a deprecation warning when a - # non native type is deserialized, without `create_additions` being explicitly - # enabled, and in JSON version 3.0, `load` will have `create_additions` disabled - # by default. - # # - Argument +source+ must be, or be convertible to, a \String: # - If +source+ responds to instance method +to_str+, # source.to_str becomes the source. @@ -632,7 +595,6 @@ def unsafe_load(source, proc = nil, options = nil) # See details below. # - Argument +opts+, if given, contains a \Hash of options for the parsing. # See {Parsing Options}[#module-JSON-label-Parsing+Options]. - # The default options can be changed via method JSON.load_default_options=. # # --- # @@ -715,6 +677,7 @@ def unsafe_load(source, proc = nil, options = nil) # when Array # obj.map! {|v| deserialize_obj v } # end + # obj # }) # pp ruby # Output: @@ -736,72 +699,37 @@ def unsafe_load(source, proc = nil, options = nil) # #"Admin", "password"=>"0wn3d"}>} # - def load(source, proc = nil, options = nil) - opts = if options.nil? - load_default_options - else - load_default_options.merge(options) - end - + def load(source, proc = nil, allow_blank: true, **options) unless source.is_a?(String) if source.respond_to? :to_str source = source.to_str + elsif source.respond_to? :read + source = source.read elsif source.respond_to? :to_io source = source.to_io.read - elsif source.respond_to?(:read) - source = source.read end end - if opts[:allow_blank] && (source.nil? || source.empty?) + if allow_blank && (source.nil? || (String === source && source.empty?)) source = 'null' end - result = parse(source, opts) - recurse_proc(result, &proc) if proc - result - end - # Recursively calls passed _Proc_ if the parsed data structure is an _Array_ or _Hash_ - def recurse_proc(result, &proc) # :nodoc: - case result - when Array - result.each { |x| recurse_proc x, &proc } - proc.call result - when Hash - result.each { |x, y| recurse_proc x, &proc; recurse_proc y, &proc } - proc.call result + if proc + parse(source, allow_nan: true, on_load: proc.to_proc, **options) else - proc.call result + parse(source, allow_nan: true, **options) end end - alias restore load - module_function :restore - - class << self - # Sets or returns the default options for the JSON.dump method. - # Initially: - # opts = JSON.dump_default_options - # opts # => {:max_nesting=>false, :allow_nan=>true} - attr_accessor :dump_default_options - end - self.dump_default_options = { - :max_nesting => false, - :allow_nan => true, - } - # :call-seq: - # JSON.dump(obj, io = nil, limit = nil) + # JSON.dump(obj, io = nil, _deprecated_limit = nil, options = nil) # # Dumps +obj+ as a \JSON string, i.e. calls generate on the object and returns the result. # - # The default options can be changed via method JSON.dump_default_options. - # # - Argument +io+, if given, should respond to method +write+; # the \JSON \String is written to +io+, and +io+ is returned. # If +io+ is not given, the \JSON \String is returned. - # - Argument +limit+, if given, is passed to JSON.generate as option +max_nesting+. - # + # - Argument +_deprecated_limit+ is deprecated, pass the +:max_nesting+ option instead. # --- # # When argument +io+ is not given, returns the \JSON \String generated from +obj+: @@ -817,41 +745,34 @@ class << self # puts File.read(path) # Output: # {"foo":[0,1],"bar":{"baz":2,"bat":3},"bam":"bad"} - def dump(obj, anIO = nil, limit = nil, kwargs = nil) + def dump(obj, anIO = nil, _deprecated_limit = nil, kwargs = nil) if kwargs.nil? - if limit.nil? + if _deprecated_limit.nil? if anIO.is_a?(Hash) kwargs = anIO anIO = nil end - elsif limit.is_a?(Hash) - kwargs = limit - limit = nil + elsif _deprecated_limit.is_a?(Hash) + kwargs = _deprecated_limit + _deprecated_limit = nil end end unless anIO.nil? if anIO.respond_to?(:to_io) anIO = anIO.to_io - elsif limit.nil? && !anIO.respond_to?(:write) - anIO, limit = nil, anIO + elsif _deprecated_limit.nil? && !anIO.respond_to?(:write) + anIO, _deprecated_limit = nil, anIO end end - opts = JSON.dump_default_options - opts = opts.merge(:max_nesting => limit) if limit - opts = opts.merge(kwargs) if kwargs - - begin - State.generate(obj, opts, anIO) - rescue JSON::NestingError - raise ArgumentError, "exceed depth limit" - end - end + opts = { + allow_nan: !(kwargs && kwargs[:rfc8785]), + } + opts[:max_nesting] = _deprecated_limit if _deprecated_limit + opts.merge!(kwargs) if kwargs - # Encodes string using String.encode. - def self.iconv(to, from, string) - string.encode(to, from) + State.generate(obj, opts, anIO) end # JSON::Coder holds a parser and generator configuration. @@ -865,14 +786,36 @@ def self.iconv(to, from, string) # MyApp::JSONC_CODER.load(document) # class Coder + PARSER_OPTIONS = %i( + max_nesting + allow_nan + allow_trailing_comma + allow_comments + allow_control_characters + allow_invalid_escape + symbolize_names + freeze + allow_duplicate_key + decimal_class + ).freeze + private_constant :PARSER_OPTIONS + + EXCLUDED_GENERATOR_OPTIONS = (PARSER_OPTIONS - %i( + max_nesting + allow_nan + allow_duplicate_key + )).freeze + private_constant :EXCLUDED_GENERATOR_OPTIONS + # :call-seq: - # JSON.new(options = nil, &block) + # JSON::Coder.new(**options, &block) # - # Argument +options+, if given, contains a \Hash of options for both parsing and generating. - # See {Parsing Options}[#module-JSON-label-Parsing+Options], and {Generating Options}[#module-JSON-label-Generating+Options]. + # Keyword arguments +options+, if given, are options for both parsing and generating. + # See {Parsing Options}[rdoc-ref:JSON@Parsing+Options], + # and {Generating Options}[rdoc-ref:JSON@Generating+Options]. # # For generation, the strict: true option is always set. When a Ruby object with no native \JSON counterpart is - # encoutered, the block provided to the initialize method is invoked, and must return a Ruby object that has a native + # encountered, the block provided to the initialize method is invoked, and must return a Ruby object that has a native # \JSON counterpart: # # module MyApp @@ -888,18 +831,25 @@ class Coder # # puts MyApp::API_JSON_CODER.dump(Time.now.utc) # => "2025-01-21T08:41:44.286Z" # - def initialize(options = nil, &as_json) - if options.nil? - options = { strict: true } - else - options = options.dup - options[:strict] = true + # The conversion callback can also be passed as +as_json:+. If both are given, the block takes precedence. + # Passing +false+ or +nil+ for +strict:+ raises ArgumentError. + # + def initialize(object_class: nil, array_class: nil, on_load: nil, strict: true, **options, &as_json) + raise ArgumentError, "JSON::Coder requires strict mode" unless strict + + if object_class || array_class + on_load = ParserOptions.on_load(on_load, object_class, array_class) end - options[:as_json] = as_json if as_json - options[:create_additions] = false unless options.key?(:create_additions) + parser_options = options.slice(*PARSER_OPTIONS) + parser_options[:on_load] = on_load if on_load + @parser_config = Ext::Parser::Config.new(parser_options).freeze - @state = State.new(options).freeze - @parser_config = Ext::Parser::Config.new(options) + generator_options = options.reject { |k, _| EXCLUDED_GENERATOR_OPTIONS.include?(k) } + @state = State.new( + **generator_options, + strict: true, + as_json: as_json || generator_options[:as_json], + ).freeze end # call-seq: @@ -908,7 +858,7 @@ def initialize(options = nil, &as_json) # # Serialize the given object into a \JSON document. def dump(object, io = nil) - @state.generate_new(object, io) + @state.generate(object, io) end alias_method :generate, :dump @@ -929,28 +879,34 @@ def load_file(path) load(File.read(path, encoding: Encoding::UTF_8)) end end -end -module ::Kernel - private + module GeneratorMethods + # call-seq: to_json(*) + # + # Converts this object into a JSON string. + # If this object doesn't directly maps to a JSON native type, + # first convert it to a string (calling #to_s), then converts + # it to a JSON string, and returns the result. + # This is a fallback, if no special method #to_json was defined for some object. + def to_json(state = nil, *) + obj = case self + when nil, false, true, Integer, Float, Array, Hash + self + else + "#{self}" + end - # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in - # one line. - def j(*objs) - objs.each do |obj| - puts JSON::generate(obj, :allow_nan => true, :max_nesting => false) + if state.nil? + JSON::State._generate_no_fallback(obj, nil, nil) + else + JSON::State.from_state(state)._generate_no_fallback(obj) + end end - nil end +end - # Outputs _objs_ to STDOUT as JSON strings in a pretty format, with - # indentation and over many lines. - def jj(*objs) - objs.each do |obj| - puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false) - end - nil - end +module ::Kernel + private # If _object_ is string-like, parse the string and return the parsed result as # a Ruby data structure. Otherwise, generate a JSON text from the Ruby data @@ -958,27 +914,11 @@ def jj(*objs) # # The _opts_ argument is passed through to generate/parse respectively. See # generate and parse for their documentation. - def JSON(object, *args) - if object.is_a?(String) - return JSON.parse(object, args.first) - elsif object.respond_to?(:to_str) - str = object.to_str - if str.is_a?(String) - return JSON.parse(object.to_str, args.first) - end - end - - JSON.generate(object, args.first) + def JSON(object, opts = nil) + JSON[object, opts] end end -# Extends any Class to include _json_creatable?_ method. -class ::Class - # Returns true if this class can be used to create an instance - # from a serialised JSON string. The class has to implement a class - # method _json_create_ that expects a hash as first parameter. The hash - # should include the required data. - def json_creatable? - respond_to?(:json_create) - end +class Object + include JSON::GeneratorMethods end diff --git a/lib/json/ext.rb b/lib/json/ext.rb index 1db5ea122..75d0e4e3d 100644 --- a/lib/json/ext.rb +++ b/lib/json/ext.rb @@ -28,18 +28,168 @@ def parse end end + class << self + if defined?(::Ractor) && Ractor.respond_to?(:shareable_lambda) + def shareable_lambda(block) # :nodoc: + Ractor.shareable_lambda(&block) + end + else + def shareable_lambda(block) # :nodoc: + block + end + end + + def valid_encoding?(str) # :nodoc: + str.ascii_only? || (str.encoding == Encoding::UTF_8 && str.valid_encoding?) + end + + def coerce_key(key, as_json) # :nodoc: + as_json_called = false + + unless String === key || Symbol === key + key = as_json.call(key, true) + as_json_called = true + end + + if Symbol === key + key = key.to_s + end + + unless String === key + raise GeneratorError.new("#{key.class} not allowed as object key in JSON", key) + end + + unless valid_encoding?(key) + if as_json_called + raise GeneratorError.new("source sequence is illegal/malformed utf-8", key) + end + + key = as_json.call(key, true) + unless String === key + raise GeneratorError.new("#{key.class} not allowed as object key in JSON", key) + end + + unless Ext.valid_encoding?(key) + raise GeneratorError.new("source sequence is illegal/malformed utf-8", key) + end + end + + key + end + + def on_duplicate_key(hash, as_json) + set = {} + hash.each_key do |key| + key = coerce_key(key, as_json) + if set[key] + raise GeneratorError.new("detected duplicate key #{key.inspect}", key) + else + set[key] = true + end + end + end + end + require 'json/ext/parser' Ext::Parser::Config = Ext::ParserConfig JSON.parser = Ext::Parser - if RUBY_ENGINE == 'truffleruby' + generator = if RUBY_ENGINE == 'truffleruby' require 'json/truffle_ruby/generator' - JSON.generator = ::JSON::TruffleRuby::Generator + JSON::TruffleRuby::Generator else require 'json/ext/generator' - JSON.generator = Generator + Generator end + + # The default proc used when the +sort_keys+ generation option is +true+. + # It returns a new hash with the entries sorted by their keys. + generator::State.default_sort_keys_proc = shareable_lambda(->(hash) { + hash.sort.to_h + }) + + # Directly lifted from Gregg Kellogg's json-canonicalization + generator::State.rfc8785_number_formatter_proc = shareable_lambda(->(num) { + if Integer === num + if num > Float::MAX || num < -Float::MAX + raise GeneratorError.new("Integer out of range for RFC 8785", num) + end + num = num.to_f + end + if num.zero? + "0" + else + if num < 0 + num, sign = -num, '-' + end + native_rep = num.to_s + decimal, exponential = native_rep.split('e') + exp_val = exponential.to_i + exponential = exp_val > 0 ? ('+' + exp_val.to_s) : exp_val.to_s + + integral, fractional = decimal.split('.') + fractional = fractional.sub(/0+$/, '') # Remove trailing zeros + + if exp_val > 0 && exp_val < 21 + while exp_val > 0 + integral += fractional.to_s[0] || '0' + fractional = fractional.to_s[1..-1] + exp_val -= 1 + end + exponential = nil + elsif exp_val == 0 + exponential = nil + elsif exp_val < 0 && exp_val > -7 + # Small numbers are shown as 0.etc with e-6 as lower limit + fractional, integral, exponential = integral + fractional.to_s, '0', nil + fractional = ("0" * (-exp_val - 1)) + fractional + end + + fractional = nil if fractional.to_s.empty? + sign.to_s + integral + (fractional ? ".#{fractional}" : '') + (exponential ? "e#{exponential}" : '') + end + }) + + generator::State.rfc8785_sort_keys_proc = shareable_lambda(->(hash, as_json) { + converted = if as_json + hash.transform_keys { |k| Ext.coerce_key(k, as_json) } + else + hash.transform_keys(&:to_s) + end + + if converted.size < hash.size + Ext.on_duplicate_key(hash, as_json) + end + + converted.sort_by { |k,| k.encode(Encoding::UTF_16) }.to_h + }) + + JSON.generator = generator end - JSON_LOADED = true unless defined?(::JSON::JSON_LOADED) + if defined?(ResumableParser) # Not yet available on JRuby + class ResumableParser + # Returns whether the parser is entirely done: no unconsumed bytes in + # the buffer, no document under construction and no parsed value + # awaiting retrieval. + # + # The main use case is detecting a truncated stream once the input is + # exhausted: + # + # loop do + # begin + # parser << socket.readpartial(4096) + # rescue EOFError + # break + # end + # while parser.parse + # process(parser.value) + # end + # end + # warn "stream was truncated" unless parser.empty? + def empty? + eos? && !partial_value? && !value? + end + end + end end diff --git a/lib/json/ext/generator/state.rb b/lib/json/ext/generator/state.rb index d40c3b5ec..92c8fc480 100644 --- a/lib/json/ext/generator/state.rb +++ b/lib/json/ext/generator/state.rb @@ -8,20 +8,8 @@ class State # # Instantiates a new State object, configured by _opts_. # - # _opts_ can have the following keys: - # - # * *indent*: a string used to indent levels (default: ''), - # * *space*: a string that is put after, a : or , delimiter (default: ''), - # * *space_before*: a string that is put before a : pair delimiter (default: ''), - # * *object_nl*: a string that is put at the end of a JSON object (default: ''), - # * *array_nl*: a string that is put at the end of a JSON array (default: ''), - # * *allow_nan*: true if NaN, Infinity, and -Infinity should be - # generated, otherwise an exception is thrown, if these values are - # encountered. This options defaults to false. - # * *ascii_only*: true if only ASCII characters should be generated. This - # option defaults to false. - # * *buffer_initial_length*: sets the initial length of the generator's - # internal buffer. + # Argument +opts+, if given, contains a \Hash of options for the generation. + # See {Generating Options}[rdoc-ref:JSON@Generating+Options]. def initialize(opts = nil) if opts && !opts.empty? configure(opts) @@ -66,40 +54,19 @@ def to_h strict: strict?, depth: depth, buffer_initial_length: buffer_initial_length, + sort_keys: sort_keys, + rfc8785: rfc8785?, } - instance_variables.each do |iv| - iv = iv.to_s[1..-1] - result[iv.to_sym] = self[iv] + allow_duplicate_key = allow_duplicate_key? + unless allow_duplicate_key.nil? + result[:allow_duplicate_key] = allow_duplicate_key end result end alias_method :to_hash, :to_h - - # call-seq: [](name) - # - # Returns the value returned by method +name+. - def [](name) - if respond_to?(name) - __send__(name) - else - instance_variable_get("@#{name}") if - instance_variables.include?("@#{name}".to_sym) # avoid warning - end - end - - # call-seq: []=(name, value) - # - # Sets the attribute name to value. - def []=(name, value) - if respond_to?(name_writer = "#{name}=") - __send__ name_writer, value - else - instance_variable_set "@#{name}", value - end - end end end end diff --git a/lib/json/generic_object.rb b/lib/json/generic_object.rb deleted file mode 100644 index ec5aa9dcb..000000000 --- a/lib/json/generic_object.rb +++ /dev/null @@ -1,75 +0,0 @@ -# frozen_string_literal: true -begin - require 'ostruct' -rescue LoadError - warn "JSON::GenericObject requires 'ostruct'. Please install it with `gem install ostruct`." -end - -module JSON - class GenericObject < OpenStruct - class << self - alias [] new - - def json_creatable? - @json_creatable - end - - attr_writer :json_creatable - - def json_create(data) - data = data.dup - data.delete JSON.create_id - self[data] - end - - def from_hash(object) - case - when object.respond_to?(:to_hash) - result = new - object.to_hash.each do |key, value| - result[key] = from_hash(value) - end - result - when object.respond_to?(:to_ary) - object.to_ary.map { |a| from_hash(a) } - else - object - end - end - - def load(source, proc = nil, opts = {}) - result = ::JSON.load(source, proc, opts.merge(:object_class => self)) - result.nil? ? new : result - end - - def dump(obj, *args) - ::JSON.dump(obj, *args) - end - end - self.json_creatable = false - - def to_hash - table - end - - def [](name) - __send__(name) - end unless method_defined?(:[]) - - def []=(name, value) - __send__("#{name}=", value) - end unless method_defined?(:[]=) - - def |(other) - self.class[other.to_hash.merge(to_hash)] - end - - def as_json(*) - { JSON.create_id => self.class.name }.merge to_hash - end - - def to_json(*a) - as_json.to_json(*a) - end - end if defined?(::OpenStruct) -end diff --git a/lib/json/truffle_ruby/generator.rb b/lib/json/truffle_ruby/generator.rb index bf6289f1f..f73708f2d 100644 --- a/lib/json/truffle_ruby/generator.rb +++ b/lib/json/truffle_ruby/generator.rb @@ -47,6 +47,19 @@ module Generator SCRIPT_SAFE_ESCAPE_PATTERN = /[\/"\\\x0-\x1f\u2028-\u2029]/ + def self.native_type?(value) # :nodoc: + (false == value || true == value || nil == value || String === value || Symbol === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value) + end + + def self.native_key?(key) # :nodoc: + (Symbol === key || String === key) + end + + def self.valid_encoding?(string) # :nodoc: + return false unless string.encoding == ::Encoding::UTF_8 || string.encoding == ::Encoding::US_ASCII + string.is_a?(Symbol) || string.valid_encoding? + end + # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with # UTF16 big endian characters as \u????, and return it. def self.utf8_to_json(string, script_safe = false) # :nodoc: @@ -98,6 +111,10 @@ def self.valid_utf8?(string) # This class is used to create State instances, that are use to hold data # while generating a JSON text from a Ruby data structure. class State + singleton_class.attr_accessor :default_sort_keys_proc # :nodoc: + singleton_class.attr_accessor :rfc8785_number_formatter_proc # :nodoc: + singleton_class.attr_accessor :rfc8785_sort_keys_proc # :nodoc: + def self.generate(obj, opts = nil, io = nil) new(opts).generate(obj, io) end @@ -110,14 +127,14 @@ def self.from_state(opts) if opts case when self === opts - return opts + return opts.validate_rfc8785 when opts.respond_to?(:to_hash) return new(opts.to_hash) when opts.respond_to?(:to_h) return new(opts.to_h) end end - SAFE_STATE_PROTOTYPE.dup + new end # Instantiates a new State object, configured by _opts_. @@ -151,7 +168,10 @@ def initialize(opts = nil) @script_safe = false @strict = false @max_nesting = 100 - configure(opts) if opts + @sort_keys = false + @allow_duplicate_key = false + @rfc8785 = false + _configure(**opts) if opts end # This string is used to indent levels in the JSON text. @@ -172,7 +192,16 @@ def initialize(opts = nil) attr_accessor :array_nl # This proc converts unsupported types into native JSON types. - attr_accessor :as_json + attr_reader :as_json + + def as_json=(value) + raise FrozenError, "can't modify frozen #{self.class}: #{inspect}" if frozen? + value = value.to_proc if !(Proc === value) && value.respond_to?(:to_proc) + unless Proc === value + raise TypeError, "as_json must be a Proc" + end + @as_json = value + end # This integer returns the maximum level of data structure nesting in # the generated JSON, max_nesting = 0 if no maximum is checked. @@ -186,6 +215,36 @@ def initialize(opts = nil) # supported by the JSON spec will raise a JSON::GeneratorError attr_accessor :strict + # Controls key sorting in the generated JSON. If set to +true+, object + # keys are sorted by key lexicographically. If set to a Proc, it + # receives the entire Hash and must return a Hash with its pairs in the + # desired order. + attr_reader :sort_keys + + # Controls whether RFC8785 (canonicalization) mode is enabled. Otherwise returns false. + attr_writer :rfc8785 + + def sort_keys=(value) # :nodoc: + type_error = false + @sort_keys = case value + when Proc + value + when true + State.default_sort_keys_proc + when nil, false + false + else + type_error = true + false + end + + if type_error + raise TypeError, "The `sort_keys` argument must be a boolean or a Proc" + end + + @sort_keys + end + # :stopdoc: attr_reader :buffer_initial_length @@ -198,13 +257,20 @@ def buffer_initial_length=(length) # This integer returns the current depth data structure nesting in the # generated JSON. - attr_accessor :depth + attr_reader :depth + + def depth=(depth) + if depth.negative? + raise ArgumentError, "depth must be >= 0 (got #{depth})" + end + @depth = depth + end def check_max_nesting # :nodoc: return if @max_nesting.zero? current_nesting = depth + 1 current_nesting > @max_nesting and - raise NestingError, "nesting of #{current_nesting} is too deep" + raise NestingError, "nesting of #{current_nesting} is too deep. Did you try to serialize objects with circular references?" end # Returns true, if circular data structures are checked, @@ -237,62 +303,106 @@ def strict? @strict end + # Returns true, if RFC8785 (canonicalization) mode is enabled. Otherwise returns false. + def rfc8785? + @rfc8785 + end + + def validate_rfc8785 # :nodoc: + if @rfc8785 + option = if !@indent.empty? then :indent + elsif !@space.empty? then :space + elsif !@space_before.empty? then :space_before + elsif !@object_nl.empty? then :object_nl + elsif !@array_nl.empty? then :array_nl + elsif @ascii_only then :ascii_only + elsif @script_safe then :script_safe + elsif @allow_nan then :allow_nan + end + raise ArgumentError, "#{option} cannot be used with rfc8785" if option + end + self + end + # Configure this State instance with the Hash _opts_, and return # itself. - def configure(opts) - if opts.respond_to?(:to_hash) - opts = opts.to_hash - elsif opts.respond_to?(:to_h) - opts = opts.to_h - else - raise TypeError, "can't convert #{opts.class} into Hash" - end - opts.each do |key, value| - instance_variable_set "@#{key}", value + def configure(options) + unless Hash == options + if options.respond_to?(:to_hash) + options = options.to_hash + elsif options.respond_to?(:to_h) + options = options.to_h + end end + _configure(**options) + end + alias merge configure - # NOTE: If adding new instance variables here, check whether #generate should check them for #generate_json - @indent = opts[:indent] || '' if opts.key?(:indent) - @space = opts[:space] || '' if opts.key?(:space) - @space_before = opts[:space_before] || '' if opts.key?(:space_before) - @object_nl = opts[:object_nl] || '' if opts.key?(:object_nl) - @array_nl = opts[:array_nl] || '' if opts.key?(:array_nl) - @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan) - @as_json = opts[:as_json].to_proc if opts[:as_json] - @ascii_only = opts[:ascii_only] if opts.key?(:ascii_only) - @depth = opts[:depth] || 0 - @buffer_initial_length ||= opts[:buffer_initial_length] - - @script_safe = if opts.key?(:script_safe) - !!opts[:script_safe] - elsif opts.key?(:escape_slash) - !!opts[:escape_slash] - else - false + # Defaults are the current values so that #configure only writes what it was given. + private def _configure( + indent: @indent, space: @space, space_before: @space_before, object_nl: @object_nl, + array_nl: @array_nl, allow_nan: @allow_nan, as_json: @as_json, ascii_only: @ascii_only, + sort_keys: @sort_keys, depth: @depth, buffer_initial_length: @buffer_initial_length, + allow_duplicate_key: @allow_duplicate_key, script_safe: @script_safe, strict: @strict, + max_nesting: @max_nesting, rfc8785: @rfc8785 + ) + if depth.negative? + raise ArgumentError, "depth must be >= 0 (got #{depth})" end - @strict = !!opts[:strict] if opts.key?(:strict) + if max_nesting && !(Integer === max_nesting) + raise TypeError, ":max_nesting must be `false` or an Integer, got: #{max_nesting.class}" + end - if !opts.key?(:max_nesting) # defaults to 100 - @max_nesting = 100 - elsif opts[:max_nesting] - @max_nesting = opts[:max_nesting] + @indent = indent || '' + @space = space || '' + @space_before = space_before || '' + @object_nl = object_nl || '' + @array_nl = array_nl || '' + @allow_nan = allow_nan || false + if as_json + self.as_json = as_json else - @max_nesting = 0 + @as_json = false end - self + @ascii_only = ascii_only || false + self.sort_keys = sort_keys + @depth = depth + @buffer_initial_length = buffer_initial_length + @allow_duplicate_key = allow_duplicate_key + @script_safe = script_safe + @strict = strict + @max_nesting = max_nesting || 0 + self.rfc8785 = rfc8785 + + validate_rfc8785 + end + + def allow_duplicate_key? # :nodoc: + @allow_duplicate_key end - alias merge configure # Returns the configuration instance variables as a hash, that can be # passed to the configure method. def to_h - result = {} - instance_variables.each do |iv| - iv = iv.to_s[1..-1] - result[iv.to_sym] = self[iv] - end - result + { + indent: @indent, + space: @space, + space_before: @space_before, + object_nl: @object_nl, + array_nl: @array_nl, + as_json: @as_json, + allow_nan: @allow_nan, + ascii_only: @ascii_only, + max_nesting: @max_nesting, + script_safe: @script_safe, + strict: @strict, + depth: @depth, + buffer_initial_length: @buffer_initial_length, + sort_keys: @sort_keys, + allow_duplicate_key: @allow_duplicate_key, + rfc8785: @rfc8785, + } end alias to_hash to_h @@ -302,12 +412,17 @@ def to_h # created this method raises a # GeneratorError exception. def generate(obj, anIO = nil) + return dup.generate(obj, anIO) if frozen? + + depth = @depth + validate_rfc8785 if @indent.empty? and @space.empty? and @space_before.empty? and @object_nl.empty? and @array_nl.empty? and - !@ascii_only and !@script_safe and @max_nesting == 0 and (!@strict || Symbol === obj) + !@ascii_only and !@script_safe and @max_nesting == 0 and (!@strict || Symbol === obj) and !@sort_keys result = generate_json(obj, ''.dup) else result = obj.to_json(self) end + JSON::TruffleRuby::Generator.valid_utf8?(result) or raise GeneratorError.new( "source sequence #{result.inspect} is illegal/malformed utf-8", obj @@ -318,10 +433,8 @@ def generate(obj, anIO = nil) else result end - end - - def generate_new(obj, anIO = nil) # :nodoc: - dup.generate(obj, anIO) + ensure + @depth = depth unless frozen? end # Handles @allow_nan, @buffer_initial_length, other ivars must be the default value (see above) @@ -330,8 +443,26 @@ def generate_new(obj, anIO = nil) # :nodoc: when Hash buf << '{' first = true + key_type = nil + if @rfc8785 + obj = State.rfc8785_sort_keys_proc.call(obj, @strict && @as_json) + elsif @sort_keys + obj = @sort_keys.call(obj) + end obj.each_pair do |k,v| - buf << ',' unless first + if first + if ::String == k.class || ::Symbol == k.class + key_type = k.class + else + JSON.send(:on_mixed_keys_hash, obj) + end + else + if key_type && !@allow_duplicate_key && key_type != k.class + key_type = nil # stop checking + JSON.send(:on_mixed_keys_hash, obj) + end + buf << ',' + end key_str = k.to_s if key_str.class == String @@ -363,7 +494,7 @@ def generate_new(obj, anIO = nil) # :nodoc: buf << obj.to_json(self) end when Integer - buf << obj.to_s + buf << (@rfc8785 ? State.rfc8785_number_formatter_proc.call(obj) : obj.to_s) when Symbol if @strict fast_serialize_string(obj.name, buf) @@ -395,24 +526,6 @@ def generate_new(obj, anIO = nil) # :nodoc: end buf << '"' end - - # Return the value returned by method +name+. - def [](name) - if respond_to?(name) - __send__(name) - else - instance_variable_get("@#{name}") if - instance_variables.include?("@#{name}".to_sym) # avoid warning - end - end - - def []=(name, value) - if respond_to?(name_writer = "#{name}=") - __send__ name_writer, value - else - instance_variable_set "@#{name}", value - end - end end module GeneratorMethods @@ -424,10 +537,10 @@ def to_json(state = nil, *) state = State.from_state(state) if state if state&.strict? value = self - if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value) + if state.strict? && !Generator.native_type?(value) if state.as_json - value = state.as_json.call(value) - unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value + value = state.as_json.call(value, false) + unless Generator.native_type?(value) raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value) end value.to_json(state) @@ -449,33 +562,68 @@ module Hash # _depth_ is used to find out nesting depth, to indent accordingly. def to_json(state = nil, *) state = State.from_state(state) - state.check_max_nesting - json_transform(state) + depth = state.depth + begin + state.check_max_nesting + json_transform(state) + ensure + state.depth = depth + end end private - def json_shift(state) - state.object_nl.empty? or return '' - state.indent * state.depth - end - def json_transform(state) depth = state.depth += 1 if empty? state.depth -= 1 - return '{}' + return +'{}' end delim = ",#{state.object_nl}" - result = +"{#{state.object_nl}" + result = "{#{state.object_nl}" first = true + key_type = nil indent = !state.object_nl.empty? - each { |key, value| - result << delim unless first + hash = self + + if state.rfc8785? + hash = State.rfc8785_sort_keys_proc.call(hash, state.strict? && state.as_json) + elsif state.sort_keys + hash = state.sort_keys.call(hash) + end + + hash.each { |key, value| + if first + if ::String == key.class || ::Symbol == key.class + key_type = key.class + else + JSON.send(:on_mixed_keys_hash, hash) + end + else + if key_type && !state.allow_duplicate_key? && key_type != key.class + key_type = nil # stop checking + JSON.send(:on_mixed_keys_hash, hash) + end + result << delim + end result << state.indent * depth if indent + if state.strict? + if state.as_json && (!Generator.native_key?(key) || !Generator.valid_encoding?(key)) + key = state.as_json.call(key, true) + end + + unless Generator.native_key?(key) + raise GeneratorError.new("#{key.class} not allowed as object key in JSON", key) + end + + unless Generator.valid_encoding?(key) + raise GeneratorError.new("source sequence is illegal/malformed utf-8", key) + end + end + key_str = key.to_s if key_str.is_a?(String) key_json = key_str.to_json(state) @@ -483,25 +631,27 @@ def json_transform(state) raise TypeError, "#{key.class}#to_s returns an instance of #{key_str.class}, expected a String" end - result = +"#{result}#{key_json}#{state.space_before}:#{state.space}" - if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value) + result = "#{result}#{key_json}#{state.space_before}:#{state.space}" + if state.strict? && !Generator.native_type?(value) if state.as_json - value = state.as_json.call(value) - unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value + value = state.as_json.call(value, false) + unless Generator.native_type?(value) raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value) end result << value.to_json(state) + state.depth = depth else raise GeneratorError.new("#{value.class} not allowed in JSON", value) end elsif value.respond_to?(:to_json) result << value.to_json(state) + state.depth = depth else result << %{"#{String(value)}"} end first = false } - depth = state.depth -= 1 + depth -= 1 unless first result << state.object_nl result << state.indent * depth if indent @@ -518,8 +668,13 @@ module Array # produced JSON string output further. def to_json(state = nil, *) state = State.from_state(state) - state.check_max_nesting - json_transform(state) + depth = state.depth + begin + state.check_max_nesting + json_transform(state) + ensure + state.depth = depth + end end private @@ -529,7 +684,7 @@ def json_transform(state) if empty? state.depth -= 1 - return '[]' + return +'[]' end result = '['.dup @@ -545,10 +700,10 @@ def json_transform(state) each { |value| result << delim unless first result << state.indent * depth if indent - if state.strict? && !(false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value || Symbol == value) + if state.strict? && !Generator.native_type?(value) if state.as_json - value = state.as_json.call(value) - unless false == value || true == value || nil == value || String === value || Array === value || Hash === value || Integer === value || Float === value || Fragment === value || Symbol === value + value = state.as_json.call(value, false) + unless Generator.native_type?(value) raise GeneratorError.new("#{value.class} returned by #{state.as_json} not allowed in JSON", value) end result << value.to_json(state) @@ -557,12 +712,13 @@ def json_transform(state) end elsif value.respond_to?(:to_json) result << value.to_json(state) + state.depth = depth else result << %{"#{String(value)}"} end first = false } - depth = state.depth -= 1 + depth -= 1 result << state.array_nl result << state.indent * depth if indent result << ']' @@ -571,22 +727,32 @@ def json_transform(state) module Integer # Returns a JSON string representation for this Integer number. - def to_json(*) to_s end + def to_json(state = nil, *) + if state && State.from_state(state).rfc8785? + State.rfc8785_number_formatter_proc.call(self) + else + to_s + end + end end module Float # Returns a JSON string representation for this Float number. def to_json(state = nil, *args) state = State.from_state(state) + if infinite? || nan? if state.allow_nan? to_s elsif state.strict? && state.as_json - casted_value = state.as_json.call(self) + casted_value = state.as_json.call(self, false) if casted_value.equal?(self) raise GeneratorError.new("#{self} not allowed in JSON", self) end + unless Generator.native_type?(casted_value) + raise GeneratorError.new("#{casted_value.class} returned by #{state.as_json} not allowed in JSON", casted_value) + end state.check_max_nesting state.depth += 1 @@ -596,6 +762,8 @@ def to_json(state = nil, *args) else raise GeneratorError.new("#{self} not allowed in JSON", self) end + elsif state.rfc8785? + State.rfc8785_number_formatter_proc.call(self) else to_s end @@ -619,14 +787,25 @@ module String # \u????. def to_json(state = nil, *args) state = State.from_state(state) - if encoding == ::Encoding::UTF_8 - unless valid_encoding? + string = self + + if state.strict? && state.as_json + unless Generator.valid_encoding?(string) + string = state.as_json.call(string, false) + unless string.is_a?(::String) + return string.to_json(state, *args) + end + end + end + + if string.encoding == ::Encoding::UTF_8 + unless string.valid_encoding? raise GeneratorError.new("source sequence is illegal/malformed utf-8", self) end - string = self else - string = encode(::Encoding::UTF_8) + string = string.encode(::Encoding::UTF_8) end + if state.ascii_only? %("#{JSON::TruffleRuby::Generator.utf8_to_json_ascii(string, state.script_safe)}") else @@ -635,54 +814,21 @@ def to_json(state = nil, *args) rescue Encoding::UndefinedConversionError => error raise ::JSON::GeneratorError.new(error.message, self) end - - # Module that holds the extending methods if, the String module is - # included. - module Extend - # Raw Strings are JSON Objects (the raw bytes are stored in an - # array for the key "raw"). The Ruby String can be created by this - # module method. - def json_create(o) - o['raw'].pack('C*') - end - end - - # Extends _modul_ with the String::Extend module. - def self.included(modul) - modul.extend Extend - end - - # This method creates a raw object hash, that can be nested into - # other data structures and will be unparsed as a raw string. This - # method should be used, if you want to convert raw strings to JSON - # instead of UTF-8 strings, e. g. binary data. - def to_json_raw_object - { - JSON.create_id => self.class.name, - 'raw' => self.unpack('C*'), - } - end - - # This method creates a JSON text from the result of - # a call to to_json_raw_object of this String. - def to_json_raw(*args) - to_json_raw_object.to_json(*args) - end end module TrueClass # Returns a JSON string for true: 'true'. - def to_json(*) 'true' end + def to_json(*) +'true' end end module FalseClass # Returns a JSON string for false: 'false'. - def to_json(*) 'false' end + def to_json(*) +'false' end end module NilClass # Returns a JSON string for nil: 'null'. - def to_json(*) 'null' end + def to_json(*) +'null' end end end end diff --git a/lib/json/version.rb b/lib/json/version.rb index 65bcb976f..4d37baaa7 100644 --- a/lib/json/version.rb +++ b/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.10.1' + VERSION = '3.1.0' end diff --git a/test/json/fixtures/pass15.json b/test/json/fixtures/fail15.json similarity index 100% rename from test/json/fixtures/pass15.json rename to test/json/fixtures/fail15.json diff --git a/test/json/fixtures/pass16.json b/test/json/fixtures/fail16.json similarity index 100% rename from test/json/fixtures/pass16.json rename to test/json/fixtures/fail16.json diff --git a/test/json/fixtures/pass17.json b/test/json/fixtures/fail17.json similarity index 100% rename from test/json/fixtures/pass17.json rename to test/json/fixtures/fail17.json diff --git a/test/json/fixtures/pass26.json b/test/json/fixtures/fail26.json similarity index 100% rename from test/json/fixtures/pass26.json rename to test/json/fixtures/fail26.json diff --git a/test/json/fixtures/minefield/i_number_double_huge_neg_exp.json b/test/json/fixtures/minefield/i_number_double_huge_neg_exp.json new file mode 100644 index 000000000..ae4c7b71f --- /dev/null +++ b/test/json/fixtures/minefield/i_number_double_huge_neg_exp.json @@ -0,0 +1 @@ +[123.456e-789] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_number_huge_exp.json b/test/json/fixtures/minefield/i_number_huge_exp.json new file mode 100644 index 000000000..9b5efa236 --- /dev/null +++ b/test/json/fixtures/minefield/i_number_huge_exp.json @@ -0,0 +1 @@ +[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_number_neg_int_huge_exp.json b/test/json/fixtures/minefield/i_number_neg_int_huge_exp.json new file mode 100644 index 000000000..3abd58a5c --- /dev/null +++ b/test/json/fixtures/minefield/i_number_neg_int_huge_exp.json @@ -0,0 +1 @@ +[-1e+9999] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_number_pos_double_huge_exp.json b/test/json/fixtures/minefield/i_number_pos_double_huge_exp.json new file mode 100644 index 000000000..e10a7eb62 --- /dev/null +++ b/test/json/fixtures/minefield/i_number_pos_double_huge_exp.json @@ -0,0 +1 @@ +[1.5e+9999] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_number_real_neg_overflow.json b/test/json/fixtures/minefield/i_number_real_neg_overflow.json new file mode 100644 index 000000000..3d628a994 --- /dev/null +++ b/test/json/fixtures/minefield/i_number_real_neg_overflow.json @@ -0,0 +1 @@ +[-123123e100000] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_number_real_pos_overflow.json b/test/json/fixtures/minefield/i_number_real_pos_overflow.json new file mode 100644 index 000000000..54d7d3dcd --- /dev/null +++ b/test/json/fixtures/minefield/i_number_real_pos_overflow.json @@ -0,0 +1 @@ +[123123e100000] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_number_real_underflow.json b/test/json/fixtures/minefield/i_number_real_underflow.json new file mode 100644 index 000000000..c5236eb26 --- /dev/null +++ b/test/json/fixtures/minefield/i_number_real_underflow.json @@ -0,0 +1 @@ +[123e-10000000] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_number_too_big_neg_int.json b/test/json/fixtures/minefield/i_number_too_big_neg_int.json new file mode 100644 index 000000000..dfa384619 --- /dev/null +++ b/test/json/fixtures/minefield/i_number_too_big_neg_int.json @@ -0,0 +1 @@ +[-123123123123123123123123123123] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_number_too_big_pos_int.json b/test/json/fixtures/minefield/i_number_too_big_pos_int.json new file mode 100644 index 000000000..338a8c3c0 --- /dev/null +++ b/test/json/fixtures/minefield/i_number_too_big_pos_int.json @@ -0,0 +1 @@ +[100000000000000000000] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_number_very_big_negative_int.json b/test/json/fixtures/minefield/i_number_very_big_negative_int.json new file mode 100644 index 000000000..e2d9738c2 --- /dev/null +++ b/test/json/fixtures/minefield/i_number_very_big_negative_int.json @@ -0,0 +1 @@ +[-237462374673276894279832749832423479823246327846] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_object_key_lone_2nd_surrogate.json b/test/json/fixtures/minefield/i_object_key_lone_2nd_surrogate.json new file mode 100644 index 000000000..5be7ebaf9 --- /dev/null +++ b/test/json/fixtures/minefield/i_object_key_lone_2nd_surrogate.json @@ -0,0 +1 @@ +{"\uDFAA":0} \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_1st_surrogate_but_2nd_missing.json b/test/json/fixtures/minefield/i_string_1st_surrogate_but_2nd_missing.json new file mode 100644 index 000000000..3b9e37c67 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_1st_surrogate_but_2nd_missing.json @@ -0,0 +1 @@ +["\uDADA"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_1st_valid_surrogate_2nd_invalid.json b/test/json/fixtures/minefield/i_string_1st_valid_surrogate_2nd_invalid.json new file mode 100644 index 000000000..487592832 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_1st_valid_surrogate_2nd_invalid.json @@ -0,0 +1 @@ +["\uD888\u1234"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_UTF-16LE_with_BOM.json b/test/json/fixtures/minefield/i_string_UTF-16LE_with_BOM.json new file mode 100644 index 000000000..2a79c0629 Binary files /dev/null and b/test/json/fixtures/minefield/i_string_UTF-16LE_with_BOM.json differ diff --git a/test/json/fixtures/minefield/i_string_UTF-8_invalid_sequence.json b/test/json/fixtures/minefield/i_string_UTF-8_invalid_sequence.json new file mode 100644 index 000000000..e2a968a15 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_UTF-8_invalid_sequence.json @@ -0,0 +1 @@ +["日шú"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_UTF8_surrogate_U+D800.json b/test/json/fixtures/minefield/i_string_UTF8_surrogate_U+D800.json new file mode 100644 index 000000000..916bff920 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_UTF8_surrogate_U+D800.json @@ -0,0 +1 @@ +["í €"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_incomplete_surrogate_and_escape_valid.json b/test/json/fixtures/minefield/i_string_incomplete_surrogate_and_escape_valid.json new file mode 100644 index 000000000..3cb11d229 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_incomplete_surrogate_and_escape_valid.json @@ -0,0 +1 @@ +["\uD800\n"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_incomplete_surrogate_pair.json b/test/json/fixtures/minefield/i_string_incomplete_surrogate_pair.json new file mode 100644 index 000000000..38ec23bb0 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_incomplete_surrogate_pair.json @@ -0,0 +1 @@ +["\uDd1ea"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_incomplete_surrogates_escape_valid.json b/test/json/fixtures/minefield/i_string_incomplete_surrogates_escape_valid.json new file mode 100644 index 000000000..c9cd6f6c3 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_incomplete_surrogates_escape_valid.json @@ -0,0 +1 @@ +["\uD800\uD800\n"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_invalid_lonely_surrogate.json b/test/json/fixtures/minefield/i_string_invalid_lonely_surrogate.json new file mode 100644 index 000000000..3abbd8d8d --- /dev/null +++ b/test/json/fixtures/minefield/i_string_invalid_lonely_surrogate.json @@ -0,0 +1 @@ +["\ud800"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_invalid_surrogate.json b/test/json/fixtures/minefield/i_string_invalid_surrogate.json new file mode 100644 index 000000000..ffddc04f5 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_invalid_surrogate.json @@ -0,0 +1 @@ +["\ud800abc"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_invalid_utf-8.json b/test/json/fixtures/minefield/i_string_invalid_utf-8.json new file mode 100644 index 000000000..8e45a7eca --- /dev/null +++ b/test/json/fixtures/minefield/i_string_invalid_utf-8.json @@ -0,0 +1 @@ +["ÿ"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_inverted_surrogates_U+1D11E.json b/test/json/fixtures/minefield/i_string_inverted_surrogates_U+1D11E.json new file mode 100644 index 000000000..0d5456cc3 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_inverted_surrogates_U+1D11E.json @@ -0,0 +1 @@ +["\uDd1e\uD834"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_iso_latin_1.json b/test/json/fixtures/minefield/i_string_iso_latin_1.json new file mode 100644 index 000000000..9389c9823 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_iso_latin_1.json @@ -0,0 +1 @@ +["é"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_lone_second_surrogate.json b/test/json/fixtures/minefield/i_string_lone_second_surrogate.json new file mode 100644 index 000000000..1dbd397f3 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_lone_second_surrogate.json @@ -0,0 +1 @@ +["\uDFAA"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_lone_utf8_continuation_byte.json b/test/json/fixtures/minefield/i_string_lone_utf8_continuation_byte.json new file mode 100644 index 000000000..729337c0a --- /dev/null +++ b/test/json/fixtures/minefield/i_string_lone_utf8_continuation_byte.json @@ -0,0 +1 @@ +["�"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_not_in_unicode_range.json b/test/json/fixtures/minefield/i_string_not_in_unicode_range.json new file mode 100644 index 000000000..df90a2916 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_not_in_unicode_range.json @@ -0,0 +1 @@ +["ô¿¿¿"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_overlong_sequence_2_bytes.json b/test/json/fixtures/minefield/i_string_overlong_sequence_2_bytes.json new file mode 100644 index 000000000..c8cee5e0a --- /dev/null +++ b/test/json/fixtures/minefield/i_string_overlong_sequence_2_bytes.json @@ -0,0 +1 @@ +["À¯"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_overlong_sequence_6_bytes.json b/test/json/fixtures/minefield/i_string_overlong_sequence_6_bytes.json new file mode 100644 index 000000000..9a91da791 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_overlong_sequence_6_bytes.json @@ -0,0 +1 @@ +["üƒ¿¿¿¿"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_overlong_sequence_6_bytes_null.json b/test/json/fixtures/minefield/i_string_overlong_sequence_6_bytes_null.json new file mode 100644 index 000000000..d24fffdd9 --- /dev/null +++ b/test/json/fixtures/minefield/i_string_overlong_sequence_6_bytes_null.json @@ -0,0 +1 @@ +["ü€€€€€"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_truncated-utf-8.json b/test/json/fixtures/minefield/i_string_truncated-utf-8.json new file mode 100644 index 000000000..63c7777fb --- /dev/null +++ b/test/json/fixtures/minefield/i_string_truncated-utf-8.json @@ -0,0 +1 @@ +["àÿ"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_string_utf16BE_no_BOM.json b/test/json/fixtures/minefield/i_string_utf16BE_no_BOM.json new file mode 100644 index 000000000..57e5392ff Binary files /dev/null and b/test/json/fixtures/minefield/i_string_utf16BE_no_BOM.json differ diff --git a/test/json/fixtures/minefield/i_string_utf16LE_no_BOM.json b/test/json/fixtures/minefield/i_string_utf16LE_no_BOM.json new file mode 100644 index 000000000..c49c1b25d Binary files /dev/null and b/test/json/fixtures/minefield/i_string_utf16LE_no_BOM.json differ diff --git a/test/json/fixtures/minefield/i_structure_500_nested_arrays.json b/test/json/fixtures/minefield/i_structure_500_nested_arrays.json new file mode 100644 index 000000000..711840589 --- /dev/null +++ b/test/json/fixtures/minefield/i_structure_500_nested_arrays.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/test/json/fixtures/minefield/i_structure_UTF-8_BOM_empty_object.json b/test/json/fixtures/minefield/i_structure_UTF-8_BOM_empty_object.json new file mode 100644 index 000000000..22fdca1b2 --- /dev/null +++ b/test/json/fixtures/minefield/i_structure_UTF-8_BOM_empty_object.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_1_true_without_comma.json b/test/json/fixtures/minefield/n_array_1_true_without_comma.json new file mode 100644 index 000000000..c14e3f6b1 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_1_true_without_comma.json @@ -0,0 +1 @@ +[1 true] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_a_invalid_utf8.json b/test/json/fixtures/minefield/n_array_a_invalid_utf8.json new file mode 100644 index 000000000..38a86e2e6 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_a_invalid_utf8.json @@ -0,0 +1 @@ +[aå] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_colon_instead_of_comma.json b/test/json/fixtures/minefield/n_array_colon_instead_of_comma.json new file mode 100644 index 000000000..0d02ad448 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_colon_instead_of_comma.json @@ -0,0 +1 @@ +["": 1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_comma_after_close.json b/test/json/fixtures/minefield/n_array_comma_after_close.json new file mode 100644 index 000000000..2ccba8d95 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_comma_after_close.json @@ -0,0 +1 @@ +[""], \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_comma_and_number.json b/test/json/fixtures/minefield/n_array_comma_and_number.json new file mode 100644 index 000000000..d2c84e374 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_comma_and_number.json @@ -0,0 +1 @@ +[,1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_double_comma.json b/test/json/fixtures/minefield/n_array_double_comma.json new file mode 100644 index 000000000..0431712bc --- /dev/null +++ b/test/json/fixtures/minefield/n_array_double_comma.json @@ -0,0 +1 @@ +[1,,2] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_double_extra_comma.json b/test/json/fixtures/minefield/n_array_double_extra_comma.json new file mode 100644 index 000000000..3f01d3129 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_double_extra_comma.json @@ -0,0 +1 @@ +["x",,] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_extra_close.json b/test/json/fixtures/minefield/n_array_extra_close.json new file mode 100644 index 000000000..c12f9fae1 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_extra_close.json @@ -0,0 +1 @@ +["x"]] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_extra_comma.json b/test/json/fixtures/minefield/n_array_extra_comma.json new file mode 100644 index 000000000..5f8ce18e4 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_extra_comma.json @@ -0,0 +1 @@ +["",] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_incomplete.json b/test/json/fixtures/minefield/n_array_incomplete.json new file mode 100644 index 000000000..cc65b0b51 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_incomplete.json @@ -0,0 +1 @@ +["x" \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_incomplete_invalid_value.json b/test/json/fixtures/minefield/n_array_incomplete_invalid_value.json new file mode 100644 index 000000000..c21a8f6cf --- /dev/null +++ b/test/json/fixtures/minefield/n_array_incomplete_invalid_value.json @@ -0,0 +1 @@ +[x \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_inner_array_no_comma.json b/test/json/fixtures/minefield/n_array_inner_array_no_comma.json new file mode 100644 index 000000000..c70b71647 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_inner_array_no_comma.json @@ -0,0 +1 @@ +[3[4]] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_invalid_utf8.json b/test/json/fixtures/minefield/n_array_invalid_utf8.json new file mode 100644 index 000000000..6099d3441 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_invalid_utf8.json @@ -0,0 +1 @@ +[ÿ] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_items_separated_by_semicolon.json b/test/json/fixtures/minefield/n_array_items_separated_by_semicolon.json new file mode 100644 index 000000000..d4bd7314c --- /dev/null +++ b/test/json/fixtures/minefield/n_array_items_separated_by_semicolon.json @@ -0,0 +1 @@ +[1:2] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_just_comma.json b/test/json/fixtures/minefield/n_array_just_comma.json new file mode 100644 index 000000000..9d7077c68 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_just_comma.json @@ -0,0 +1 @@ +[,] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_just_minus.json b/test/json/fixtures/minefield/n_array_just_minus.json new file mode 100644 index 000000000..29501c6ca --- /dev/null +++ b/test/json/fixtures/minefield/n_array_just_minus.json @@ -0,0 +1 @@ +[-] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_missing_value.json b/test/json/fixtures/minefield/n_array_missing_value.json new file mode 100644 index 000000000..3a6ba86f3 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_missing_value.json @@ -0,0 +1 @@ +[ , ""] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_newlines_unclosed.json b/test/json/fixtures/minefield/n_array_newlines_unclosed.json new file mode 100644 index 000000000..646680065 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_newlines_unclosed.json @@ -0,0 +1,3 @@ +["a", +4 +,1, \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_number_and_comma.json b/test/json/fixtures/minefield/n_array_number_and_comma.json new file mode 100644 index 000000000..13f6f1d18 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_number_and_comma.json @@ -0,0 +1 @@ +[1,] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_number_and_several_commas.json b/test/json/fixtures/minefield/n_array_number_and_several_commas.json new file mode 100644 index 000000000..0ac408cb8 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_number_and_several_commas.json @@ -0,0 +1 @@ +[1,,] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_spaces_vertical_tab_formfeed.json b/test/json/fixtures/minefield/n_array_spaces_vertical_tab_formfeed.json new file mode 100644 index 000000000..6cd7cf585 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_spaces_vertical_tab_formfeed.json @@ -0,0 +1 @@ +[" a"\f] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_star_inside.json b/test/json/fixtures/minefield/n_array_star_inside.json new file mode 100644 index 000000000..5a5194647 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_star_inside.json @@ -0,0 +1 @@ +[*] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_unclosed.json b/test/json/fixtures/minefield/n_array_unclosed.json new file mode 100644 index 000000000..060733059 --- /dev/null +++ b/test/json/fixtures/minefield/n_array_unclosed.json @@ -0,0 +1 @@ +["" \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_unclosed_trailing_comma.json b/test/json/fixtures/minefield/n_array_unclosed_trailing_comma.json new file mode 100644 index 000000000..6604698ff --- /dev/null +++ b/test/json/fixtures/minefield/n_array_unclosed_trailing_comma.json @@ -0,0 +1 @@ +[1, \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_unclosed_with_new_lines.json b/test/json/fixtures/minefield/n_array_unclosed_with_new_lines.json new file mode 100644 index 000000000..4f61de3fb --- /dev/null +++ b/test/json/fixtures/minefield/n_array_unclosed_with_new_lines.json @@ -0,0 +1,3 @@ +[1, +1 +,1 \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_array_unclosed_with_object_inside.json b/test/json/fixtures/minefield/n_array_unclosed_with_object_inside.json new file mode 100644 index 000000000..043a87e2d --- /dev/null +++ b/test/json/fixtures/minefield/n_array_unclosed_with_object_inside.json @@ -0,0 +1 @@ +[{} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_incomplete_false.json b/test/json/fixtures/minefield/n_incomplete_false.json new file mode 100644 index 000000000..eb18c6a14 --- /dev/null +++ b/test/json/fixtures/minefield/n_incomplete_false.json @@ -0,0 +1 @@ +[fals] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_incomplete_null.json b/test/json/fixtures/minefield/n_incomplete_null.json new file mode 100644 index 000000000..c18ef5385 --- /dev/null +++ b/test/json/fixtures/minefield/n_incomplete_null.json @@ -0,0 +1 @@ +[nul] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_incomplete_true.json b/test/json/fixtures/minefield/n_incomplete_true.json new file mode 100644 index 000000000..f451ac6d2 --- /dev/null +++ b/test/json/fixtures/minefield/n_incomplete_true.json @@ -0,0 +1 @@ +[tru] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_multidigit_number_then_00.json b/test/json/fixtures/minefield/n_multidigit_number_then_00.json new file mode 100644 index 000000000..c22507b86 Binary files /dev/null and b/test/json/fixtures/minefield/n_multidigit_number_then_00.json differ diff --git a/test/json/fixtures/minefield/n_number_++.json b/test/json/fixtures/minefield/n_number_++.json new file mode 100644 index 000000000..bdb62aaf4 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_++.json @@ -0,0 +1 @@ +[++1234] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_+1.json b/test/json/fixtures/minefield/n_number_+1.json new file mode 100644 index 000000000..3cbe58c92 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_+1.json @@ -0,0 +1 @@ +[+1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_+Inf.json b/test/json/fixtures/minefield/n_number_+Inf.json new file mode 100644 index 000000000..871ae14d5 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_+Inf.json @@ -0,0 +1 @@ +[+Inf] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_-01.json b/test/json/fixtures/minefield/n_number_-01.json new file mode 100644 index 000000000..0df32bac8 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_-01.json @@ -0,0 +1 @@ +[-01] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_-1.0..json b/test/json/fixtures/minefield/n_number_-1.0..json new file mode 100644 index 000000000..7cf55a85a --- /dev/null +++ b/test/json/fixtures/minefield/n_number_-1.0..json @@ -0,0 +1 @@ +[-1.0.] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_-2..json b/test/json/fixtures/minefield/n_number_-2..json new file mode 100644 index 000000000..9be84365d --- /dev/null +++ b/test/json/fixtures/minefield/n_number_-2..json @@ -0,0 +1 @@ +[-2.] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_-NaN.json b/test/json/fixtures/minefield/n_number_-NaN.json new file mode 100644 index 000000000..f61615d40 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_-NaN.json @@ -0,0 +1 @@ +[-NaN] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_.-1.json b/test/json/fixtures/minefield/n_number_.-1.json new file mode 100644 index 000000000..1c9f2dd1b --- /dev/null +++ b/test/json/fixtures/minefield/n_number_.-1.json @@ -0,0 +1 @@ +[.-1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_.2e-3.json b/test/json/fixtures/minefield/n_number_.2e-3.json new file mode 100644 index 000000000..c6c976f25 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_.2e-3.json @@ -0,0 +1 @@ +[.2e-3] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_0.1.2.json b/test/json/fixtures/minefield/n_number_0.1.2.json new file mode 100644 index 000000000..c83a25621 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_0.1.2.json @@ -0,0 +1 @@ +[0.1.2] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_0.3e+.json b/test/json/fixtures/minefield/n_number_0.3e+.json new file mode 100644 index 000000000..a55a1bfef --- /dev/null +++ b/test/json/fixtures/minefield/n_number_0.3e+.json @@ -0,0 +1 @@ +[0.3e+] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_0.3e.json b/test/json/fixtures/minefield/n_number_0.3e.json new file mode 100644 index 000000000..3dd5df4b3 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_0.3e.json @@ -0,0 +1 @@ +[0.3e] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_0.e1.json b/test/json/fixtures/minefield/n_number_0.e1.json new file mode 100644 index 000000000..c92c71ccb --- /dev/null +++ b/test/json/fixtures/minefield/n_number_0.e1.json @@ -0,0 +1 @@ +[0.e1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_0_capital_E+.json b/test/json/fixtures/minefield/n_number_0_capital_E+.json new file mode 100644 index 000000000..3ba2c7d6d --- /dev/null +++ b/test/json/fixtures/minefield/n_number_0_capital_E+.json @@ -0,0 +1 @@ +[0E+] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_0_capital_E.json b/test/json/fixtures/minefield/n_number_0_capital_E.json new file mode 100644 index 000000000..5301840d1 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_0_capital_E.json @@ -0,0 +1 @@ +[0E] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_0e+.json b/test/json/fixtures/minefield/n_number_0e+.json new file mode 100644 index 000000000..8ab0bc4b8 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_0e+.json @@ -0,0 +1 @@ +[0e+] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_0e.json b/test/json/fixtures/minefield/n_number_0e.json new file mode 100644 index 000000000..47ec421bb --- /dev/null +++ b/test/json/fixtures/minefield/n_number_0e.json @@ -0,0 +1 @@ +[0e] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_1.0e+.json b/test/json/fixtures/minefield/n_number_1.0e+.json new file mode 100644 index 000000000..cd84b9f69 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_1.0e+.json @@ -0,0 +1 @@ +[1.0e+] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_1.0e-.json b/test/json/fixtures/minefield/n_number_1.0e-.json new file mode 100644 index 000000000..4eb7afa0f --- /dev/null +++ b/test/json/fixtures/minefield/n_number_1.0e-.json @@ -0,0 +1 @@ +[1.0e-] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_1.0e.json b/test/json/fixtures/minefield/n_number_1.0e.json new file mode 100644 index 000000000..21753f4c7 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_1.0e.json @@ -0,0 +1 @@ +[1.0e] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_1_000.json b/test/json/fixtures/minefield/n_number_1_000.json new file mode 100644 index 000000000..7b18b66b3 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_1_000.json @@ -0,0 +1 @@ +[1 000.0] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_1eE2.json b/test/json/fixtures/minefield/n_number_1eE2.json new file mode 100644 index 000000000..4318a341d --- /dev/null +++ b/test/json/fixtures/minefield/n_number_1eE2.json @@ -0,0 +1 @@ +[1eE2] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_2.e+3.json b/test/json/fixtures/minefield/n_number_2.e+3.json new file mode 100644 index 000000000..4442f394d --- /dev/null +++ b/test/json/fixtures/minefield/n_number_2.e+3.json @@ -0,0 +1 @@ +[2.e+3] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_2.e-3.json b/test/json/fixtures/minefield/n_number_2.e-3.json new file mode 100644 index 000000000..a65060edf --- /dev/null +++ b/test/json/fixtures/minefield/n_number_2.e-3.json @@ -0,0 +1 @@ +[2.e-3] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_2.e3.json b/test/json/fixtures/minefield/n_number_2.e3.json new file mode 100644 index 000000000..66f7cf701 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_2.e3.json @@ -0,0 +1 @@ +[2.e3] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_9.e+.json b/test/json/fixtures/minefield/n_number_9.e+.json new file mode 100644 index 000000000..732a7b11c --- /dev/null +++ b/test/json/fixtures/minefield/n_number_9.e+.json @@ -0,0 +1 @@ +[9.e+] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_Inf.json b/test/json/fixtures/minefield/n_number_Inf.json new file mode 100644 index 000000000..c40c734c3 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_Inf.json @@ -0,0 +1 @@ +[Inf] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_NaN.json b/test/json/fixtures/minefield/n_number_NaN.json new file mode 100644 index 000000000..499231790 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_NaN.json @@ -0,0 +1 @@ +[NaN] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_U+FF11_fullwidth_digit_one.json b/test/json/fixtures/minefield/n_number_U+FF11_fullwidth_digit_one.json new file mode 100644 index 000000000..b14587e5e --- /dev/null +++ b/test/json/fixtures/minefield/n_number_U+FF11_fullwidth_digit_one.json @@ -0,0 +1 @@ +[1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_expression.json b/test/json/fixtures/minefield/n_number_expression.json new file mode 100644 index 000000000..76fdbc8a4 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_expression.json @@ -0,0 +1 @@ +[1+2] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_hex_1_digit.json b/test/json/fixtures/minefield/n_number_hex_1_digit.json new file mode 100644 index 000000000..3b214880c --- /dev/null +++ b/test/json/fixtures/minefield/n_number_hex_1_digit.json @@ -0,0 +1 @@ +[0x1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_hex_2_digits.json b/test/json/fixtures/minefield/n_number_hex_2_digits.json new file mode 100644 index 000000000..83e516ab0 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_hex_2_digits.json @@ -0,0 +1 @@ +[0x42] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_infinity.json b/test/json/fixtures/minefield/n_number_infinity.json new file mode 100644 index 000000000..8c2baf783 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_infinity.json @@ -0,0 +1 @@ +[Infinity] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_invalid+-.json b/test/json/fixtures/minefield/n_number_invalid+-.json new file mode 100644 index 000000000..1cce602b5 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_invalid+-.json @@ -0,0 +1 @@ +[0e+-1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_invalid-negative-real.json b/test/json/fixtures/minefield/n_number_invalid-negative-real.json new file mode 100644 index 000000000..5fc3c1efb --- /dev/null +++ b/test/json/fixtures/minefield/n_number_invalid-negative-real.json @@ -0,0 +1 @@ +[-123.123foo] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_invalid-utf-8-in-bigger-int.json b/test/json/fixtures/minefield/n_number_invalid-utf-8-in-bigger-int.json new file mode 100644 index 000000000..3b97e580e --- /dev/null +++ b/test/json/fixtures/minefield/n_number_invalid-utf-8-in-bigger-int.json @@ -0,0 +1 @@ +[123å] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_invalid-utf-8-in-exponent.json b/test/json/fixtures/minefield/n_number_invalid-utf-8-in-exponent.json new file mode 100644 index 000000000..ea35d723c --- /dev/null +++ b/test/json/fixtures/minefield/n_number_invalid-utf-8-in-exponent.json @@ -0,0 +1 @@ +[1e1å] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_invalid-utf-8-in-int.json b/test/json/fixtures/minefield/n_number_invalid-utf-8-in-int.json new file mode 100644 index 000000000..371226e4c --- /dev/null +++ b/test/json/fixtures/minefield/n_number_invalid-utf-8-in-int.json @@ -0,0 +1 @@ +[0å] diff --git a/test/json/fixtures/minefield/n_number_minus_infinity.json b/test/json/fixtures/minefield/n_number_minus_infinity.json new file mode 100644 index 000000000..cf4133d22 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_minus_infinity.json @@ -0,0 +1 @@ +[-Infinity] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_minus_sign_with_trailing_garbage.json b/test/json/fixtures/minefield/n_number_minus_sign_with_trailing_garbage.json new file mode 100644 index 000000000..a6d8e78e7 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_minus_sign_with_trailing_garbage.json @@ -0,0 +1 @@ +[-foo] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_minus_space_1.json b/test/json/fixtures/minefield/n_number_minus_space_1.json new file mode 100644 index 000000000..9a5ebedf6 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_minus_space_1.json @@ -0,0 +1 @@ +[- 1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_neg_int_starting_with_zero.json b/test/json/fixtures/minefield/n_number_neg_int_starting_with_zero.json new file mode 100644 index 000000000..67af0960a --- /dev/null +++ b/test/json/fixtures/minefield/n_number_neg_int_starting_with_zero.json @@ -0,0 +1 @@ +[-012] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_neg_real_without_int_part.json b/test/json/fixtures/minefield/n_number_neg_real_without_int_part.json new file mode 100644 index 000000000..1f2a43496 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_neg_real_without_int_part.json @@ -0,0 +1 @@ +[-.123] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_neg_with_garbage_at_end.json b/test/json/fixtures/minefield/n_number_neg_with_garbage_at_end.json new file mode 100644 index 000000000..2aa73119f --- /dev/null +++ b/test/json/fixtures/minefield/n_number_neg_with_garbage_at_end.json @@ -0,0 +1 @@ +[-1x] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_real_garbage_after_e.json b/test/json/fixtures/minefield/n_number_real_garbage_after_e.json new file mode 100644 index 000000000..9213dfca8 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_real_garbage_after_e.json @@ -0,0 +1 @@ +[1ea] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_real_with_invalid_utf8_after_e.json b/test/json/fixtures/minefield/n_number_real_with_invalid_utf8_after_e.json new file mode 100644 index 000000000..1e52ef964 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_real_with_invalid_utf8_after_e.json @@ -0,0 +1 @@ +[1eå] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_real_without_fractional_part.json b/test/json/fixtures/minefield/n_number_real_without_fractional_part.json new file mode 100644 index 000000000..1de287cf8 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_real_without_fractional_part.json @@ -0,0 +1 @@ +[1.] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_starting_with_dot.json b/test/json/fixtures/minefield/n_number_starting_with_dot.json new file mode 100644 index 000000000..f682dbdce --- /dev/null +++ b/test/json/fixtures/minefield/n_number_starting_with_dot.json @@ -0,0 +1 @@ +[.123] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_with_alpha.json b/test/json/fixtures/minefield/n_number_with_alpha.json new file mode 100644 index 000000000..1e42d8182 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_with_alpha.json @@ -0,0 +1 @@ +[1.2a-3] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_with_alpha_char.json b/test/json/fixtures/minefield/n_number_with_alpha_char.json new file mode 100644 index 000000000..b79daccb8 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_with_alpha_char.json @@ -0,0 +1 @@ +[1.8011670033376514H-308] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_number_with_leading_zero.json b/test/json/fixtures/minefield/n_number_with_leading_zero.json new file mode 100644 index 000000000..7106da1f3 --- /dev/null +++ b/test/json/fixtures/minefield/n_number_with_leading_zero.json @@ -0,0 +1 @@ +[012] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_bad_value.json b/test/json/fixtures/minefield/n_object_bad_value.json new file mode 100644 index 000000000..a03a8c03b --- /dev/null +++ b/test/json/fixtures/minefield/n_object_bad_value.json @@ -0,0 +1 @@ +["x", truth] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_bracket_key.json b/test/json/fixtures/minefield/n_object_bracket_key.json new file mode 100644 index 000000000..cc443b483 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_bracket_key.json @@ -0,0 +1 @@ +{[: "x"} diff --git a/test/json/fixtures/minefield/n_object_comma_instead_of_colon.json b/test/json/fixtures/minefield/n_object_comma_instead_of_colon.json new file mode 100644 index 000000000..8d5637708 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_comma_instead_of_colon.json @@ -0,0 +1 @@ +{"x", null} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_double_colon.json b/test/json/fixtures/minefield/n_object_double_colon.json new file mode 100644 index 000000000..80e8c7b89 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_double_colon.json @@ -0,0 +1 @@ +{"x"::"b"} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_emoji.json b/test/json/fixtures/minefield/n_object_emoji.json new file mode 100644 index 000000000..cb4078eaa --- /dev/null +++ b/test/json/fixtures/minefield/n_object_emoji.json @@ -0,0 +1 @@ +{🇨🇭} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_garbage_at_end.json b/test/json/fixtures/minefield/n_object_garbage_at_end.json new file mode 100644 index 000000000..80c42cbad --- /dev/null +++ b/test/json/fixtures/minefield/n_object_garbage_at_end.json @@ -0,0 +1 @@ +{"a":"a" 123} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_key_with_single_quotes.json b/test/json/fixtures/minefield/n_object_key_with_single_quotes.json new file mode 100644 index 000000000..77c327599 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_key_with_single_quotes.json @@ -0,0 +1 @@ +{key: 'value'} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_lone_continuation_byte_in_key_and_trailing_comma.json b/test/json/fixtures/minefield/n_object_lone_continuation_byte_in_key_and_trailing_comma.json new file mode 100644 index 000000000..aa2cb637c --- /dev/null +++ b/test/json/fixtures/minefield/n_object_lone_continuation_byte_in_key_and_trailing_comma.json @@ -0,0 +1 @@ +{"¹":"0",} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_missing_colon.json b/test/json/fixtures/minefield/n_object_missing_colon.json new file mode 100644 index 000000000..b98eff62d --- /dev/null +++ b/test/json/fixtures/minefield/n_object_missing_colon.json @@ -0,0 +1 @@ +{"a" b} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_missing_key.json b/test/json/fixtures/minefield/n_object_missing_key.json new file mode 100644 index 000000000..b4fb0f528 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_missing_key.json @@ -0,0 +1 @@ +{:"b"} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_missing_semicolon.json b/test/json/fixtures/minefield/n_object_missing_semicolon.json new file mode 100644 index 000000000..e3451384f --- /dev/null +++ b/test/json/fixtures/minefield/n_object_missing_semicolon.json @@ -0,0 +1 @@ +{"a" "b"} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_missing_value.json b/test/json/fixtures/minefield/n_object_missing_value.json new file mode 100644 index 000000000..3ef538a60 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_missing_value.json @@ -0,0 +1 @@ +{"a": \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_no-colon.json b/test/json/fixtures/minefield/n_object_no-colon.json new file mode 100644 index 000000000..f3797b357 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_no-colon.json @@ -0,0 +1 @@ +{"a" \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_non_string_key.json b/test/json/fixtures/minefield/n_object_non_string_key.json new file mode 100644 index 000000000..b9945b34b --- /dev/null +++ b/test/json/fixtures/minefield/n_object_non_string_key.json @@ -0,0 +1 @@ +{1:1} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_non_string_key_but_huge_number_instead.json b/test/json/fixtures/minefield/n_object_non_string_key_but_huge_number_instead.json new file mode 100644 index 000000000..b37fa86c0 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_non_string_key_but_huge_number_instead.json @@ -0,0 +1 @@ +{9999E9999:1} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_repeated_null_null.json b/test/json/fixtures/minefield/n_object_repeated_null_null.json new file mode 100644 index 000000000..f7d2959d0 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_repeated_null_null.json @@ -0,0 +1 @@ +{null:null,null:null} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_several_trailing_commas.json b/test/json/fixtures/minefield/n_object_several_trailing_commas.json new file mode 100644 index 000000000..3c9afe8dc --- /dev/null +++ b/test/json/fixtures/minefield/n_object_several_trailing_commas.json @@ -0,0 +1 @@ +{"id":0,,,,,} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_single_quote.json b/test/json/fixtures/minefield/n_object_single_quote.json new file mode 100644 index 000000000..e5cdf976a --- /dev/null +++ b/test/json/fixtures/minefield/n_object_single_quote.json @@ -0,0 +1 @@ +{'a':0} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_trailing_comma.json b/test/json/fixtures/minefield/n_object_trailing_comma.json new file mode 100644 index 000000000..a4b025094 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_trailing_comma.json @@ -0,0 +1 @@ +{"id":0,} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_trailing_comment.json b/test/json/fixtures/minefield/n_object_trailing_comment.json new file mode 100644 index 000000000..a372c6553 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_trailing_comment.json @@ -0,0 +1 @@ +{"a":"b"}/**/ \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_trailing_comment_open.json b/test/json/fixtures/minefield/n_object_trailing_comment_open.json new file mode 100644 index 000000000..d557f41ca --- /dev/null +++ b/test/json/fixtures/minefield/n_object_trailing_comment_open.json @@ -0,0 +1 @@ +{"a":"b"}/**// \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_trailing_comment_slash_open.json b/test/json/fixtures/minefield/n_object_trailing_comment_slash_open.json new file mode 100644 index 000000000..e335136c0 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_trailing_comment_slash_open.json @@ -0,0 +1 @@ +{"a":"b"}// \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_trailing_comment_slash_open_incomplete.json b/test/json/fixtures/minefield/n_object_trailing_comment_slash_open_incomplete.json new file mode 100644 index 000000000..d892e49f1 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_trailing_comment_slash_open_incomplete.json @@ -0,0 +1 @@ +{"a":"b"}/ \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_two_commas_in_a_row.json b/test/json/fixtures/minefield/n_object_two_commas_in_a_row.json new file mode 100644 index 000000000..7c639ae64 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_two_commas_in_a_row.json @@ -0,0 +1 @@ +{"a":"b",,"c":"d"} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_unquoted_key.json b/test/json/fixtures/minefield/n_object_unquoted_key.json new file mode 100644 index 000000000..8ba137293 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_unquoted_key.json @@ -0,0 +1 @@ +{a: "b"} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_unterminated-value.json b/test/json/fixtures/minefield/n_object_unterminated-value.json new file mode 100644 index 000000000..7fe699a6a --- /dev/null +++ b/test/json/fixtures/minefield/n_object_unterminated-value.json @@ -0,0 +1 @@ +{"a":"a \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_with_single_string.json b/test/json/fixtures/minefield/n_object_with_single_string.json new file mode 100644 index 000000000..d63f7fbb7 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_with_single_string.json @@ -0,0 +1 @@ +{ "foo" : "bar", "a" } \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_object_with_trailing_garbage.json b/test/json/fixtures/minefield/n_object_with_trailing_garbage.json new file mode 100644 index 000000000..787c8f0a8 --- /dev/null +++ b/test/json/fixtures/minefield/n_object_with_trailing_garbage.json @@ -0,0 +1 @@ +{"a":"b"}# \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_single_space.json b/test/json/fixtures/minefield/n_single_space.json new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/test/json/fixtures/minefield/n_single_space.json @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_1_surrogate_then_escape.json b/test/json/fixtures/minefield/n_string_1_surrogate_then_escape.json new file mode 100644 index 000000000..acec66d8f --- /dev/null +++ b/test/json/fixtures/minefield/n_string_1_surrogate_then_escape.json @@ -0,0 +1 @@ +["\uD800\"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_1_surrogate_then_escape_u.json b/test/json/fixtures/minefield/n_string_1_surrogate_then_escape_u.json new file mode 100644 index 000000000..e834b05e9 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_1_surrogate_then_escape_u.json @@ -0,0 +1 @@ +["\uD800\u"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_1_surrogate_then_escape_u1.json b/test/json/fixtures/minefield/n_string_1_surrogate_then_escape_u1.json new file mode 100644 index 000000000..a04cd3489 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_1_surrogate_then_escape_u1.json @@ -0,0 +1 @@ +["\uD800\u1"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_1_surrogate_then_escape_u1x.json b/test/json/fixtures/minefield/n_string_1_surrogate_then_escape_u1x.json new file mode 100644 index 000000000..bfbd23409 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_1_surrogate_then_escape_u1x.json @@ -0,0 +1 @@ +["\uD800\u1x"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_accentuated_char_no_quotes.json b/test/json/fixtures/minefield/n_string_accentuated_char_no_quotes.json new file mode 100644 index 000000000..fd6895693 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_accentuated_char_no_quotes.json @@ -0,0 +1 @@ +[é] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_backslash_00.json b/test/json/fixtures/minefield/n_string_backslash_00.json new file mode 100644 index 000000000..b5bf267b5 Binary files /dev/null and b/test/json/fixtures/minefield/n_string_backslash_00.json differ diff --git a/test/json/fixtures/minefield/n_string_escape_x.json b/test/json/fixtures/minefield/n_string_escape_x.json new file mode 100644 index 000000000..fae291938 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_escape_x.json @@ -0,0 +1 @@ +["\x00"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_escaped_backslash_bad.json b/test/json/fixtures/minefield/n_string_escaped_backslash_bad.json new file mode 100644 index 000000000..016fcb47e --- /dev/null +++ b/test/json/fixtures/minefield/n_string_escaped_backslash_bad.json @@ -0,0 +1 @@ +["\\\"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_escaped_ctrl_char_tab.json b/test/json/fixtures/minefield/n_string_escaped_ctrl_char_tab.json new file mode 100644 index 000000000..f35ea382b --- /dev/null +++ b/test/json/fixtures/minefield/n_string_escaped_ctrl_char_tab.json @@ -0,0 +1 @@ +["\ "] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_escaped_emoji.json b/test/json/fixtures/minefield/n_string_escaped_emoji.json new file mode 100644 index 000000000..a27775421 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_escaped_emoji.json @@ -0,0 +1 @@ +["\🌀"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_incomplete_escape.json b/test/json/fixtures/minefield/n_string_incomplete_escape.json new file mode 100644 index 000000000..3415c33ca --- /dev/null +++ b/test/json/fixtures/minefield/n_string_incomplete_escape.json @@ -0,0 +1 @@ +["\"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_incomplete_escaped_character.json b/test/json/fixtures/minefield/n_string_incomplete_escaped_character.json new file mode 100644 index 000000000..0f2197ea2 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_incomplete_escaped_character.json @@ -0,0 +1 @@ +["\u00A"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_incomplete_surrogate.json b/test/json/fixtures/minefield/n_string_incomplete_surrogate.json new file mode 100644 index 000000000..75504a656 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_incomplete_surrogate.json @@ -0,0 +1 @@ +["\uD834\uDd"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_incomplete_surrogate_escape_invalid.json b/test/json/fixtures/minefield/n_string_incomplete_surrogate_escape_invalid.json new file mode 100644 index 000000000..bd9656060 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_incomplete_surrogate_escape_invalid.json @@ -0,0 +1 @@ +["\uD800\uD800\x"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_invalid-utf-8-in-escape.json b/test/json/fixtures/minefield/n_string_invalid-utf-8-in-escape.json new file mode 100644 index 000000000..0c4300643 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_invalid-utf-8-in-escape.json @@ -0,0 +1 @@ +["\uå"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_invalid_backslash_esc.json b/test/json/fixtures/minefield/n_string_invalid_backslash_esc.json new file mode 100644 index 000000000..d1eb60921 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_invalid_backslash_esc.json @@ -0,0 +1 @@ +["\a"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_invalid_unicode_escape.json b/test/json/fixtures/minefield/n_string_invalid_unicode_escape.json new file mode 100644 index 000000000..7608cb6ba --- /dev/null +++ b/test/json/fixtures/minefield/n_string_invalid_unicode_escape.json @@ -0,0 +1 @@ +["\uqqqq"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_invalid_utf8_after_escape.json b/test/json/fixtures/minefield/n_string_invalid_utf8_after_escape.json new file mode 100644 index 000000000..2f757a25b --- /dev/null +++ b/test/json/fixtures/minefield/n_string_invalid_utf8_after_escape.json @@ -0,0 +1 @@ +["\å"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_leading_uescaped_thinspace.json b/test/json/fixtures/minefield/n_string_leading_uescaped_thinspace.json new file mode 100644 index 000000000..7b297c636 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_leading_uescaped_thinspace.json @@ -0,0 +1 @@ +[\u0020"asd"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_no_quotes_with_bad_escape.json b/test/json/fixtures/minefield/n_string_no_quotes_with_bad_escape.json new file mode 100644 index 000000000..01bc70aba --- /dev/null +++ b/test/json/fixtures/minefield/n_string_no_quotes_with_bad_escape.json @@ -0,0 +1 @@ +[\n] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_single_doublequote.json b/test/json/fixtures/minefield/n_string_single_doublequote.json new file mode 100644 index 000000000..9d68933c4 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_single_doublequote.json @@ -0,0 +1 @@ +" \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_single_quote.json b/test/json/fixtures/minefield/n_string_single_quote.json new file mode 100644 index 000000000..caff239bf --- /dev/null +++ b/test/json/fixtures/minefield/n_string_single_quote.json @@ -0,0 +1 @@ +['single quote'] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_single_string_no_double_quotes.json b/test/json/fixtures/minefield/n_string_single_string_no_double_quotes.json new file mode 100644 index 000000000..f2ba8f84a --- /dev/null +++ b/test/json/fixtures/minefield/n_string_single_string_no_double_quotes.json @@ -0,0 +1 @@ +abc \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_start_escape_unclosed.json b/test/json/fixtures/minefield/n_string_start_escape_unclosed.json new file mode 100644 index 000000000..db62a46fc --- /dev/null +++ b/test/json/fixtures/minefield/n_string_start_escape_unclosed.json @@ -0,0 +1 @@ +["\ \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_unescaped_ctrl_char.json b/test/json/fixtures/minefield/n_string_unescaped_ctrl_char.json new file mode 100644 index 000000000..9f2134807 Binary files /dev/null and b/test/json/fixtures/minefield/n_string_unescaped_ctrl_char.json differ diff --git a/test/json/fixtures/minefield/n_string_unescaped_newline.json b/test/json/fixtures/minefield/n_string_unescaped_newline.json new file mode 100644 index 000000000..700d36086 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_unescaped_newline.json @@ -0,0 +1,2 @@ +["new +line"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_unescaped_tab.json b/test/json/fixtures/minefield/n_string_unescaped_tab.json new file mode 100644 index 000000000..160264a2d --- /dev/null +++ b/test/json/fixtures/minefield/n_string_unescaped_tab.json @@ -0,0 +1 @@ +[" "] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_unicode_CapitalU.json b/test/json/fixtures/minefield/n_string_unicode_CapitalU.json new file mode 100644 index 000000000..17332bb17 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_unicode_CapitalU.json @@ -0,0 +1 @@ +"\UA66D" \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_string_with_trailing_garbage.json b/test/json/fixtures/minefield/n_string_with_trailing_garbage.json new file mode 100644 index 000000000..efe3bd272 --- /dev/null +++ b/test/json/fixtures/minefield/n_string_with_trailing_garbage.json @@ -0,0 +1 @@ +""x \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_100000_opening_arrays.json b/test/json/fixtures/minefield/n_structure_100000_opening_arrays.json new file mode 100644 index 000000000..a4823eecc --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_100000_opening_arrays.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_U+2060_word_joined.json b/test/json/fixtures/minefield/n_structure_U+2060_word_joined.json new file mode 100644 index 000000000..81156a699 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_U+2060_word_joined.json @@ -0,0 +1 @@ +[⁠] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_UTF8_BOM_no_data.json b/test/json/fixtures/minefield/n_structure_UTF8_BOM_no_data.json new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_UTF8_BOM_no_data.json @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_angle_bracket_..json b/test/json/fixtures/minefield/n_structure_angle_bracket_..json new file mode 100644 index 000000000..a56fef0b0 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_angle_bracket_..json @@ -0,0 +1 @@ +<.> \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_angle_bracket_null.json b/test/json/fixtures/minefield/n_structure_angle_bracket_null.json new file mode 100644 index 000000000..617f26254 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_angle_bracket_null.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_array_trailing_garbage.json b/test/json/fixtures/minefield/n_structure_array_trailing_garbage.json new file mode 100644 index 000000000..5a745e6f3 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_array_trailing_garbage.json @@ -0,0 +1 @@ +[1]x \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_array_with_extra_array_close.json b/test/json/fixtures/minefield/n_structure_array_with_extra_array_close.json new file mode 100644 index 000000000..6cfb1398d --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_array_with_extra_array_close.json @@ -0,0 +1 @@ +[1]] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_array_with_unclosed_string.json b/test/json/fixtures/minefield/n_structure_array_with_unclosed_string.json new file mode 100644 index 000000000..ba6b1788b --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_array_with_unclosed_string.json @@ -0,0 +1 @@ +["asd] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_ascii-unicode-identifier.json b/test/json/fixtures/minefield/n_structure_ascii-unicode-identifier.json new file mode 100644 index 000000000..ef2ab62fe --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_ascii-unicode-identifier.json @@ -0,0 +1 @@ +aå \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_capitalized_True.json b/test/json/fixtures/minefield/n_structure_capitalized_True.json new file mode 100644 index 000000000..7cd88469a --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_capitalized_True.json @@ -0,0 +1 @@ +[True] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_close_unopened_array.json b/test/json/fixtures/minefield/n_structure_close_unopened_array.json new file mode 100644 index 000000000..d2af0c646 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_close_unopened_array.json @@ -0,0 +1 @@ +1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_comma_instead_of_closing_brace.json b/test/json/fixtures/minefield/n_structure_comma_instead_of_closing_brace.json new file mode 100644 index 000000000..ac61b8200 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_comma_instead_of_closing_brace.json @@ -0,0 +1 @@ +{"x": true, \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_double_array.json b/test/json/fixtures/minefield/n_structure_double_array.json new file mode 100644 index 000000000..058d1626e --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_double_array.json @@ -0,0 +1 @@ +[][] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_end_array.json b/test/json/fixtures/minefield/n_structure_end_array.json new file mode 100644 index 000000000..54caf60b1 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_end_array.json @@ -0,0 +1 @@ +] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_incomplete_UTF8_BOM.json b/test/json/fixtures/minefield/n_structure_incomplete_UTF8_BOM.json new file mode 100644 index 000000000..bfcdd514f --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_incomplete_UTF8_BOM.json @@ -0,0 +1 @@ +ï»{} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_lone-invalid-utf-8.json b/test/json/fixtures/minefield/n_structure_lone-invalid-utf-8.json new file mode 100644 index 000000000..8b1296cad --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_lone-invalid-utf-8.json @@ -0,0 +1 @@ +å \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_lone-open-bracket.json b/test/json/fixtures/minefield/n_structure_lone-open-bracket.json new file mode 100644 index 000000000..8e2f0bef1 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_lone-open-bracket.json @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/diagrams/.keep b/test/json/fixtures/minefield/n_structure_no_data.json similarity index 100% rename from diagrams/.keep rename to test/json/fixtures/minefield/n_structure_no_data.json diff --git a/test/json/fixtures/minefield/n_structure_null-byte-outside-string.json b/test/json/fixtures/minefield/n_structure_null-byte-outside-string.json new file mode 100644 index 000000000..326db1442 Binary files /dev/null and b/test/json/fixtures/minefield/n_structure_null-byte-outside-string.json differ diff --git a/test/json/fixtures/minefield/n_structure_number_with_trailing_garbage.json b/test/json/fixtures/minefield/n_structure_number_with_trailing_garbage.json new file mode 100644 index 000000000..0746539d2 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_number_with_trailing_garbage.json @@ -0,0 +1 @@ +2@ \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_object_followed_by_closing_object.json b/test/json/fixtures/minefield/n_structure_object_followed_by_closing_object.json new file mode 100644 index 000000000..aa9ebaec5 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_object_followed_by_closing_object.json @@ -0,0 +1 @@ +{}} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_object_unclosed_no_value.json b/test/json/fixtures/minefield/n_structure_object_unclosed_no_value.json new file mode 100644 index 000000000..17d045147 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_object_unclosed_no_value.json @@ -0,0 +1 @@ +{"": \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_object_with_comment.json b/test/json/fixtures/minefield/n_structure_object_with_comment.json new file mode 100644 index 000000000..ed1b569b7 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_object_with_comment.json @@ -0,0 +1 @@ +{"a":/*comment*/"b"} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_object_with_trailing_garbage.json b/test/json/fixtures/minefield/n_structure_object_with_trailing_garbage.json new file mode 100644 index 000000000..9ca2336d7 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_object_with_trailing_garbage.json @@ -0,0 +1 @@ +{"a": true} "x" \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_open_array_apostrophe.json b/test/json/fixtures/minefield/n_structure_open_array_apostrophe.json new file mode 100644 index 000000000..8bebe3af0 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_open_array_apostrophe.json @@ -0,0 +1 @@ +[' \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_open_array_comma.json b/test/json/fixtures/minefield/n_structure_open_array_comma.json new file mode 100644 index 000000000..6295fdc36 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_open_array_comma.json @@ -0,0 +1 @@ +[, \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_open_array_object.json b/test/json/fixtures/minefield/n_structure_open_array_object.json new file mode 100644 index 000000000..e870445b2 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_open_array_object.json @@ -0,0 +1 @@ +[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"": diff --git a/test/json/fixtures/minefield/n_structure_open_array_open_object.json b/test/json/fixtures/minefield/n_structure_open_array_open_object.json new file mode 100644 index 000000000..7a63c8c57 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_open_array_open_object.json @@ -0,0 +1 @@ +[{ \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_open_array_open_string.json b/test/json/fixtures/minefield/n_structure_open_array_open_string.json new file mode 100644 index 000000000..9822a6baf --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_open_array_open_string.json @@ -0,0 +1 @@ +["a \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_open_array_string.json b/test/json/fixtures/minefield/n_structure_open_array_string.json new file mode 100644 index 000000000..42a619362 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_open_array_string.json @@ -0,0 +1 @@ +["a" \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_open_object.json b/test/json/fixtures/minefield/n_structure_open_object.json new file mode 100644 index 000000000..81750b96f --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_open_object.json @@ -0,0 +1 @@ +{ \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_open_object_close_array.json b/test/json/fixtures/minefield/n_structure_open_object_close_array.json new file mode 100644 index 000000000..eebc700a1 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_open_object_close_array.json @@ -0,0 +1 @@ +{] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_open_object_comma.json b/test/json/fixtures/minefield/n_structure_open_object_comma.json new file mode 100644 index 000000000..47bc9106f --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_open_object_comma.json @@ -0,0 +1 @@ +{, \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_open_object_open_array.json b/test/json/fixtures/minefield/n_structure_open_object_open_array.json new file mode 100644 index 000000000..381ede5de --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_open_object_open_array.json @@ -0,0 +1 @@ +{[ \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_open_object_open_string.json b/test/json/fixtures/minefield/n_structure_open_object_open_string.json new file mode 100644 index 000000000..328c30cd7 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_open_object_open_string.json @@ -0,0 +1 @@ +{"a \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_open_object_string_with_apostrophes.json b/test/json/fixtures/minefield/n_structure_open_object_string_with_apostrophes.json new file mode 100644 index 000000000..9dba17090 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_open_object_string_with_apostrophes.json @@ -0,0 +1 @@ +{'a' \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_open_open.json b/test/json/fixtures/minefield/n_structure_open_open.json new file mode 100644 index 000000000..841fd5f86 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_open_open.json @@ -0,0 +1 @@ +["\{["\{["\{["\{ \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_single_eacute.json b/test/json/fixtures/minefield/n_structure_single_eacute.json new file mode 100644 index 000000000..92a39f398 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_single_eacute.json @@ -0,0 +1 @@ +é \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_single_star.json b/test/json/fixtures/minefield/n_structure_single_star.json new file mode 100644 index 000000000..f59ec20aa --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_single_star.json @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_trailing_#.json b/test/json/fixtures/minefield/n_structure_trailing_#.json new file mode 100644 index 000000000..898611087 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_trailing_#.json @@ -0,0 +1 @@ +{"a":"b"}#{} \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_uescaped_LF_before_string.json b/test/json/fixtures/minefield/n_structure_uescaped_LF_before_string.json new file mode 100644 index 000000000..df2f0f242 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_uescaped_LF_before_string.json @@ -0,0 +1 @@ +[\u000A""] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_unclosed_array.json b/test/json/fixtures/minefield/n_structure_unclosed_array.json new file mode 100644 index 000000000..11209515c --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_unclosed_array.json @@ -0,0 +1 @@ +[1 \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_unclosed_array_partial_null.json b/test/json/fixtures/minefield/n_structure_unclosed_array_partial_null.json new file mode 100644 index 000000000..0d591762c --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_unclosed_array_partial_null.json @@ -0,0 +1 @@ +[ false, nul \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_unclosed_array_unfinished_false.json b/test/json/fixtures/minefield/n_structure_unclosed_array_unfinished_false.json new file mode 100644 index 000000000..a2ff8504a --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_unclosed_array_unfinished_false.json @@ -0,0 +1 @@ +[ true, fals \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_unclosed_array_unfinished_true.json b/test/json/fixtures/minefield/n_structure_unclosed_array_unfinished_true.json new file mode 100644 index 000000000..3149e8f5a --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_unclosed_array_unfinished_true.json @@ -0,0 +1 @@ +[ false, tru \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_unclosed_object.json b/test/json/fixtures/minefield/n_structure_unclosed_object.json new file mode 100644 index 000000000..694d69dbd --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_unclosed_object.json @@ -0,0 +1 @@ +{"asd":"asd" \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_unicode-identifier.json b/test/json/fixtures/minefield/n_structure_unicode-identifier.json new file mode 100644 index 000000000..7284aea33 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_unicode-identifier.json @@ -0,0 +1 @@ +å \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_whitespace_U+2060_word_joiner.json b/test/json/fixtures/minefield/n_structure_whitespace_U+2060_word_joiner.json new file mode 100644 index 000000000..81156a699 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_whitespace_U+2060_word_joiner.json @@ -0,0 +1 @@ +[⁠] \ No newline at end of file diff --git a/test/json/fixtures/minefield/n_structure_whitespace_formfeed.json b/test/json/fixtures/minefield/n_structure_whitespace_formfeed.json new file mode 100644 index 000000000..a9ea535d1 --- /dev/null +++ b/test/json/fixtures/minefield/n_structure_whitespace_formfeed.json @@ -0,0 +1 @@ +[ ] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_array_arraysWithSpaces.json b/test/json/fixtures/minefield/y_array_arraysWithSpaces.json new file mode 100644 index 000000000..582290798 --- /dev/null +++ b/test/json/fixtures/minefield/y_array_arraysWithSpaces.json @@ -0,0 +1 @@ +[[] ] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_array_empty-string.json b/test/json/fixtures/minefield/y_array_empty-string.json new file mode 100644 index 000000000..93b6be2bc --- /dev/null +++ b/test/json/fixtures/minefield/y_array_empty-string.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_array_empty.json b/test/json/fixtures/minefield/y_array_empty.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/test/json/fixtures/minefield/y_array_empty.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_array_ending_with_newline.json b/test/json/fixtures/minefield/y_array_ending_with_newline.json new file mode 100644 index 000000000..eac5f7b46 --- /dev/null +++ b/test/json/fixtures/minefield/y_array_ending_with_newline.json @@ -0,0 +1 @@ +["a"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_array_false.json b/test/json/fixtures/minefield/y_array_false.json new file mode 100644 index 000000000..67b2f0760 --- /dev/null +++ b/test/json/fixtures/minefield/y_array_false.json @@ -0,0 +1 @@ +[false] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_array_heterogeneous.json b/test/json/fixtures/minefield/y_array_heterogeneous.json new file mode 100644 index 000000000..d3c1e2648 --- /dev/null +++ b/test/json/fixtures/minefield/y_array_heterogeneous.json @@ -0,0 +1 @@ +[null, 1, "1", {}] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_array_null.json b/test/json/fixtures/minefield/y_array_null.json new file mode 100644 index 000000000..500db4a86 --- /dev/null +++ b/test/json/fixtures/minefield/y_array_null.json @@ -0,0 +1 @@ +[null] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_array_with_1_and_newline.json b/test/json/fixtures/minefield/y_array_with_1_and_newline.json new file mode 100644 index 000000000..994825500 --- /dev/null +++ b/test/json/fixtures/minefield/y_array_with_1_and_newline.json @@ -0,0 +1,2 @@ +[1 +] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_array_with_leading_space.json b/test/json/fixtures/minefield/y_array_with_leading_space.json new file mode 100644 index 000000000..18bfe6422 --- /dev/null +++ b/test/json/fixtures/minefield/y_array_with_leading_space.json @@ -0,0 +1 @@ + [1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_array_with_several_null.json b/test/json/fixtures/minefield/y_array_with_several_null.json new file mode 100644 index 000000000..99f6c5d1d --- /dev/null +++ b/test/json/fixtures/minefield/y_array_with_several_null.json @@ -0,0 +1 @@ +[1,null,null,null,2] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_array_with_trailing_space.json b/test/json/fixtures/minefield/y_array_with_trailing_space.json new file mode 100644 index 000000000..de9e7a944 --- /dev/null +++ b/test/json/fixtures/minefield/y_array_with_trailing_space.json @@ -0,0 +1 @@ +[2] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number.json b/test/json/fixtures/minefield/y_number.json new file mode 100644 index 000000000..e5f5cc334 --- /dev/null +++ b/test/json/fixtures/minefield/y_number.json @@ -0,0 +1 @@ +[123e65] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_0e+1.json b/test/json/fixtures/minefield/y_number_0e+1.json new file mode 100644 index 000000000..d1d396706 --- /dev/null +++ b/test/json/fixtures/minefield/y_number_0e+1.json @@ -0,0 +1 @@ +[0e+1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_0e1.json b/test/json/fixtures/minefield/y_number_0e1.json new file mode 100644 index 000000000..3283a7936 --- /dev/null +++ b/test/json/fixtures/minefield/y_number_0e1.json @@ -0,0 +1 @@ +[0e1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_after_space.json b/test/json/fixtures/minefield/y_number_after_space.json new file mode 100644 index 000000000..623570d96 --- /dev/null +++ b/test/json/fixtures/minefield/y_number_after_space.json @@ -0,0 +1 @@ +[ 4] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_double_close_to_zero.json b/test/json/fixtures/minefield/y_number_double_close_to_zero.json new file mode 100644 index 000000000..96555ff78 --- /dev/null +++ b/test/json/fixtures/minefield/y_number_double_close_to_zero.json @@ -0,0 +1 @@ +[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001] diff --git a/test/json/fixtures/minefield/y_number_int_with_exp.json b/test/json/fixtures/minefield/y_number_int_with_exp.json new file mode 100644 index 000000000..a4ca9e754 --- /dev/null +++ b/test/json/fixtures/minefield/y_number_int_with_exp.json @@ -0,0 +1 @@ +[20e1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_minus_zero.json b/test/json/fixtures/minefield/y_number_minus_zero.json new file mode 100644 index 000000000..37af1312a --- /dev/null +++ b/test/json/fixtures/minefield/y_number_minus_zero.json @@ -0,0 +1 @@ +[-0] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_negative_int.json b/test/json/fixtures/minefield/y_number_negative_int.json new file mode 100644 index 000000000..8e30f8bd9 --- /dev/null +++ b/test/json/fixtures/minefield/y_number_negative_int.json @@ -0,0 +1 @@ +[-123] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_negative_one.json b/test/json/fixtures/minefield/y_number_negative_one.json new file mode 100644 index 000000000..99d21a2a0 --- /dev/null +++ b/test/json/fixtures/minefield/y_number_negative_one.json @@ -0,0 +1 @@ +[-1] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_negative_zero.json b/test/json/fixtures/minefield/y_number_negative_zero.json new file mode 100644 index 000000000..37af1312a --- /dev/null +++ b/test/json/fixtures/minefield/y_number_negative_zero.json @@ -0,0 +1 @@ +[-0] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_real_capital_e.json b/test/json/fixtures/minefield/y_number_real_capital_e.json new file mode 100644 index 000000000..6edbdfcb1 --- /dev/null +++ b/test/json/fixtures/minefield/y_number_real_capital_e.json @@ -0,0 +1 @@ +[1E22] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_real_capital_e_neg_exp.json b/test/json/fixtures/minefield/y_number_real_capital_e_neg_exp.json new file mode 100644 index 000000000..0a01bd3ef --- /dev/null +++ b/test/json/fixtures/minefield/y_number_real_capital_e_neg_exp.json @@ -0,0 +1 @@ +[1E-2] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_real_capital_e_pos_exp.json b/test/json/fixtures/minefield/y_number_real_capital_e_pos_exp.json new file mode 100644 index 000000000..5a8fc0972 --- /dev/null +++ b/test/json/fixtures/minefield/y_number_real_capital_e_pos_exp.json @@ -0,0 +1 @@ +[1E+2] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_real_exponent.json b/test/json/fixtures/minefield/y_number_real_exponent.json new file mode 100644 index 000000000..da2522d61 --- /dev/null +++ b/test/json/fixtures/minefield/y_number_real_exponent.json @@ -0,0 +1 @@ +[123e45] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_real_fraction_exponent.json b/test/json/fixtures/minefield/y_number_real_fraction_exponent.json new file mode 100644 index 000000000..3944a7a45 --- /dev/null +++ b/test/json/fixtures/minefield/y_number_real_fraction_exponent.json @@ -0,0 +1 @@ +[123.456e78] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_real_neg_exp.json b/test/json/fixtures/minefield/y_number_real_neg_exp.json new file mode 100644 index 000000000..ca40d3c25 --- /dev/null +++ b/test/json/fixtures/minefield/y_number_real_neg_exp.json @@ -0,0 +1 @@ +[1e-2] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_real_pos_exponent.json b/test/json/fixtures/minefield/y_number_real_pos_exponent.json new file mode 100644 index 000000000..343601d51 --- /dev/null +++ b/test/json/fixtures/minefield/y_number_real_pos_exponent.json @@ -0,0 +1 @@ +[1e+2] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_simple_int.json b/test/json/fixtures/minefield/y_number_simple_int.json new file mode 100644 index 000000000..e47f69afc --- /dev/null +++ b/test/json/fixtures/minefield/y_number_simple_int.json @@ -0,0 +1 @@ +[123] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_number_simple_real.json b/test/json/fixtures/minefield/y_number_simple_real.json new file mode 100644 index 000000000..b02878e5f --- /dev/null +++ b/test/json/fixtures/minefield/y_number_simple_real.json @@ -0,0 +1 @@ +[123.456789] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_object.json b/test/json/fixtures/minefield/y_object.json new file mode 100644 index 000000000..78262eda3 --- /dev/null +++ b/test/json/fixtures/minefield/y_object.json @@ -0,0 +1 @@ +{"asd":"sdf", "dfg":"fgh"} \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_object_basic.json b/test/json/fixtures/minefield/y_object_basic.json new file mode 100644 index 000000000..646bbe7bb --- /dev/null +++ b/test/json/fixtures/minefield/y_object_basic.json @@ -0,0 +1 @@ +{"asd":"sdf"} \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_object_duplicated_key.json b/test/json/fixtures/minefield/y_object_duplicated_key.json new file mode 100644 index 000000000..bbc2e1ce4 --- /dev/null +++ b/test/json/fixtures/minefield/y_object_duplicated_key.json @@ -0,0 +1 @@ +{"a":"b","a":"c"} \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_object_duplicated_key_and_value.json b/test/json/fixtures/minefield/y_object_duplicated_key_and_value.json new file mode 100644 index 000000000..211581c20 --- /dev/null +++ b/test/json/fixtures/minefield/y_object_duplicated_key_and_value.json @@ -0,0 +1 @@ +{"a":"b","a":"b"} \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_object_empty.json b/test/json/fixtures/minefield/y_object_empty.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/test/json/fixtures/minefield/y_object_empty.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_object_empty_key.json b/test/json/fixtures/minefield/y_object_empty_key.json new file mode 100644 index 000000000..c0013d3b8 --- /dev/null +++ b/test/json/fixtures/minefield/y_object_empty_key.json @@ -0,0 +1 @@ +{"":0} \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_object_escaped_null_in_key.json b/test/json/fixtures/minefield/y_object_escaped_null_in_key.json new file mode 100644 index 000000000..593f0f67f --- /dev/null +++ b/test/json/fixtures/minefield/y_object_escaped_null_in_key.json @@ -0,0 +1 @@ +{"foo\u0000bar": 42} \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_object_extreme_numbers.json b/test/json/fixtures/minefield/y_object_extreme_numbers.json new file mode 100644 index 000000000..a0d3531c3 --- /dev/null +++ b/test/json/fixtures/minefield/y_object_extreme_numbers.json @@ -0,0 +1 @@ +{ "min": -1.0e+28, "max": 1.0e+28 } \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_object_long_strings.json b/test/json/fixtures/minefield/y_object_long_strings.json new file mode 100644 index 000000000..bdc4a0871 --- /dev/null +++ b/test/json/fixtures/minefield/y_object_long_strings.json @@ -0,0 +1 @@ +{"x":[{"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}], "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"} \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_object_simple.json b/test/json/fixtures/minefield/y_object_simple.json new file mode 100644 index 000000000..dacac917f --- /dev/null +++ b/test/json/fixtures/minefield/y_object_simple.json @@ -0,0 +1 @@ +{"a":[]} \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_object_string_unicode.json b/test/json/fixtures/minefield/y_object_string_unicode.json new file mode 100644 index 000000000..8effdb297 --- /dev/null +++ b/test/json/fixtures/minefield/y_object_string_unicode.json @@ -0,0 +1 @@ +{"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430" } \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_object_with_newlines.json b/test/json/fixtures/minefield/y_object_with_newlines.json new file mode 100644 index 000000000..246ec6b34 --- /dev/null +++ b/test/json/fixtures/minefield/y_object_with_newlines.json @@ -0,0 +1,3 @@ +{ +"a": "b" +} \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_1_2_3_bytes_UTF-8_sequences.json b/test/json/fixtures/minefield/y_string_1_2_3_bytes_UTF-8_sequences.json new file mode 100644 index 000000000..9967ddeb8 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_1_2_3_bytes_UTF-8_sequences.json @@ -0,0 +1 @@ +["\u0060\u012a\u12AB"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_accepted_surrogate_pair.json b/test/json/fixtures/minefield/y_string_accepted_surrogate_pair.json new file mode 100644 index 000000000..996875cc8 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_accepted_surrogate_pair.json @@ -0,0 +1 @@ +["\uD801\udc37"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_accepted_surrogate_pairs.json b/test/json/fixtures/minefield/y_string_accepted_surrogate_pairs.json new file mode 100644 index 000000000..3401021ec --- /dev/null +++ b/test/json/fixtures/minefield/y_string_accepted_surrogate_pairs.json @@ -0,0 +1 @@ +["\ud83d\ude39\ud83d\udc8d"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_allowed_escapes.json b/test/json/fixtures/minefield/y_string_allowed_escapes.json new file mode 100644 index 000000000..7f495532f --- /dev/null +++ b/test/json/fixtures/minefield/y_string_allowed_escapes.json @@ -0,0 +1 @@ +["\"\\\/\b\f\n\r\t"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_backslash_and_u_escaped_zero.json b/test/json/fixtures/minefield/y_string_backslash_and_u_escaped_zero.json new file mode 100644 index 000000000..d4439eda7 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_backslash_and_u_escaped_zero.json @@ -0,0 +1 @@ +["\\u0000"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_backslash_doublequotes.json b/test/json/fixtures/minefield/y_string_backslash_doublequotes.json new file mode 100644 index 000000000..ae03243b6 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_backslash_doublequotes.json @@ -0,0 +1 @@ +["\""] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_comments.json b/test/json/fixtures/minefield/y_string_comments.json new file mode 100644 index 000000000..2260c20c2 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_comments.json @@ -0,0 +1 @@ +["a/*b*/c/*d//e"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_double_escape_a.json b/test/json/fixtures/minefield/y_string_double_escape_a.json new file mode 100644 index 000000000..6715d6f40 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_double_escape_a.json @@ -0,0 +1 @@ +["\\a"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_double_escape_n.json b/test/json/fixtures/minefield/y_string_double_escape_n.json new file mode 100644 index 000000000..44ca56c4d --- /dev/null +++ b/test/json/fixtures/minefield/y_string_double_escape_n.json @@ -0,0 +1 @@ +["\\n"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_escaped_control_character.json b/test/json/fixtures/minefield/y_string_escaped_control_character.json new file mode 100644 index 000000000..5b014a9c2 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_escaped_control_character.json @@ -0,0 +1 @@ +["\u0012"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_escaped_noncharacter.json b/test/json/fixtures/minefield/y_string_escaped_noncharacter.json new file mode 100644 index 000000000..2ff52e2c5 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_escaped_noncharacter.json @@ -0,0 +1 @@ +["\uFFFF"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_in_array.json b/test/json/fixtures/minefield/y_string_in_array.json new file mode 100644 index 000000000..21d7ae4cd --- /dev/null +++ b/test/json/fixtures/minefield/y_string_in_array.json @@ -0,0 +1 @@ +["asd"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_in_array_with_leading_space.json b/test/json/fixtures/minefield/y_string_in_array_with_leading_space.json new file mode 100644 index 000000000..9e1887c1e --- /dev/null +++ b/test/json/fixtures/minefield/y_string_in_array_with_leading_space.json @@ -0,0 +1 @@ +[ "asd"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_last_surrogates_1_and_2.json b/test/json/fixtures/minefield/y_string_last_surrogates_1_and_2.json new file mode 100644 index 000000000..3919cef76 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_last_surrogates_1_and_2.json @@ -0,0 +1 @@ +["\uDBFF\uDFFF"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_nbsp_uescaped.json b/test/json/fixtures/minefield/y_string_nbsp_uescaped.json new file mode 100644 index 000000000..2085ab1a1 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_nbsp_uescaped.json @@ -0,0 +1 @@ +["new\u00A0line"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_nonCharacterInUTF-8_U+10FFFF.json b/test/json/fixtures/minefield/y_string_nonCharacterInUTF-8_U+10FFFF.json new file mode 100644 index 000000000..059e4d9dd --- /dev/null +++ b/test/json/fixtures/minefield/y_string_nonCharacterInUTF-8_U+10FFFF.json @@ -0,0 +1 @@ +["􏿿"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_nonCharacterInUTF-8_U+FFFF.json b/test/json/fixtures/minefield/y_string_nonCharacterInUTF-8_U+FFFF.json new file mode 100644 index 000000000..4c913bd41 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_nonCharacterInUTF-8_U+FFFF.json @@ -0,0 +1 @@ +["￿"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_null_escape.json b/test/json/fixtures/minefield/y_string_null_escape.json new file mode 100644 index 000000000..c1ad84404 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_null_escape.json @@ -0,0 +1 @@ +["\u0000"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_one-byte-utf-8.json b/test/json/fixtures/minefield/y_string_one-byte-utf-8.json new file mode 100644 index 000000000..157185923 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_one-byte-utf-8.json @@ -0,0 +1 @@ +["\u002c"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_pi.json b/test/json/fixtures/minefield/y_string_pi.json new file mode 100644 index 000000000..9df11ae88 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_pi.json @@ -0,0 +1 @@ +["π"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_reservedCharacterInUTF-8_U+1BFFF.json b/test/json/fixtures/minefield/y_string_reservedCharacterInUTF-8_U+1BFFF.json new file mode 100644 index 000000000..10a33a171 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_reservedCharacterInUTF-8_U+1BFFF.json @@ -0,0 +1 @@ +["𛿿"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_simple_ascii.json b/test/json/fixtures/minefield/y_string_simple_ascii.json new file mode 100644 index 000000000..8cadf7d05 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_simple_ascii.json @@ -0,0 +1 @@ +["asd "] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_space.json b/test/json/fixtures/minefield/y_string_space.json new file mode 100644 index 000000000..efd782cc3 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_space.json @@ -0,0 +1 @@ +" " \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json b/test/json/fixtures/minefield/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json new file mode 100644 index 000000000..7620b6655 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json @@ -0,0 +1 @@ +["\uD834\uDd1e"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_three-byte-utf-8.json b/test/json/fixtures/minefield/y_string_three-byte-utf-8.json new file mode 100644 index 000000000..108f1d67d --- /dev/null +++ b/test/json/fixtures/minefield/y_string_three-byte-utf-8.json @@ -0,0 +1 @@ +["\u0821"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_two-byte-utf-8.json b/test/json/fixtures/minefield/y_string_two-byte-utf-8.json new file mode 100644 index 000000000..461503c31 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_two-byte-utf-8.json @@ -0,0 +1 @@ +["\u0123"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_u+2028_line_sep.json b/test/json/fixtures/minefield/y_string_u+2028_line_sep.json new file mode 100644 index 000000000..897b6021a --- /dev/null +++ b/test/json/fixtures/minefield/y_string_u+2028_line_sep.json @@ -0,0 +1 @@ +["
"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_u+2029_par_sep.json b/test/json/fixtures/minefield/y_string_u+2029_par_sep.json new file mode 100644 index 000000000..8cd998c89 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_u+2029_par_sep.json @@ -0,0 +1 @@ +["
"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_uEscape.json b/test/json/fixtures/minefield/y_string_uEscape.json new file mode 100644 index 000000000..f7b41a02f --- /dev/null +++ b/test/json/fixtures/minefield/y_string_uEscape.json @@ -0,0 +1 @@ +["\u0061\u30af\u30EA\u30b9"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_uescaped_newline.json b/test/json/fixtures/minefield/y_string_uescaped_newline.json new file mode 100644 index 000000000..3a5a220b6 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_uescaped_newline.json @@ -0,0 +1 @@ +["new\u000Aline"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_unescaped_char_delete.json b/test/json/fixtures/minefield/y_string_unescaped_char_delete.json new file mode 100644 index 000000000..7d064f498 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_unescaped_char_delete.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_unicode.json b/test/json/fixtures/minefield/y_string_unicode.json new file mode 100644 index 000000000..3598095b7 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_unicode.json @@ -0,0 +1 @@ +["\uA66D"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_unicodeEscapedBackslash.json b/test/json/fixtures/minefield/y_string_unicodeEscapedBackslash.json new file mode 100644 index 000000000..0bb3b51e7 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_unicodeEscapedBackslash.json @@ -0,0 +1 @@ +["\u005C"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_unicode_2.json b/test/json/fixtures/minefield/y_string_unicode_2.json new file mode 100644 index 000000000..a7dcb9768 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_unicode_2.json @@ -0,0 +1 @@ +["⍂㈴⍂"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_unicode_U+10FFFE_nonchar.json b/test/json/fixtures/minefield/y_string_unicode_U+10FFFE_nonchar.json new file mode 100644 index 000000000..9a8370b96 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_unicode_U+10FFFE_nonchar.json @@ -0,0 +1 @@ +["\uDBFF\uDFFE"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_unicode_U+1FFFE_nonchar.json b/test/json/fixtures/minefield/y_string_unicode_U+1FFFE_nonchar.json new file mode 100644 index 000000000..c51f8ae45 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_unicode_U+1FFFE_nonchar.json @@ -0,0 +1 @@ +["\uD83F\uDFFE"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json b/test/json/fixtures/minefield/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json new file mode 100644 index 000000000..626d5f815 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json @@ -0,0 +1 @@ +["\u200B"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_unicode_U+2064_invisible_plus.json b/test/json/fixtures/minefield/y_string_unicode_U+2064_invisible_plus.json new file mode 100644 index 000000000..1e23972c6 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_unicode_U+2064_invisible_plus.json @@ -0,0 +1 @@ +["\u2064"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_unicode_U+FDD0_nonchar.json b/test/json/fixtures/minefield/y_string_unicode_U+FDD0_nonchar.json new file mode 100644 index 000000000..18ef151b4 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_unicode_U+FDD0_nonchar.json @@ -0,0 +1 @@ +["\uFDD0"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_unicode_U+FFFE_nonchar.json b/test/json/fixtures/minefield/y_string_unicode_U+FFFE_nonchar.json new file mode 100644 index 000000000..13d261fda --- /dev/null +++ b/test/json/fixtures/minefield/y_string_unicode_U+FFFE_nonchar.json @@ -0,0 +1 @@ +["\uFFFE"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_unicode_escaped_double_quote.json b/test/json/fixtures/minefield/y_string_unicode_escaped_double_quote.json new file mode 100644 index 000000000..4e6257856 --- /dev/null +++ b/test/json/fixtures/minefield/y_string_unicode_escaped_double_quote.json @@ -0,0 +1 @@ +["\u0022"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_utf8.json b/test/json/fixtures/minefield/y_string_utf8.json new file mode 100644 index 000000000..40878435f --- /dev/null +++ b/test/json/fixtures/minefield/y_string_utf8.json @@ -0,0 +1 @@ +["€𝄞"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_string_with_del_character.json b/test/json/fixtures/minefield/y_string_with_del_character.json new file mode 100644 index 000000000..8bd24907d --- /dev/null +++ b/test/json/fixtures/minefield/y_string_with_del_character.json @@ -0,0 +1 @@ +["aa"] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_structure_lonely_false.json b/test/json/fixtures/minefield/y_structure_lonely_false.json new file mode 100644 index 000000000..02e4a84d6 --- /dev/null +++ b/test/json/fixtures/minefield/y_structure_lonely_false.json @@ -0,0 +1 @@ +false \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_structure_lonely_int.json b/test/json/fixtures/minefield/y_structure_lonely_int.json new file mode 100644 index 000000000..f70d7bba4 --- /dev/null +++ b/test/json/fixtures/minefield/y_structure_lonely_int.json @@ -0,0 +1 @@ +42 \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_structure_lonely_negative_real.json b/test/json/fixtures/minefield/y_structure_lonely_negative_real.json new file mode 100644 index 000000000..b5135a207 --- /dev/null +++ b/test/json/fixtures/minefield/y_structure_lonely_negative_real.json @@ -0,0 +1 @@ +-0.1 \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_structure_lonely_null.json b/test/json/fixtures/minefield/y_structure_lonely_null.json new file mode 100644 index 000000000..ec747fa47 --- /dev/null +++ b/test/json/fixtures/minefield/y_structure_lonely_null.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_structure_lonely_string.json b/test/json/fixtures/minefield/y_structure_lonely_string.json new file mode 100644 index 000000000..b6e982ca9 --- /dev/null +++ b/test/json/fixtures/minefield/y_structure_lonely_string.json @@ -0,0 +1 @@ +"asd" \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_structure_lonely_true.json b/test/json/fixtures/minefield/y_structure_lonely_true.json new file mode 100644 index 000000000..f32a5804e --- /dev/null +++ b/test/json/fixtures/minefield/y_structure_lonely_true.json @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_structure_string_empty.json b/test/json/fixtures/minefield/y_structure_string_empty.json new file mode 100644 index 000000000..3cc762b55 --- /dev/null +++ b/test/json/fixtures/minefield/y_structure_string_empty.json @@ -0,0 +1 @@ +"" \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_structure_trailing_newline.json b/test/json/fixtures/minefield/y_structure_trailing_newline.json new file mode 100644 index 000000000..0c3426d4c --- /dev/null +++ b/test/json/fixtures/minefield/y_structure_trailing_newline.json @@ -0,0 +1 @@ +["a"] diff --git a/test/json/fixtures/minefield/y_structure_true_in_array.json b/test/json/fixtures/minefield/y_structure_true_in_array.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/test/json/fixtures/minefield/y_structure_true_in_array.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/test/json/fixtures/minefield/y_structure_whitespace_array.json b/test/json/fixtures/minefield/y_structure_whitespace_array.json new file mode 100644 index 000000000..2bedf7f2d --- /dev/null +++ b/test/json/fixtures/minefield/y_structure_whitespace_array.json @@ -0,0 +1 @@ + [] \ No newline at end of file diff --git a/test/json/fixtures/pass1.json b/test/json/fixtures/pass1.json index 7828fcc13..fa9058b13 100644 --- a/test/json/fixtures/pass1.json +++ b/test/json/fixtures/pass1.json @@ -12,7 +12,7 @@ "real": -9876.543210, "e": 0.123456789e-12, "E": 1.234567890E+34, - "": 23456789012E666, + "": 23456789012E66, "zero": 0, "one": 1, "space": " ", diff --git a/test/json/fixtures/rfc8785/input/arrays.json b/test/json/fixtures/rfc8785/input/arrays.json new file mode 100644 index 000000000..9154bdf15 --- /dev/null +++ b/test/json/fixtures/rfc8785/input/arrays.json @@ -0,0 +1,8 @@ +[ + 56, + { + "d": true, + "10": null, + "1": [ ] + } +] \ No newline at end of file diff --git a/test/json/fixtures/rfc8785/input/french.json b/test/json/fixtures/rfc8785/input/french.json new file mode 100644 index 000000000..d4ad2e4ed --- /dev/null +++ b/test/json/fixtures/rfc8785/input/french.json @@ -0,0 +1,6 @@ +{ + "peach": "This sorting order", + "péché": "is wrong according to French", + "pêche": "but canonicalization MUST", + "sin": "ignore locale" +} \ No newline at end of file diff --git a/test/json/fixtures/rfc8785/input/structures.json b/test/json/fixtures/rfc8785/input/structures.json new file mode 100644 index 000000000..eb71efb84 --- /dev/null +++ b/test/json/fixtures/rfc8785/input/structures.json @@ -0,0 +1,8 @@ +{ + "1": {"f": {"f": "hi","F": 5} ,"\n": 56.0}, + "10": { }, + "": "empty", + "a": { }, + "111": [ {"e": "yes","E": "no" } ], + "A": { } +} \ No newline at end of file diff --git a/test/json/fixtures/rfc8785/input/unicode.json b/test/json/fixtures/rfc8785/input/unicode.json new file mode 100644 index 000000000..9ec5cc7db --- /dev/null +++ b/test/json/fixtures/rfc8785/input/unicode.json @@ -0,0 +1,3 @@ +{ + "Unnormalized Unicode":"A\u030a" +} \ No newline at end of file diff --git a/test/json/fixtures/rfc8785/input/values.json b/test/json/fixtures/rfc8785/input/values.json new file mode 100644 index 000000000..f7712c2fb --- /dev/null +++ b/test/json/fixtures/rfc8785/input/values.json @@ -0,0 +1,5 @@ +{ + "numbers": [333333333.33333329, 1E30, 4.50, 2e-3, 0.000000000000000000000000001], + "string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/", + "literals": [null, true, false] +} \ No newline at end of file diff --git a/test/json/fixtures/rfc8785/input/wierd.json b/test/json/fixtures/rfc8785/input/wierd.json new file mode 100644 index 000000000..940bec4f9 --- /dev/null +++ b/test/json/fixtures/rfc8785/input/wierd.json @@ -0,0 +1,11 @@ +{ + "\u20ac": "Euro Sign", + "\r": "Carriage Return", + "\u000a": "Newline", + "1": "One", + "\u0080": "Control\u007f", + "\ud83d\ude02": "Smiley", + "\u00f6": "Latin Small Letter O With Diaeresis", + "\ufb33": "Hebrew Letter Dalet With Dagesh", + "": "Browser Challenge" +} \ No newline at end of file diff --git a/test/json/fixtures/rfc8785/output/arrays.json b/test/json/fixtures/rfc8785/output/arrays.json new file mode 100644 index 000000000..5efb93db7 --- /dev/null +++ b/test/json/fixtures/rfc8785/output/arrays.json @@ -0,0 +1 @@ +[56,{"1":[],"10":null,"d":true}] \ No newline at end of file diff --git a/test/json/fixtures/rfc8785/output/french.json b/test/json/fixtures/rfc8785/output/french.json new file mode 100644 index 000000000..2e15cd1b2 --- /dev/null +++ b/test/json/fixtures/rfc8785/output/french.json @@ -0,0 +1 @@ +{"peach":"This sorting order","péché":"is wrong according to French","pêche":"but canonicalization MUST","sin":"ignore locale"} \ No newline at end of file diff --git a/test/json/fixtures/rfc8785/output/structures.json b/test/json/fixtures/rfc8785/output/structures.json new file mode 100644 index 000000000..dc21e2424 --- /dev/null +++ b/test/json/fixtures/rfc8785/output/structures.json @@ -0,0 +1 @@ +{"":"empty","1":{"\n":56,"f":{"F":5,"f":"hi"}},"10":{},"111":[{"E":"no","e":"yes"}],"A":{},"a":{}} \ No newline at end of file diff --git a/test/json/fixtures/rfc8785/output/unicode.json b/test/json/fixtures/rfc8785/output/unicode.json new file mode 100644 index 000000000..ee60fd121 --- /dev/null +++ b/test/json/fixtures/rfc8785/output/unicode.json @@ -0,0 +1 @@ +{"Unnormalized Unicode":"Å"} \ No newline at end of file diff --git a/test/json/fixtures/rfc8785/output/values.json b/test/json/fixtures/rfc8785/output/values.json new file mode 100644 index 000000000..29b720b6e --- /dev/null +++ b/test/json/fixtures/rfc8785/output/values.json @@ -0,0 +1 @@ +{"literals":[null,true,false],"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27],"string":"€$\u000f\nA'B\"\\\\\"/"} \ No newline at end of file diff --git a/test/json/fixtures/rfc8785/output/wierd.json b/test/json/fixtures/rfc8785/output/wierd.json new file mode 100644 index 000000000..62c83a333 --- /dev/null +++ b/test/json/fixtures/rfc8785/output/wierd.json @@ -0,0 +1 @@ +{"\n":"Newline","\r":"Carriage Return","1":"One","":"Browser Challenge","€":"Control","ö":"Latin Small Letter O With Diaeresis","€":"Euro Sign","😂":"Smiley","דּ":"Hebrew Letter Dalet With Dagesh"} \ No newline at end of file diff --git a/test/json/json_addition_test.rb b/test/json/json_addition_test.rb deleted file mode 100644 index 1eb269c2f..000000000 --- a/test/json/json_addition_test.rb +++ /dev/null @@ -1,204 +0,0 @@ -# frozen_string_literal: true -require_relative 'test_helper' -require 'json/add/core' -require 'json/add/complex' -require 'json/add/rational' -require 'json/add/bigdecimal' -require 'json/add/ostruct' -require 'json/add/set' -require 'date' - -class JSONAdditionTest < Test::Unit::TestCase - include JSON - - class A - def initialize(a) - @a = a - end - - attr_reader :a - - def ==(other) - a == other.a - end - - def self.json_create(object) - new(*object['args']) - end - - def to_json(*args) - { - 'json_class' => self.class.name, - 'args' => [ @a ], - }.to_json(*args) - end - end - - class A2 < A - def to_json(*args) - { - 'json_class' => self.class.name, - 'args' => [ @a ], - }.to_json(*args) - end - end - - class B - def self.json_creatable? - false - end - - def to_json(*args) - { - 'json_class' => self.class.name, - }.to_json(*args) - end - end - - class C - def self.json_creatable? - false - end - - def to_json(*args) - { - 'json_class' => 'JSONAdditionTest::Nix', - }.to_json(*args) - end - end - - def test_extended_json - a = A.new(666) - assert A.json_creatable? - json = generate(a) - a_again = parse(json, :create_additions => true) - assert_kind_of a.class, a_again - assert_equal a, a_again - end - - def test_extended_json_default - a = A.new(666) - assert A.json_creatable? - json = generate(a) - a_hash = parse(json) - assert_kind_of Hash, a_hash - end - - def test_extended_json_disabled - a = A.new(666) - assert A.json_creatable? - json = generate(a) - a_again = parse(json, :create_additions => true) - assert_kind_of a.class, a_again - assert_equal a, a_again - a_hash = parse(json, :create_additions => false) - assert_kind_of Hash, a_hash - assert_equal( - {"args"=>[666], "json_class"=>"JSONAdditionTest::A"}.sort_by { |k,| k }, - a_hash.sort_by { |k,| k } - ) - end - - def test_extended_json_fail1 - b = B.new - assert !B.json_creatable? - json = generate(b) - assert_equal({ "json_class"=>"JSONAdditionTest::B" }, parse(json)) - end - - def test_extended_json_fail2 - c = C.new - assert !C.json_creatable? - json = generate(c) - assert_raise(ArgumentError, NameError) { parse(json, :create_additions => true) } - end - - def test_raw_strings - raw = ''.b - raw_array = [] - for i in 0..255 - raw << i - raw_array << i - end - json = raw.to_json_raw - json_raw_object = raw.to_json_raw_object - hash = { 'json_class' => 'String', 'raw'=> raw_array } - assert_equal hash, json_raw_object - assert_match(/\A\{.*\}\z/, json) - assert_match(/"json_class":"String"/, json) - assert_match(/"raw":\[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255\]/, json) - raw_again = parse(json, :create_additions => true) - assert_equal raw, raw_again - end - - MyJsonStruct = Struct.new 'MyJsonStruct', :foo, :bar - - def test_core - t = Time.now - assert_equal t, JSON(JSON(t), :create_additions => true) - d = Date.today - assert_equal d, JSON(JSON(d), :create_additions => true) - d = DateTime.civil(2007, 6, 14, 14, 57, 10, Rational(1, 12), 2299161) - assert_equal d, JSON(JSON(d), :create_additions => true) - assert_equal 1..10, JSON(JSON(1..10), :create_additions => true) - assert_equal 1...10, JSON(JSON(1...10), :create_additions => true) - assert_equal "a".."c", JSON(JSON("a".."c"), :create_additions => true) - assert_equal "a"..."c", JSON(JSON("a"..."c"), :create_additions => true) - s = MyJsonStruct.new 4711, 'foot' - assert_equal s, JSON(JSON(s), :create_additions => true) - struct = Struct.new :foo, :bar - s = struct.new 4711, 'foot' - assert_raise(JSONError) { JSON(s) } - begin - raise TypeError, "test me" - rescue TypeError => e - e_json = JSON.generate e - e_again = JSON e_json, :create_additions => true - assert_kind_of TypeError, e_again - assert_equal e.message, e_again.message - assert_equal e.backtrace, e_again.backtrace - end - assert_equal(/foo/, JSON(JSON(/foo/), :create_additions => true)) - assert_equal(/foo/i, JSON(JSON(/foo/i), :create_additions => true)) - end - - def test_deprecated_load_create_additions - assert_deprecated_warning(/use JSON\.unsafe_load/) do - JSON.load(JSON.dump(Time.now)) - end - end - - def test_utc_datetime - now = Time.now - d = DateTime.parse(now.to_s) # usual case - assert_equal d, parse(d.to_json, :create_additions => true) - d = DateTime.parse(now.utc.to_s) # of = 0 - assert_equal d, parse(d.to_json, :create_additions => true) - d = DateTime.civil(2008, 6, 17, 11, 48, 32, Rational(1,24)) - assert_equal d, parse(d.to_json, :create_additions => true) - d = DateTime.civil(2008, 6, 17, 11, 48, 32, Rational(12,24)) - assert_equal d, parse(d.to_json, :create_additions => true) - end - - def test_rational_complex - assert_equal Rational(2, 9), parse(JSON(Rational(2, 9)), :create_additions => true) - assert_equal Complex(2, 9), parse(JSON(Complex(2, 9)), :create_additions => true) - end - - def test_bigdecimal - assert_equal BigDecimal('3.141', 23), JSON(JSON(BigDecimal('3.141', 23)), :create_additions => true) - assert_equal BigDecimal('3.141', 666), JSON(JSON(BigDecimal('3.141', 666)), :create_additions => true) - end if defined?(::BigDecimal) - - def test_ostruct - o = OpenStruct.new - # XXX this won't work; o.foo = { :bar => true } - o.foo = { 'bar' => true } - assert_equal o, parse(JSON(o), :create_additions => true) - end if defined?(::OpenStruct) - - def test_set - s = Set.new([:a, :b, :c, :a]) - assert_equal s, JSON.parse(JSON(s), :create_additions => true) - end -end diff --git a/test/json/json_coder_test.rb b/test/json/json_coder_test.rb index 986118191..b91431821 100755 --- a/test/json/json_coder_test.rb +++ b/test/json/json_coder_test.rb @@ -1,4 +1,3 @@ -#!/usr/bin/env ruby # frozen_string_literal: true require_relative 'test_helper' @@ -11,13 +10,108 @@ def test_json_coder_with_proc assert_equal %(["[Object object]"]), coder.dump([Object.new]) end + def test_json_coder_with_as_json_option + callback = ->(object, is_key) { 42 } + [callback, callback.method(:call)].each do |as_json| + coder = JSON::Coder.new(as_json: as_json) + assert_equal '[42]', coder.dump([Object.new]) + end + end + + def test_json_coder_block_overrides_as_json_option + coder = JSON::Coder.new(as_json: ->(object) { 1 }) { 2 } + assert_equal '[2]', coder.dump([Object.new]) + end + + def test_json_coder_as_json_invalid_type + assert_raise(TypeError) { JSON::Coder.new(as_json: 'x') } + end + + def test_json_coder_rejects_disabling_strict_mode + [false, nil].each do |strict| + error = assert_raise(ArgumentError) { JSON::Coder.new(strict: strict) } + assert_equal 'JSON::Coder requires strict mode', error.message + end + assert_raise(JSON::GeneratorError) { JSON::Coder.new.dump(Object.new) } + assert_raise(JSON::GeneratorError) { JSON::Coder.new(strict: true).dump(Object.new) } + end + def test_json_coder_with_proc_with_unsupported_value - coder = JSON::Coder.new do |object| + coder = JSON::Coder.new do |object, is_key| + assert_equal false, is_key Object.new end assert_raise(JSON::GeneratorError) { coder.dump([Object.new]) } end + def test_json_coder_with_proc_returning_symbol + coder = JSON::Coder.new { _1 } + assert_equal %({"sym":"sym"}), coder.dump({ sym: :sym }) + end + + def test_json_coder_hash_key + obj = Object.new + coder = JSON::Coder.new do |obj, is_key| + assert_equal true, is_key + obj.to_s + end + assert_equal %({#{obj.to_s.inspect}:1}), coder.dump({ obj => 1 }) + + coder = JSON::Coder.new { 42 } + error = assert_raise JSON::GeneratorError do + coder.dump({ obj => 1 }) + end + assert_equal "Integer not allowed as object key in JSON", error.message + end + + def test_json_coder_rfc8785_converted_keys + time = Time.utc(1999) + calls = [] + coder = JSON::Coder.new(rfc8785: true) do |object, is_key| + calls << [object, is_key] + object.year.to_s + end + input = { time => 1, '1999!' => 2, Time.utc(2000) => 3 } + assert_equal '{"1999":1,"1999!":2,"2000":3}', coder.dump(input) + assert_equal [[time, true], [Time.utc(2000), true]], calls + assert_equal [time, '1999!', Time.utc(2000)], input.keys + end + + def test_json_coder_rfc8785_converted_key_validation + object = Object.new + coder = JSON::Coder.new(rfc8785: true) { 42 } + error = assert_raise(JSON::GeneratorError) { coder.dump(object => 1) } + assert_equal 'Integer not allowed as object key in JSON', error.message + assert_equal 42, error.invalid_object + + coder = JSON::Coder.new(rfc8785: true) { :a } + assert_equal '{"a":1,"b":2}', coder.dump('b' => 2, object => 1) + + invalid = "\xFF" + calls = 0 + coder = JSON::Coder.new(rfc8785: true) { calls += 1; invalid } + error = assert_raise(JSON::GeneratorError) { coder.dump(object => 1) } + assert_same invalid, error.invalid_object + assert_equal 1, calls + end + + def test_json_coder_rfc8785_converted_key_collision + coder = JSON::Coder.new(rfc8785: true) { 'a' } + assert_raise(JSON::GeneratorError) { coder.dump(Object.new => 1, 'a' => 2) } + assert_raise(JSON::GeneratorError) { coder.dump(Object.new => 1, Object.new => 2) } + assert_raise(JSON::GeneratorError) { coder.dump(Object.new => 1, :a => 2) } + end + + def test_json_coder_rfc8785_invalid_encoding_key + calls = [] + coder = JSON::Coder.new(rfc8785: true) do |object, is_key| + calls << [object, is_key] + 'a' + end + assert_equal '{"a":1,"b":2}', coder.dump('b' => 2, "\xFF" => 1) + assert_equal [["\xFF", true]], calls + end + def test_json_coder_options coder = JSON::Coder.new(array_nl: "\n") do |object| 42 @@ -36,18 +130,135 @@ def test_json_coder_load_options assert_equal({a: 1}, coder.load('{"a":1}')) end + def test_json_coder_load_with_on_load_method + on_load = ->(value) { Integer === value ? value + 1 : value }.method(:call) + coder = JSON::Coder.new(on_load: on_load) + assert_equal [2], coder.load('[1]') + end + + def test_json_coder_on_load_invalid_type + assert_raise(TypeError) { JSON::Coder.new(on_load: 'x') } + end + def test_json_coder_dump_NaN_or_Infinity - coder = JSON::Coder.new(&:inspect) + coder = JSON::Coder.new { |o| o.inspect } assert_equal "NaN", coder.load(coder.dump(Float::NAN)) assert_equal "Infinity", coder.load(coder.dump(Float::INFINITY)) assert_equal "-Infinity", coder.load(coder.dump(-Float::INFINITY)) end def test_json_coder_dump_NaN_or_Infinity_loop - coder = JSON::Coder.new(&:itself) + coder = JSON::Coder.new { |o| o.itself } error = assert_raise JSON::GeneratorError do coder.dump(Float::NAN) end assert_include error.message, "NaN not allowed in JSON" end + + def test_json_coder_string_invalid_encoding + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + object + end + + error = assert_raise JSON::GeneratorError do + coder.dump("\xFF") + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 1, calls + + error = assert_raise JSON::GeneratorError do + coder.dump({ "\xFF" => 1 }) + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 2, calls + + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + object.dup + end + + error = assert_raise JSON::GeneratorError do + coder.dump("\xFF") + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 1, calls + + error = assert_raise JSON::GeneratorError do + coder.dump({ "\xFF" => 1 }) + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 2, calls + + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + object.bytes + end + + assert_equal "[255]", coder.dump("\xFF") + assert_equal 1, calls + + error = assert_raise JSON::GeneratorError do + coder.dump({ "\xFF" => 1 }) + end + assert_equal "Array not allowed as object key in JSON", error.message + assert_equal 2, calls + + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + [object].pack("m") + end + + assert_equal '"/w==\\n"', coder.dump("\xFF") + assert_equal 1, calls + + assert_equal '{"/w==\\n":1}', coder.dump({ "\xFF" => 1 }) + assert_equal 2, calls + end + + def test_json_coder_symbol_invalid_encoding + symbol = "\xFF".b.to_sym + + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + object.bytes + end + + assert_equal "[[255]]", coder.dump([symbol]) + assert_equal 1, calls + + coder = JSON::Coder.new { |object, is_key| nil } + assert_equal "[null]", coder.dump([symbol]) + + coder = JSON::Coder.new { |object, is_key| Object.new } + assert_raise(JSON::GeneratorError) { coder.dump([symbol]) } + + if RUBY_ENGINE == "ruby" + coder = JSON::Coder.new { |object, is_key| symbol } + error = assert_raise JSON::GeneratorError do + coder.dump([symbol]) + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + end + end + + def test_depth + coder = JSON::Coder.new(object_nl: "\n", array_nl: "\n", space: " ", indent: " ", depth: 1) + assert_equal %({\n "foo": 42\n }), coder.dump(foo: 42) + end + + def test_nesting_recovery + coder = JSON::Coder.new + ary = [] + ary << ary + assert_raise JSON::NestingError do + coder.dump(ary) + end + assert_equal '{"a":1}', coder.dump({ a: 1 }) + end end diff --git a/test/json/json_common_interface_test.rb b/test/json/json_common_interface_test.rb index 1f157da02..cf11c8f0e 100644 --- a/test/json/json_common_interface_test.rb +++ b/test/json/json_common_interface_test.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true require_relative 'test_helper' -require 'stringio' -require 'tempfile' class JSONCommonInterfaceTest < Test::Unit::TestCase include JSON @@ -42,12 +40,6 @@ def setup '"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}' end - def test_index - assert_equal @json, JSON[@hash] - assert_equal @json, JSON[@hash_with_method_missing] - assert_equal @hash, JSON[@json] - end - def test_parser assert_match(/::Parser\z/, JSON.parser.name) end @@ -60,21 +52,20 @@ def test_state assert_match(/::(TruffleRuby)?Generator::State\z/, JSON.state.name) end - def test_create_id - assert_equal 'json_class', JSON.create_id - JSON.create_id = 'foo_bar' - assert_equal 'foo_bar', JSON.create_id - ensure - JSON.create_id = 'json_class' + def test_parse + assert_equal [ 1, 2, 3, ], JSON.parse('[ 1, 2, 3 ]') end - def test_deep_const_get - assert_raise(ArgumentError) { JSON.deep_const_get('Nix::Da') } - assert_equal File::SEPARATOR, JSON.deep_const_get('File::SEPARATOR') - end + def test_parse_unknown_option + error = assert_raise(ArgumentError) do + JSON.parse('[]', quirks_mode: true) + end + assert_match "quirks_mode", error.message - def test_parse - assert_equal [ 1, 2, 3, ], JSON.parse('[ 1, 2, 3 ]') + error = assert_raise(ArgumentError) do + JSON.parse('[]', a: 1, b: 2) + end + assert_match "a, b", error.message end def test_parse_bang @@ -85,32 +76,79 @@ def test_generate assert_equal '[1,2,3]', JSON.generate([ 1, 2, 3 ]) end + def test_generate_unknown_option + error = assert_raise(ArgumentError) do + JSON.generate([], quirks_mode: true) + end + assert_match "quirks_mode", error.message + + error = assert_raise(ArgumentError) do + JSON.generate([], a: 1, b: 2) + end + assert_match(/unknown keywords: :?a, :?b/, error.message) + end + def test_fast_generate assert_equal '[1,2,3]', JSON.generate([ 1, 2, 3 ]) end def test_pretty_generate assert_equal "[\n 1,\n 2,\n 3\n]", JSON.pretty_generate([ 1, 2, 3 ]) + assert_equal <<~JSON.strip, JSON.pretty_generate({ a: { b: "f"}, c: "d"}) + { + "a": { + "b": "f" + }, + "c": "d" + } + JSON + + # Cause the state to be spilled on the heap. + o = Object.new + def o.to_s + "Object" + end + actual = JSON.pretty_generate({ a: { b: o}, c: "d", e: "f"}) + assert_equal <<~JSON.strip, actual + { + "a": { + "b": "Object" + }, + "c": "d", + "e": "f" + } + JSON end def test_load assert_equal @hash, JSON.load(@json) + assert_equal nil, JSON.load(nil) + assert_equal nil, JSON.load('') + tempfile = Tempfile.open('@json') tempfile.write @json tempfile.rewind assert_equal @hash, JSON.load(tempfile) + tempfile.close! + stringio = StringIO.new(@json) stringio.rewind assert_equal @hash, JSON.load(stringio) - assert_equal nil, JSON.load(nil) - assert_equal nil, JSON.load('') + + tempfile = Tempfile.open('@json.gzip', mode: File::BINARY) + gzw = Zlib::GzipWriter.new(tempfile) + gzw.write(@json) + gzw.close + tempfile.open + gzr = Zlib::GzipReader.new(tempfile) + assert_equal @hash, JSON.load(gzr) ensure tempfile.close! end def test_load_with_proc visited = [] - JSON.load('{"foo": [1, 2, 3], "bar": {"baz": "plop"}}', proc { |o| visited << JSON.dump(o) }) + JSON.load('{"foo": [1, 2, 3], "bar": {"baz": "plop"}}', proc { |o| visited << JSON.dump(o); o }) expected = [ '"foo"', @@ -129,37 +167,118 @@ def test_load_with_proc def test_load_with_options json = '{ "foo": NaN }' - assert JSON.load(json, nil, :allow_nan => true)['foo'].nan? + assert JSON.load(json, nil, allow_nan: true)['foo'].nan? + assert JSON.load(json, allow_nan: true)['foo'].nan? end def test_load_null - assert_equal nil, JSON.load(nil, nil, :allow_blank => true) - assert_raise(TypeError) { JSON.load(nil, nil, :allow_blank => false) } - assert_raise(JSON::ParserError) { JSON.load('', nil, :allow_blank => false) } + assert_equal nil, JSON.load(nil, nil, allow_blank: true) + assert_raise(TypeError) { JSON.load(nil, nil, allow_blank: false) } + assert_raise(JSON::ParserError) { JSON.load('', nil, allow_blank: false) } + assert_raise(TypeError) { JSON.load([], nil, allow_blank: true) } + assert_raise(TypeError) { JSON.load({}, nil, allow_blank: true) } + end + + def test_unsafe_load + string_able_klass = Class.new do + def initialize(str) + @str = str + end + + def to_str + @str + end + end + + io_able_klass = Class.new do + def initialize(str) + @str = str + end + + def to_io + StringIO.new(@str) + end + end + + assert_equal @hash, JSON.unsafe_load(@json) + tempfile = Tempfile.open('@json') + tempfile.write @json + tempfile.rewind + assert_equal @hash, JSON.unsafe_load(tempfile) + stringio = StringIO.new(@json) + stringio.rewind + assert_equal @hash, JSON.unsafe_load(stringio) + string_able = string_able_klass.new(@json) + assert_equal @hash, JSON.unsafe_load(string_able) + io_able = io_able_klass.new(@json) + assert_equal @hash, JSON.unsafe_load(io_able) + assert_equal nil, JSON.unsafe_load(nil) + assert_equal nil, JSON.unsafe_load('') + ensure + tempfile.close! + end + + def test_unsafe_load_with_proc + visited = [] + JSON.unsafe_load('{"foo": [1, 2, 3], "bar": {"baz": "plop"}}', proc { |o| visited << JSON.dump(o); o }) + + expected = [ + '"foo"', + '1', + '2', + '3', + '[1,2,3]', + '"bar"', + '"baz"', + '"plop"', + '{"baz":"plop"}', + '{"foo":[1,2,3],"bar":{"baz":"plop"}}', + ] + assert_equal expected, visited + end + + def test_unsafe_load_default_options + too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' + assert JSON.unsafe_load(too_deep, nil).is_a?(Array) + nan_json = '{ "foo": NaN }' + assert JSON.unsafe_load(nan_json, nil)['foo'].nan? + assert_equal nil, JSON.unsafe_load(nil, nil) + t = Time.new(2025, 9, 3, 14, 50, 0) + assert_equal t.to_s, JSON.unsafe_load(JSON(t)).to_s + end + + def test_unsafe_load_with_options + nan_json = '{ "foo": NaN }' + assert_raise(JSON::ParserError) { JSON.unsafe_load(nan_json, nil, allow_nan: false)['foo'].nan? } + # make sure it still uses the defaults when something is provided + assert JSON.unsafe_load(nan_json, nil, allow_blank: true)['foo'].nan? + assert JSON.unsafe_load(nan_json, allow_nan: true)['foo'].nan? + end + + def test_unsafe_load_null + assert_equal nil, JSON.unsafe_load(nil, nil, allow_blank: true) + assert_raise(TypeError) { JSON.unsafe_load(nil, nil, allow_blank: false) } + assert_raise(JSON::ParserError) { JSON.unsafe_load('', nil, allow_blank: false) } end def test_dump - too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' + too_deep = '[' * 101 + ']' * 101 obj = eval(too_deep) - assert_equal too_deep, dump(obj) - assert_kind_of String, Marshal.dump(obj) - assert_raise(ArgumentError) { dump(obj, 100) } - assert_raise(ArgumentError) { Marshal.dump(obj, 100) } - assert_equal too_deep, dump(obj, 101) - assert_kind_of String, Marshal.dump(obj, 101) - - assert_equal too_deep, JSON.dump(obj, StringIO.new, 101, strict: false).string - assert_equal too_deep, dump(obj, StringIO.new, 101, strict: false).string - assert_raise(JSON::GeneratorError) { JSON.dump(Object.new, StringIO.new, 101, strict: true).string } - assert_raise(JSON::GeneratorError) { dump(Object.new, StringIO.new, 101, strict: true).string } - - assert_equal too_deep, dump(obj, nil, nil, strict: false) - assert_equal too_deep, dump(obj, nil, 101, strict: false) - assert_equal too_deep, dump(obj, StringIO.new, nil, strict: false).string - assert_equal too_deep, dump(obj, nil, strict: false) - assert_equal too_deep, dump(obj, 101, strict: false) - assert_equal too_deep, dump(obj, StringIO.new, strict: false).string - assert_equal too_deep, dump(obj, strict: false) + assert_raise(JSON::NestingError) { dump(obj) } + assert_equal too_deep, dump(obj, max_nesting: 101) + + assert_equal too_deep, JSON.dump(obj, StringIO.new, max_nesting: 101, strict: false).string + assert_equal too_deep, dump(obj, StringIO.new, max_nesting: 101, strict: false).string + assert_raise(JSON::GeneratorError) { JSON.dump(Object.new, StringIO.new, max_nesting: 101, strict: true).string } + assert_raise(JSON::GeneratorError) { dump(Object.new, StringIO.new, max_nesting: 101, strict: true).string } + + assert_raise(JSON::NestingError) { dump(obj, nil, strict: false) } + assert_equal too_deep, dump(obj, nil, max_nesting: 101, strict: false) + assert_raise(JSON::NestingError) { dump(obj, StringIO.new, strict: false) } + assert_raise(JSON::NestingError) { dump(obj, strict: false) } + assert_equal too_deep, dump(obj, max_nesting: 101, strict: false) + assert_raise(JSON::NestingError) { dump(obj, StringIO.new, strict: false) } + assert_raise(JSON::NestingError) { dump(obj, strict: false) } end def test_dump_in_io @@ -173,18 +292,18 @@ def test_dump_in_io assert_equal JSON.dump(big_object), io.string end - def test_dump_should_modify_defaults - max_nesting = JSON.dump_default_options[:max_nesting] - dump([], StringIO.new, 10) - assert_equal max_nesting, JSON.dump_default_options[:max_nesting] - end - def test_JSON assert_equal @json, JSON(@hash) assert_equal @json, JSON(@hash_with_method_missing) assert_equal @hash, JSON(@json) end + def test_index + assert_equal @json, JSON[@hash] + assert_equal @json, JSON[@hash_with_method_missing] + assert_equal @hash, JSON[@json] + end + def test_load_file test_load_shared(:load_file) end diff --git a/test/json/json_encoding_test.rb b/test/json/json_encoding_test.rb index afffd8976..7ac06b2a7 100644 --- a/test/json/json_encoding_test.rb +++ b/test/json/json_encoding_test.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative 'test_helper' class JSONEncodingTest < Test::Unit::TestCase @@ -30,6 +31,18 @@ def test_generate assert_equal @generated, JSON.generate(@utf_16_data, ascii_only: true) end + def test_generate_shared_string + # Ref: https://github.com/ruby/json/issues/859 + s = "01234567890" + assert_equal '"234567890"', JSON.dump(s[2..-1]) + s = '01234567890123456789"a"b"c"d"e"f"g"h' + assert_equal '"\"a\"b\"c\"d\"e\"f\"g\""', JSON.dump(s[20, 15]) + s = "0123456789001234567890012345678900123456789001234567890" + assert_equal '"23456789001234567890012345678900123456789001234567890"', JSON.dump(s[2..-1]) + s = "0123456789001234567890012345678900123456789001234567890" + assert_equal '"567890012345678900123456789001234567890012345678"', JSON.dump(s[5..-3]) + end + def test_unicode assert_equal '""', ''.to_json assert_equal '"\\b"', "\b".to_json @@ -37,7 +50,7 @@ def test_unicode assert_equal '"\u001f"', 0x1f.chr.to_json assert_equal '" "', ' '.to_json assert_equal "\"#{0x7f.chr}\"", 0x7f.chr.to_json - utf8 = [ "© ≠ €! \01" ] + utf8 = ["© ≠ €! \01"] json = '["© ≠ €! \u0001"]' assert_equal json, utf8.to_json(ascii_only: false) assert_equal utf8, parse(json) @@ -78,10 +91,10 @@ def test_chars json = '"\u%04x"' % i i = i.chr assert_equal i, parse(json)[0] - if i == ?\b + if i == "\b" generated = generate(i) - assert '"\b"' == generated || '"\10"' == generated - elsif [?\n, ?\r, ?\t, ?\f].include?(i) + assert ['"\b"', '"\10"'].include?(generated) + elsif ["\n", "\r", "\t", "\f"].include?(i) assert_equal i.dump, generate(i) elsif i.chr < 0x20.chr assert_equal json, generate(i) @@ -92,4 +105,171 @@ def test_chars end assert_equal "\302\200", parse('"\u0080"') end + + def test_deeply_nested_structures + # Test for deeply nested arrays + nesting_level = 100 + deeply_nested = [] + current = deeply_nested + + (nesting_level - 1).times do + current << [] + current = current[0] + end + + json = generate(deeply_nested) + assert_equal deeply_nested, parse(json) + + # Test for deeply nested objects/hashes + deeply_nested_hash = {} + current_hash = deeply_nested_hash + + (nesting_level - 1).times do |i| + current_hash["key#{i}"] = {} + current_hash = current_hash["key#{i}"] + end + + json = generate(deeply_nested_hash) + assert_equal deeply_nested_hash, parse(json) + end + + def test_very_large_json_strings + # Create a large array with repeated elements + large_array = Array.new(10_000) { |i| "item#{i}" } + + json = generate(large_array) + parsed = parse(json) + + assert_equal large_array.size, parsed.size + assert_equal large_array.first, parsed.first + assert_equal large_array.last, parsed.last + + # Create a large hash + large_hash = {} + 10_000.times { |i| large_hash["key#{i}"] = "value#{i}" } + + json = generate(large_hash) + parsed = parse(json) + + assert_equal large_hash.size, parsed.size + assert_equal large_hash["key0"], parsed["key0"] + assert_equal large_hash["key9999"], parsed["key9999"] + end + + def test_invalid_utf8_sequences + invalid_utf8 = "\xFF\xFF" + error = assert_raise(JSON::GeneratorError) do + generate(invalid_utf8) + end + assert_match(%r{source sequence is illegal/malformed utf-8}, error.message) + end + + def test_surrogate_pair_handling + # Test valid surrogate pairs + assert_equal "\u{10000}", parse('"\ud800\udc00"') + assert_equal "\u{10FFFF}", parse('"\udbff\udfff"') + + # The existing test already checks for orphaned high surrogate + assert_raise(JSON::ParserError) { parse('"\ud800"') } + + # Test generating surrogate pairs + utf8_string = "\u{10437}" + generated = generate(utf8_string, ascii_only: true) + assert_match(/\\ud801\\udc37/, generated) + end + + def test_json_escaping_edge_cases + # Test escaping forward slashes + assert_equal "/", parse('"\/"') + + # Test escaping backslashes + assert_equal "\\", parse('"\\\\"') + + # Test escaping quotes + assert_equal '"', parse('"\\""') + + # Multiple escapes in sequence - different JSON parsers might handle escaped forward slashes differently + # Some parsers preserve the escaping, others don't + escaped_result = parse('"\\\\\\"\\/"') + assert_match(/\\"/, escaped_result) + assert_match(%r{/}, escaped_result) + + # Generate string with all special characters + special_chars = "\b\f\n\r\t\"\\" + escaped_json = generate(special_chars) + assert_equal special_chars, parse(escaped_json) + end + + def test_empty_objects_and_arrays + # Test empty objects with different encodings + assert_equal({}, parse('{}')) + assert_equal({}, parse('{}'.encode(Encoding::UTF_16BE))) + assert_equal({}, parse('{}'.encode(Encoding::UTF_16LE))) + assert_equal({}, parse('{}'.encode(Encoding::UTF_32BE))) + assert_equal({}, parse('{}'.encode(Encoding::UTF_32LE))) + + # Test empty arrays with different encodings + assert_equal([], parse('[]')) + assert_equal([], parse('[]'.encode(Encoding::UTF_16BE))) + assert_equal([], parse('[]'.encode(Encoding::UTF_16LE))) + assert_equal([], parse('[]'.encode(Encoding::UTF_32BE))) + assert_equal([], parse('[]'.encode(Encoding::UTF_32LE))) + + # Test generating empty objects and arrays + assert_equal '{}', generate({}) + assert_equal '[]', generate([]) + end + + def test_null_character_handling + # Test parsing null character + assert_equal "\u0000", parse('"\u0000"') + + # Test generating null character + string_with_null = "\u0000" + generated = generate(string_with_null) + assert_equal '"\u0000"', generated + + # Test null characters in middle of string + mixed_string = "before\u0000after" + generated = generate(mixed_string) + assert_equal mixed_string, parse(generated) + end + + def test_whitespace_handling + # Test parsing with various whitespace patterns + assert_equal({}, parse(' { } ')) + assert_equal({}, parse("{\r\n}")) + assert_equal([], parse(" [ \n ] ")) + assert_equal(["a", "b"], parse(" [ \n\"a\",\r\n \"b\"\n ] ")) + assert_equal({ "a" => "b" }, parse(" { \n\"a\" \r\n: \t\"b\"\n } ")) + + # Test with excessive whitespace + excessive_whitespace = " \n\r\t" * 10 + "{}" + " \n\r\t" * 10 + assert_equal({}, parse(excessive_whitespace)) + + # Mixed whitespace in keys and values + mixed_json = '{"a \n b":"c \r\n d"}' + assert_equal({ "a \n b" => "c \r\n d" }, parse(mixed_json)) + end + + def test_control_character_handling + # Test all control characters (U+0000 to U+001F) + (0..0x1F).each do |i| + # Skip already tested ones + next if [0x08, 0x0A, 0x0D, 0x0C, 0x09].include?(i) + + control_char = i.chr('UTF-8') + escaped_json = '"' + "\\u%04x" % i + '"' + assert_equal control_char, parse(escaped_json) + + # Check that the character is properly escaped when generating + assert_match(/\\u00[0-1][0-9a-f]/, generate(control_char)) + end + + # Test string with multiple control characters + control_str = "\u0001\u0002\u0003\u0004" + generated = generate(control_str) + assert_equal control_str, parse(generated) + assert_match(/\\u0001\\u0002\\u0003\\u0004/, generated) + end end diff --git a/test/json/json_ext_parser_test.rb b/test/json/json_ext_parser_test.rb index 8aa626257..8b10146ad 100644 --- a/test/json/json_ext_parser_test.rb +++ b/test/json/json_ext_parser_test.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative 'test_helper' class JSONExtParserTest < Test::Unit::TestCase @@ -14,16 +15,35 @@ def test_allocate end def test_error_messages - ex = assert_raise(ParserError) { parse('Infinity') } - assert_equal "unexpected token at 'Infinity'", ex.message + ex = assert_raise(ParserError) { parse('Infinity something') } + unless RUBY_PLATFORM =~ /java/ + assert_equal "unexpected token 'Infinity' at line 1 column 1", ex.message + end + + ex = assert_raise(ParserError) { parse('foo bar') } + unless RUBY_PLATFORM =~ /java/ + assert_equal "unexpected token 'foo' at line 1 column 1", ex.message + end + + ex = assert_raise(ParserError) { parse('-Infinity something') } + unless RUBY_PLATFORM =~ /java/ + assert_equal "invalid number: '-Infinity' at line 1 column 1", ex.message + end + ex = assert_raise(ParserError) { parse('NaN something') } unless RUBY_PLATFORM =~ /java/ - ex = assert_raise(ParserError) { parse('-Infinity') } - assert_equal "unexpected token at '-Infinity'", ex.message + assert_equal "unexpected token 'NaN' at line 1 column 1", ex.message end - ex = assert_raise(ParserError) { parse('NaN') } - assert_equal "unexpected token at 'NaN'", ex.message + ex = assert_raise(ParserError) { parse(' ') } + unless RUBY_PLATFORM =~ /java/ + assert_equal "unexpected end of input at line 1 column 4", ex.message + end + + ex = assert_raise(ParserError) { parse('{ ') } + unless RUBY_PLATFORM =~ /java/ + assert_equal "expected object key, got EOF at line 1 column 5", ex.message + end end if GC.respond_to?(:stress=) diff --git a/test/json/json_fixtures_test.rb b/test/json/json_fixtures_test.rb index c153ebef7..a3f114c41 100644 --- a/test/json/json_fixtures_test.rb +++ b/test/json/json_fixtures_test.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require_relative 'test_helper' class JSONFixturesTest < Test::Unit::TestCase @@ -10,6 +11,8 @@ class JSONFixturesTest < Test::Unit::TestCase source = File.read(f) define_method("test_#{name}") do assert JSON.parse(source), "Did not pass for fixture '#{File.basename(f)}': #{source.inspect}" + rescue JSON::ParserError + raise "#{File.basename(f)} parsing failure" end end diff --git a/test/json/json_float_parser_fallback_test.rb b/test/json/json_float_parser_fallback_test.rb new file mode 100644 index 000000000..97423837b --- /dev/null +++ b/test/json/json_float_parser_fallback_test.rb @@ -0,0 +1,188 @@ +# frozen_string_literal: true + +require_relative 'test_helper' + +class JSONFloatParserFallbackTest < Test::Unit::TestCase + include JSON + + # Test that numbers with more than 18 significant digits fall back to rb_cstr_to_dbl + def test_more_than_18_significant_digits + # These numbers have > 18 significant digits and should use fallback path. + # They should still parse correctly, just not via the Fast Float optimization + + # input, expected (rounded to double precision) + assert_float_parsing "1.23456789012345678901234567890" + assert_float_parsing "123456789012345678.901234567890" + assert_float_parsing "0.123456789012345678901234567890" + assert_float_parsing "9999999999999999999999999999.9" + + # Edge case: exactly 18 digits + assert_float_parsing "123456789012345678.0" + + # Many fractional digits + assert_float_parsing "0.12345678901234567890123456789" + end + + # Test decimal_class option forces fallback + def test_decimal_class_option + input = "3.141" + + # Without decimal_class: uses Ryu, returns Float + result_float = JSON.parse(input) + assert_instance_of(Float, result_float) + assert_equal(3.141, result_float) + + # With decimal_class: uses fallback, returns BigDecimal + result_bigdecimal = JSON.parse(input, decimal_class: BigDecimal) + assert_instance_of(BigDecimal, result_bigdecimal) + assert_equal(BigDecimal("3.141"), result_bigdecimal) + end if defined?(::BigDecimal) + + # Test that numbers with <= 17 digits use Ryu optimization + def test_ryu_optimization_used_for_normal_numbers + test_cases = [ + ["3.141", 3.141], + ["1.23456789012345e100", 1.23456789012345e100], + ["0.00000000000001", 1.0e-14], + ["123456789012345.67", 123456789012345.67], + ["-1.7976931348623157e+308", -1.7976931348623157e+308], + ["2.2250738585072014e-308", 2.2250738585072014e-308], + # Exactly 17 significant digits + ["12345678901234567", 12345678901234567.0], + ["1.2345678901234567", 1.2345678901234567], + ] + + test_cases.each do |input, expected| + result = JSON.parse(input) + assert_in_delta(expected, result, expected.abs * 1e-15, + "Failed to parse #{input} correctly (<=17 digits, Ryu path)") + end + end + + # Test edge cases at the boundary (17 digits) + def test_seventeen_digit_boundary + # Exactly 17 significant digits should use Ryu + input_17 = "12345678901234567.0" # Force it to be a float with .0 + result = JSON.parse(input_17) + assert_in_delta(12345678901234567.0, result, 1e-10) + + # 18 significant digits should use fallback + input_18 = "123456789012345678.0" + result = JSON.parse(input_18) + # Note: This will be rounded to double precision + assert_in_delta(123456789012345680.0, result, 1e-10) + end + + # Test that leading zeros don't count toward the 17-digit limit + def test_leading_zeros_dont_count + test_cases = [ + ["0.00012345678901234567", 0.00012345678901234567], # 17 significant digits + ["0.000000000000001234567890123456789", 1.234567890123457e-15], # >17 significant + ] + + test_cases.each do |input, expected| + result = JSON.parse(input) + assert_in_delta(expected, result, expected.abs * 1e-10, + "Failed to parse #{input} correctly") + end + end + + # Test that Ryu handles special values correctly + def test_special_double_values + test_cases = [ + ["1.7976931348623157e+308", Float::MAX], # Largest finite double + ["2.2250738585072014e-308", Float::MIN], # Smallest normalized double + ] + + test_cases.each do |input, expected| + result = JSON.parse(input) + assert_in_delta(expected, result, expected.abs * 1e-10, + "Failed to parse #{input} correctly") + end + + # Test zero separately + result_pos_zero = JSON.parse("0.0") + assert_equal(0.0, result_pos_zero) + + # Note: JSON.parse doesn't preserve -0.0 vs +0.0 distinction in standard mode + result_neg_zero = JSON.parse("-0.0") + assert_equal(0.0, result_neg_zero.abs) + end + + # Test subnormal numbers that caused precision issues before fallback was added + # These are extreme edge cases discovered by fuzzing (4 in 6 billion numbers tested) + def test_subnormal_edge_cases_round_trip + # These subnormal numbers (~1e-310) had 1 ULP rounding errors in original Ryu + # They now use rb_cstr_to_dbl fallback for exact precision + test_cases = [ + "-3.2652630314355e-310", + "3.9701623107025e-310", + "-3.6607772435415e-310", + "2.9714076801985e-310", + ] + + test_cases.each do |input| + # Parse the number + result = JSON.parse(input) + + # Should be bit-identical + assert_equal(result, JSON.parse(result.to_s), + "Subnormal #{input} failed round-trip test") + + # Should be bit-identical + assert_equal(result, JSON.parse(JSON.dump(result)), + "Subnormal #{input} failed round-trip test") + + # Verify the value is in the expected subnormal range + assert(result.abs < 2.225e-308, + "#{input} should be subnormal (< 2.225e-308)") + end + end + + # Test invalid numbers are properly rejected + def test_invalid_numbers_rejected + invalid_cases = [ + "-", + ".", + "-.", + "-.e10", + "1.2.3", + "1e", + "1e+", + ] + + invalid_cases.each do |input| + assert_raise(JSON::ParserError, "Should reject invalid number: #{input}") do + JSON.parse(input) + end + end + end + + def test_large_exponent_numbers + assert_equal Float::INFINITY, JSON.parse("1e4294967296") + assert_equal 0.0, JSON.parse("1e-4294967296") + assert_equal 0.0, JSON.parse("99999999999999999e-4294967296") + assert_equal Float::INFINITY, JSON.parse("1e4294967295") + assert_equal Float::INFINITY, JSON.parse("1e4294967297") + + assert_equal(-Float::INFINITY, JSON.parse("-1e4294967296")) + assert_equal(-0.0, JSON.parse("-1e-4294967296")) + assert_equal(-0.0, JSON.parse("-99999999999999999e-4294967296")) + assert_equal(-Float::INFINITY, JSON.parse("-1e4294967295")) + assert_equal(-Float::INFINITY, JSON.parse("-1e4294967297")) + + assert_equal(Float::INFINITY, JSON.parse("1e9223372036854775808")) + assert_equal(Float::INFINITY, JSON.parse("1e9999999999999999999")) + assert_equal(Float::INFINITY, JSON.parse("1e18446744073709551616")) + assert_equal(Float::INFINITY, JSON.parse("1e10000000000000000000")) + assert_equal(Float::INFINITY, JSON.parse("1e184467440737095516160")) + assert_equal 0.0, JSON.parse("1e-18446744073709551615") + assert_equal 0.0, JSON.parse("1e-9223372036854775809") + end + + private + + def assert_float_parsing(number) + assert_equal(Float(number), JSON.parse(number), "Failed to parse #{number} correctly") + end +end diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index c67cd3349..c4b1a8ace 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -1,4 +1,3 @@ -#!/usr/bin/env ruby # frozen_string_literal: true require_relative 'test_helper' @@ -39,14 +38,6 @@ def setup JSON end - def silence - v = $VERBOSE - $VERBOSE = nil - yield - ensure - $VERBOSE = v - end - def test_generate json = generate(@hash) assert_equal(parse(@json2), parse(json)) @@ -90,6 +81,111 @@ def test_dump_strict assert_equal '"hello"', dump(:hello, strict: true) assert_equal '"hello"', :hello.to_json(strict: true) assert_equal '"World"', "World".to_json(strict: true) + assert_equal '["hello"]', dump([:hello], strict: true) + assert_equal '{"hello":"world"}', dump({ hello: :world }, strict: true) + end + + def test_dump_deprecated_limit + io = StringIO.new + JSON.dump([1], io, 0) + assert_equal '[1]', io.string + end + + def test_dump_string_mutated_during_io_write + [{}, {ascii_only: true}, {script_safe: true}, {buffer_initial_length: 1}].each do |options| + ['a', "a\né/"].each do |pattern| + string = pattern * 100_000 + assert_dump_preserves_string_during_io_write(string, string, options) + end + end + end + + def test_dump_fragment_mutated_during_io_write + string = JSON.generate('a' * 100_000) + assert_dump_preserves_string_during_io_write(JSON::Fragment.new(string), string) + end + + def test_dump_to_json_result_mutated_during_io_write + string = JSON.generate('a' * 100_000) + object = Object.new + object.define_singleton_method(:to_json) { |*| string } + assert_dump_preserves_string_during_io_write(object, string) + end + + def assert_dump_preserves_string_during_io_write(object, string, options = {}) + expected = JSON.dump([object], options) + io = StringIO.new + mutated = false + io.define_singleton_method(:write) do |chunk| + unless mutated + string.setbyte(0, 'b'.ord) + string.replace('changed') + mutated = true + GC.start + end + super(chunk) + end + assert_same io, JSON.dump([object], io, options) + assert_equal 'changed', string + assert_equal true, expected == io.string, 'IO output must preserve the original string' + end + + def test_not_frozen + [ + [[], '[]'], + [{}, '{}'], + ["string", '"string"'], + [:sym, '"sym"'], + [1, '1'], + [1.0, '1.0'], + [true, 'true'], + [false, 'false'], + [nil, 'null'], + ].each do |(obj, exp)| + dumped = dump(obj, strict: true) + assert_equal exp, dumped + refute_predicate dumped, :frozen? + end + end + + def test_state_depth_to_json + depth = Object.new + def depth.to_json(state) + JSON::State.from_state(state).depth.to_s + end + + assert_equal "0", JSON.generate(depth) + assert_equal "[1]", JSON.generate([depth]) + assert_equal %({"depth":1}), JSON.generate(depth: depth) + assert_equal "[[2]]", JSON.generate([[depth]]) + assert_equal %([{"depth":2}]), JSON.generate([{depth: depth}]) + + state = JSON::State.new + assert_equal "0", state.generate(depth) + assert_equal "[1]", state.generate([depth]) + assert_equal %({"depth":1}), state.generate(depth: depth) + assert_equal "[[2]]", state.generate([[depth]]) + assert_equal %([{"depth":2}]), state.generate([{depth: depth}]) + end + + def test_state_depth_to_json_recursive + recur = Object.new + def recur.to_json(state = nil, *) + state = JSON::State.from_state(state) + if state.depth < 3 + state.generate([state.depth, self]) + else + state.generate([state.depth]) + end + end + + assert_raise(NestingError) { JSON.generate(recur, max_nesting: 3) } + assert_equal "[0,[1,[2,[3]]]]", JSON.generate(recur, max_nesting: 4) + + state = JSON::State.new(max_nesting: 3) + assert_raise(NestingError) { state.generate(recur) } + state.max_nesting = 4 + assert_equal "[0,[1,[2,[3]]]]", JSON.generate(recur, max_nesting: 4) end def test_generate_pretty @@ -122,8 +218,77 @@ def test_generate_pretty assert_equal '666', pretty_generate(666) end + def test_generate_pretty_custom + state = State.new(space_before: "", space: "", indent: "", object_nl: "\n\n", array_nl: "") + json = pretty_generate({1=>{}, 2=>['a','b'], 3=>4}, state) + assert_equal(<<~'JSON'.chomp, json) + { + + "1":{}, + + "2":["a","b"], + + "3":4 + + } + JSON + end + + def test_generate_sort_keys + json = generate({2=>"a", 1=>"b", 3=>"c"}, sort_keys: true) + assert_equal('{"1":"b","2":"a","3":"c"}', json) + + json = generate({2=>"a", 1=>"b", 3=>"c"}, sort_keys: false) + assert_equal('{"2":"a","1":"b","3":"c"}', json) + + json = pretty_generate({2=>"a", 1=>"b", 3=>"c"}, sort_keys: true) + assert_equal(<<~'JSON'.chomp, json) + { + "1": "b", + "2": "a", + "3": "c" + } + JSON + + json = pretty_generate({2=>"a", 1=>"b", 3=>"c"}, sort_keys: false) + assert_equal(<<~'JSON'.chomp, json) + { + "2": "a", + "1": "b", + "3": "c" + } + JSON + + json = pretty_generate({2=>"a", 1=>"b", 3=>"c"}) + assert_equal(<<~'JSON'.chomp, json) + { + "2": "a", + "1": "b", + "3": "c" + } + JSON + end + + def test_generate_sort_keys_with_proc + reverse = ->(hash) { hash.sort.reverse.to_h } + json = generate({2=>"a", 1=>"b", 3=>"c"}, sort_keys: reverse) + assert_equal('{"3":"c","2":"a","1":"b"}', json) + + by_value = ->(hash) { hash.sort_by { |_k, v| v }.to_h } + json = generate({2=>"c", 1=>"a", 3=>"b"}, sort_keys: by_value) + assert_equal('{"1":"a","3":"b","2":"c"}', json) + + state = State.new(sort_keys: reverse) + assert_same reverse, state.to_h[:sort_keys] + assert_equal('{"3":"c","2":"a","1":"b"}', state.generate({2=>"a", 1=>"b", 3=>"c"})) + + # A truthy sort_keys is normalized to the default sorting proc. + state = State.new(sort_keys: true) + assert_instance_of Proc, state.sort_keys + end + def test_generate_custom - state = State.new(:space_before => " ", :space => " ", :indent => "", :object_nl => "\n", :array_nl => "") + state = State.new(space_before: " ", space: " ", indent: "", object_nl: "\n", array_nl: "") json = generate({1=>{2=>3,4=>[5,6]}}, state) assert_equal(<<~'JSON'.chomp, json) { @@ -135,18 +300,6 @@ def test_generate_custom JSON end - def test_fast_generate - json = fast_generate(@hash) - assert_equal(parse(@json2), parse(json)) - parsed_json = parse(json) - assert_equal(@hash, parsed_json) - json = fast_generate({1=>2}) - assert_equal('{"1":2}', json) - parsed_json = parse(json) - assert_equal({"1"=>2}, parsed_json) - assert_equal '666', fast_generate(666) - end - def test_own_state state = State.new json = generate(@hash, state) @@ -164,8 +317,6 @@ def test_states json = generate({1=>2}, nil) assert_equal('{"1":2}', json) s = JSON.state.new - assert s.check_circular? - assert s[:check_circular?] h = { 1=>2 } h[3] = h assert_raise(JSON::NestingError) { generate(h) } @@ -174,8 +325,6 @@ def test_states a = [ 1, 2 ] a << a assert_raise(JSON::NestingError) { generate(a, s) } - assert s.check_circular? - assert s[:check_circular?] end def test_falsy_state @@ -199,92 +348,175 @@ def test_falsy_state ) end - def test_pretty_state - state = JSON.create_pretty_state - assert_equal({ - :allow_nan => false, - :array_nl => "\n", - :as_json => false, - :ascii_only => false, - :buffer_initial_length => 1024, - :depth => 0, - :script_safe => false, - :strict => false, - :indent => " ", - :max_nesting => 100, - :object_nl => "\n", - :space => " ", - :space_before => "", - }.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s }) - end - - def test_safe_state + def test_state_defaults state = JSON::State.new assert_equal({ - :allow_nan => false, - :array_nl => "", - :as_json => false, - :ascii_only => false, - :buffer_initial_length => 1024, - :depth => 0, - :script_safe => false, - :strict => false, - :indent => "", - :max_nesting => 100, - :object_nl => "", - :space => "", - :space_before => "", - }.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s }) - end + allow_duplicate_key: false, + allow_nan: false, + array_nl: "", + as_json: false, + ascii_only: false, + buffer_initial_length: 1024, + depth: 0, + script_safe: false, + strict: false, + indent: "", + max_nesting: 100, + object_nl: "", + space: "", + space_before: "", + sort_keys: false, + rfc8785: false, + }.sort_by { |n,| n.to_s }.to_h, state.to_h.sort_by { |n,| n.to_s }.to_h) - def test_fast_state - state = JSON.create_fast_state + state = JSON::State.new(allow_duplicate_key: true) assert_equal({ - :allow_nan => false, - :array_nl => "", - :as_json => false, - :ascii_only => false, - :buffer_initial_length => 1024, - :depth => 0, - :script_safe => false, - :strict => false, - :indent => "", - :max_nesting => 0, - :object_nl => "", - :space => "", - :space_before => "", + allow_duplicate_key: true, + allow_nan: false, + array_nl: "", + as_json: false, + ascii_only: false, + buffer_initial_length: 1024, + depth: 0, + script_safe: false, + strict: false, + indent: "", + max_nesting: 100, + object_nl: "", + space: "", + space_before: "", + sort_keys: false, + rfc8785: false, }.sort_by { |n,| n.to_s }, state.to_h.sort_by { |n,| n.to_s }) end def test_allow_nan error = assert_raise(GeneratorError) { generate([JSON::NaN]) } assert_same JSON::NaN, error.invalid_object - assert_equal '[NaN]', generate([JSON::NaN], :allow_nan => true) - assert_raise(GeneratorError) { fast_generate([JSON::NaN]) } + assert_equal '[NaN]', generate([JSON::NaN], allow_nan: true) + assert_raise(GeneratorError) { generate([JSON::NaN]) } assert_raise(GeneratorError) { pretty_generate([JSON::NaN]) } - assert_equal "[\n NaN\n]", pretty_generate([JSON::NaN], :allow_nan => true) + assert_equal "[\n NaN\n]", pretty_generate([JSON::NaN], allow_nan: true) error = assert_raise(GeneratorError) { generate([JSON::Infinity]) } assert_same JSON::Infinity, error.invalid_object - assert_equal '[Infinity]', generate([JSON::Infinity], :allow_nan => true) - assert_raise(GeneratorError) { fast_generate([JSON::Infinity]) } + assert_equal '[Infinity]', generate([JSON::Infinity], allow_nan: true) + assert_raise(GeneratorError) { generate([JSON::Infinity]) } assert_raise(GeneratorError) { pretty_generate([JSON::Infinity]) } - assert_equal "[\n Infinity\n]", pretty_generate([JSON::Infinity], :allow_nan => true) + assert_equal "[\n Infinity\n]", pretty_generate([JSON::Infinity], allow_nan: true) error = assert_raise(GeneratorError) { generate([JSON::MinusInfinity]) } assert_same JSON::MinusInfinity, error.invalid_object - assert_equal '[-Infinity]', generate([JSON::MinusInfinity], :allow_nan => true) - assert_raise(GeneratorError) { fast_generate([JSON::MinusInfinity]) } + assert_equal '[-Infinity]', generate([JSON::MinusInfinity], allow_nan: true) + assert_raise(GeneratorError) { generate([JSON::MinusInfinity]) } assert_raise(GeneratorError) { pretty_generate([JSON::MinusInfinity]) } - assert_equal "[\n -Infinity\n]", pretty_generate([JSON::MinusInfinity], :allow_nan => true) + assert_equal "[\n -Infinity\n]", pretty_generate([JSON::MinusInfinity], allow_nan: true) + end + + # An object that changes state.depth when it receives to_json(state) + def bad_to_json + obj = Object.new + def obj.to_json(state) + state.depth += 1 + "{#{state.object_nl}"\ + "#{state.indent * state.depth}\"foo\":#{state.space}1#{state.object_nl}"\ + "#{state.indent * (state.depth - 1)}}" + end + obj + end + + def test_depth_restored_bad_to_json + state = JSON::State.new + state.generate(bad_to_json) + assert_equal 0, state.depth + end + + def test_depth_restored_bad_to_json_in_Array + assert_equal <<~JSON.chomp, JSON.pretty_generate([bad_to_json] * 2) + [ + { + "foo": 1 + }, + { + "foo": 1 + } + ] + JSON + state = JSON::State.new + state.generate([bad_to_json]) + assert_equal 0, state.depth + end + + def test_depth_restored_bad_to_json_in_Hash + assert_equal <<~JSON.chomp, JSON.pretty_generate(a: bad_to_json, b: bad_to_json) + { + "a": { + "foo": 1 + }, + "b": { + "foo": 1 + } + } + JSON + state = JSON::State.new + state.generate(a: bad_to_json) + assert_equal 0, state.depth end def test_depth + pretty = { object_nl: "\n", array_nl: "\n", space: " ", indent: " " } + state = JSON.state.new(**pretty) + assert_equal %({\n "foo": 42\n}), JSON.generate({ foo: 42 }, pretty) + assert_equal %({\n "foo": 42\n}), state.generate(foo: 42) + state.depth = 1 + assert_equal %({\n "foo": 42\n }), JSON.generate({ foo: 42 }, pretty.merge(depth: 1)) + assert_equal %({\n "foo": 42\n }), state.generate(foo: 42) + end + + def test_depth_nesting_error ary = []; ary << ary assert_raise(JSON::NestingError) { generate(ary) } assert_raise(JSON::NestingError) { JSON.pretty_generate(ary) } - s = JSON.state.new - assert_equal 0, s.depth + end + + def test_nesting_error_reports_attempted_depth + [[[]], { a: {} }].each do |object| + [0, 1].each do |depth| + state = JSON.state.new(depth: depth, max_nesting: depth + 1) + error = assert_raise(JSON::NestingError) { state.generate(object) } + assert_match(/\Anesting of #{depth + 2} is too deep\./, error.message) + assert_equal depth, state.depth + assert_equal '[]', state.generate([]) + end + end + end + + def test_depth_nesting_error_to_json + ary = []; ary << ary + s = JSON.state.new(depth: 1) assert_raise(JSON::NestingError) { ary.to_json(s) } - assert_equal 100, s.depth + assert_equal 1, s.depth + end + + def test_depth_nesting_error_Hash_to_json + hash = {}; hash[:a] = hash + s = JSON.state.new(depth: 1) + assert_raise(JSON::NestingError) { hash.to_json(s) } + assert_equal 1, s.depth + end + + def test_depth_nesting_error_generate + ary = []; ary << ary + s = JSON.state.new(depth: 1) + assert_raise(JSON::NestingError) { s.generate(ary) } + assert_equal 1, s.depth + end + + def test_depth_exception_calling_to_json + def (obj = Object.new).to_json(*) + raise + end + s = JSON.state.new(depth: 1).freeze + assert_raise(RuntimeError) { s.generate([{ hash: obj }]) } + assert_equal 1, s.depth end def test_buffer_initial_length @@ -315,11 +547,11 @@ def test_gc def test_configure_using_configure_and_merge numbered_state = { - :indent => "1", - :space => '2', - :space_before => '3', - :object_nl => '4', - :array_nl => '5' + indent: "1", + space: '2', + space_before: '3', + object_nl: '4', + array_nl: '5' } state1 = JSON.state.new state1.merge(numbered_state) @@ -337,9 +569,64 @@ def test_configure_using_configure_and_merge assert_equal '5', state2.array_nl end + def test_configure_only_writes_the_string_options_it_is_given + state = JSON.state.new(indent: '1', space: '2', space_before: '3', object_nl: '4', array_nl: '5') + state.configure(space: '9') + assert_equal '1', state.indent + assert_equal '9', state.space + assert_equal '3', state.space_before + assert_equal '4', state.object_nl + assert_equal '5', state.array_nl + state.merge(array_nl: '8') + assert_equal '1', state.indent + assert_equal '9', state.space + assert_equal '3', state.space_before + assert_equal '4', state.object_nl + assert_equal '8', state.array_nl + end + + def test_configure_keeps_the_layout_of_a_pretty_state + state = JSON.state.new(indent: ' ', object_nl: "\n", array_nl: "\n") + state.configure(depth: 0) + assert_equal %({\n "foo":[\n 1\n ]\n}), state.generate({ 'foo' => [1] }) + end + + def test_configure_only_writes_the_other_options_it_is_given + state = JSON.state.new(max_nesting: 3, allow_nan: true, ascii_only: true, script_safe: true, + strict: true, buffer_initial_length: 32) + state.configure(indent: '1') + assert_equal '1', state.indent + assert_equal 3, state.max_nesting + assert_equal true, state.allow_nan? + assert_equal true, state.ascii_only? + assert_equal true, state.script_safe? + assert_equal true, state.strict? + assert_equal 32, state.buffer_initial_length + end + + def test_configure_keeps_sort_keys + state = JSON.state.new(sort_keys: true) + state.configure(depth: 0) + assert_equal '{"a":2,"b":1}', state.generate({ 'b' => 1, 'a' => 2 }) + end + + def test_configure_keeps_as_json + as_json = ->(object, _is_key) { object.to_s } + state = JSON.state.new(strict: true, as_json: as_json) + state.configure(depth: 0) + assert_equal as_json, state.as_json + end + + def test_configure_writes_a_string_option_given_as_nil + state = JSON.state.new(indent: '1', space: '2') + state.configure(indent: nil) + assert_equal '', state.indent + assert_equal '2', state.space + end + def test_configure_hash_conversion state = JSON.state.new - state.configure(:indent => '1') + state.configure(indent: '1') assert_equal '1', state.indent state = JSON.state.new foo = 'foo'.dup @@ -353,50 +640,31 @@ def foo.to_h assert_equal '2', state.indent end - def test_broken_bignum # [ruby-core:38867] - pid = fork do - x = 1 << 64 - x.class.class_eval do - def to_s - end + def test_broken_bignum # [Bug #5173] + bignum = 1 << 64 + bignum_to_s = bignum.to_s + + original_to_s = bignum.class.instance_method(:to_s) + bignum.class.class_eval do + def to_s + nil end - begin - JSON::Ext::Generator::State.new.generate(x) - exit 1 - rescue TypeError - exit 0 + alias_method :to_s, :to_s + end + case RUBY_ENGINE + when "jruby" + assert_equal bignum_to_s, JSON.generate(bignum) + when "truffleruby" + assert_raise(NoMethodError) do + JSON.generate(bignum) + end + when "ruby" + assert_raise(TypeError) do + JSON.generate(bignum) end end - _, status = Process.waitpid2(pid) - assert status.success? - rescue NotImplementedError - # forking to avoid modifying core class of a parent process and - # introducing race conditions of tests are run in parallel - end - - def test_hash_likeness_set_symbol - state = JSON.state.new - assert_equal nil, state[:foo] - assert_equal nil.class, state[:foo].class - assert_equal nil, state['foo'] - state[:foo] = :bar - assert_equal :bar, state[:foo] - assert_equal :bar, state['foo'] - state_hash = state.to_hash - assert_kind_of Hash, state_hash - assert_equal :bar, state_hash[:foo] - end - - def test_hash_likeness_set_string - state = JSON.state.new - assert_equal nil, state[:foo] - assert_equal nil, state['foo'] - state['foo'] = :bar - assert_equal :bar, state[:foo] - assert_equal :bar, state['foo'] - state_hash = state.to_hash - assert_kind_of Hash, state_hash - assert_equal :bar, state_hash[:foo] + ensure + bignum.class.define_method(:to_s, original_to_s) if original_to_s end def test_json_state_to_h_roundtrip @@ -404,31 +672,63 @@ def test_json_state_to_h_roundtrip assert_equal state.to_h, JSON.state.new(state.to_h).to_h end + def test_json_state_to_h_ignores_instance_variables + state = JSON.state.new(indent: ' ') + expected = state.to_h + state.instance_variable_set(:@custom, 42) + state.instance_variable_set(:@enabled, false) + + assert_equal expected, state.to_h + assert_equal expected, state.to_hash + end + def test_json_generate assert_raise JSON::GeneratorError do generate(["\xea"]) end end + def test_json_generate_error_detailed_message + error = assert_raise JSON::GeneratorError do + generate(["\xea"]) + end + + assert_not_nil(error.detailed_message) + end + def test_json_generate_unsupported_types assert_raise JSON::GeneratorError do generate(Object.new, strict: true) end + + assert_raise JSON::GeneratorError do + generate([Object.new], strict: true) + end + + assert_raise JSON::GeneratorError do + generate({ "key" => Object.new }, strict: true) + end + + assert_raise JSON::GeneratorError do + generate({ Object.new => "value" }, strict: true) + end end def test_nesting too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' too_deep_ary = eval too_deep assert_raise(JSON::NestingError) { generate too_deep_ary } - assert_raise(JSON::NestingError) { generate too_deep_ary, :max_nesting => 100 } - ok = generate too_deep_ary, :max_nesting => 101 + assert_raise(JSON::NestingError) { generate too_deep_ary, max_nesting: 100 } + ok = generate too_deep_ary, max_nesting: 101 assert_equal too_deep, ok - ok = generate too_deep_ary, :max_nesting => nil + ok = generate too_deep_ary, max_nesting: nil assert_equal too_deep, ok - ok = generate too_deep_ary, :max_nesting => false + ok = generate too_deep_ary, max_nesting: false assert_equal too_deep, ok - ok = generate too_deep_ary, :max_nesting => 0 + ok = generate too_deep_ary, max_nesting: 0 assert_equal too_deep, ok + + assert_raise(TypeError) { generate too_deep_ary, max_nesting: "garbage" } end def test_backslash @@ -436,34 +736,82 @@ def test_backslash json = '["\\\\.(?i:gif|jpe?g|png)$"]' assert_equal json, generate(data) # - data = [ '\\"' ] - json = '["\\\\\""]' + data = [ '\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$\\.(?i:gif|jpe?g|png)$' ] + json = '["\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$\\\\.(?i:gif|jpe?g|png)$"]' + assert_equal json, generate(data) + # + data = [ '\\"\\"\\"\\"\\"\\"\\"\\"\\"\\"\\"' ] + json = '["\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\"\\\\\""]' assert_equal json, generate(data) # data = [ '/' ] json = '["/"]' assert_equal json, generate(data) # + data = [ '////////////////////////////////////////////////////////////////////////////////////' ] + json = '["////////////////////////////////////////////////////////////////////////////////////"]' + assert_equal json, generate(data) + # data = [ '/' ] json = '["\/"]' - assert_equal json, generate(data, :script_safe => true) + assert_equal json, generate(data, script_safe: true) + # + data = [ '///////////' ] + json = '["\/\/\/\/\/\/\/\/\/\/\/"]' + assert_equal json, generate(data, script_safe: true) + # + data = [ '///////////////////////////////////////////////////////' ] + json = '["\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/"]' + assert_equal json, generate(data, script_safe: true) # data = [ "\u2028\u2029" ] json = '["\u2028\u2029"]' - assert_equal json, generate(data, :script_safe => true) + assert_equal json, generate(data, script_safe: true) # data = [ "ABC \u2028 DEF \u2029 GHI" ] json = '["ABC \u2028 DEF \u2029 GHI"]' - assert_equal json, generate(data, :script_safe => true) + assert_equal json, generate(data, script_safe: true) # data = [ "/\u2028\u2029" ] json = '["\/\u2028\u2029"]' - assert_equal json, generate(data, :escape_slash => true) + assert_equal json, generate(data, script_safe: true) # data = ['"'] json = '["\""]' assert_equal json, generate(data) # + data = ['"""""""""""""""""""""""""'] + json = '["\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\""]' + assert_equal json, generate(data) + # + data = '"""""' + json = '"\"\"\"\"\""' + assert_equal json, generate(data) + # + data = "abc\n" + json = '"abc\\n"' + assert_equal json, generate(data) + # + data = "\nabc" + json = '"\\nabc"' + assert_equal json, generate(data) + # + data = "\n" + json = '"\\n"' + assert_equal json, generate(data) + # + (0..16).each do |i| + data = ('a' * i) + "\n" + json = '"' + ('a' * i) + '\\n"' + assert_equal json, generate(data) + end + # + (0..16).each do |i| + data = "\n" + ('a' * i) + json = '"' + '\\n' + ('a' * i) + '"' + assert_equal json, generate(data) + end + # data = ["'"] json = '["\\\'"]' assert_equal '["\'"]', generate(data) @@ -471,6 +819,72 @@ def test_backslash data = ["倩", "瀨"] json = '["倩","瀨"]' assert_equal json, generate(data, script_safe: true) + # + data = '["This is a "test" of the emergency broadcast system."]' + json = "\"[\\\"This is a \\\"test\\\" of the emergency broadcast system.\\\"]\"" + assert_equal json, generate(data) + # + data = '\tThis is a test of the emergency broadcast system.' + json = "\"\\\\tThis is a test of the emergency broadcast system.\"" + assert_equal json, generate(data) + # + data = 'This\tis a test of the emergency broadcast system.' + json = "\"This\\\\tis a test of the emergency broadcast system.\"" + assert_equal json, generate(data) + # + data = 'This is\ta test of the emergency broadcast system.' + json = "\"This is\\\\ta test of the emergency broadcast system.\"" + assert_equal json, generate(data) + # + data = 'This is a test of the emergency broadcast\tsystem.' + json = "\"This is a test of the emergency broadcast\\\\tsystem.\"" + assert_equal json, generate(data) + # + data = 'This is a test of the emergency broadcast\tsystem.\n' + json = "\"This is a test of the emergency broadcast\\\\tsystem.\\\\n\"" + assert_equal json, generate(data) + data = '"' * 15 + json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\"" + assert_equal json, generate(data) + data = "\"\"\"\"\"\"\"\"\"\"\"\"\"\"a" + json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"a\"" + assert_equal json, generate(data) + data = "\u0001\u0001\u0001\u0001" + json = "\"\\u0001\\u0001\\u0001\\u0001\"" + assert_equal json, generate(data) + data = "\u0001a\u0001a\u0001a\u0001a" + json = "\"\\u0001a\\u0001a\\u0001a\\u0001a\"" + assert_equal json, generate(data) + data = "\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\"" + assert_equal json, generate(data) + data = "\u0001aa\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\\u0001aa\"" + assert_equal json, generate(data) + data = "\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\"" + assert_equal json, generate(data) + data = "\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002" + json = "\"\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\"" + assert_equal json, generate(data) + data = "ab\u0002c" + json = "\"ab\\u0002c\"" + assert_equal json, generate(data) + data = "ab\u0002cab\u0002cab\u0002cab\u0002c" + json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002c\"" + assert_equal json, generate(data) + data = "ab\u0002cab\u0002cab\u0002cab\u0002cab\u0002cab\u0002c" + json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002c\"" + assert_equal json, generate(data) + data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f" + json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\"" + assert_equal json, generate(data) + data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f\b" + json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\"" + assert_equal json, generate(data) + data = "a\n\t\f\b\n\t\f\b\n\t\f\b\n\t" + json = "\"a\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\"" + assert_equal json, generate(data) end def test_string_subclass @@ -631,45 +1045,39 @@ def test_string_subclass_with_to_s assert_equal '{"JSONGeneratorTest::StringWithToS#to_s":1}', JSON.generate(StringWithToS.new => 1) end - if defined?(JSON::Ext::Generator) and RUBY_PLATFORM != "java" - def test_valid_utf8_in_different_encoding - utf8_string = "€™" - wrong_encoding_string = utf8_string.b - # This behavior is historical. Not necessary desirable. We should deprecated it. - # The pure and java version of the gem already don't behave this way. - assert_warning(/UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0/) do - assert_equal utf8_string.to_json, wrong_encoding_string.to_json - end - - assert_warning(/UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0/) do - assert_equal JSON.dump(utf8_string), JSON.dump(wrong_encoding_string) + def test_string_subclass_with_broken_to_s + klass = Class.new(String) do + def to_s + false end end + s = klass.new("test") + assert_equal '["test"]', JSON.generate([s]) - def test_string_ext_included_calls_super - included = false - - Module.send(:alias_method, :included_orig, :included) - Module.send(:remove_method, :included) - Module.send(:define_method, :included) do |base| - included_orig(base) - included = true - end - - Class.new(String) do - include JSON::Ext::Generator::GeneratorMethods::String - end + assert_raise TypeError do + JSON.generate(s => 1) + end + end - assert included - ensure - if Module.private_method_defined?(:included_orig) - Module.send(:remove_method, :included) if Module.method_defined?(:included) - Module.send(:alias_method, :included, :included_orig) - Module.send(:remove_method, :included_orig) - end + def test_valid_utf8_in_binary_encoding + string = "€™".b.freeze + assert_raise(JSON::GeneratorError) { string.to_json } + assert_raise(JSON::GeneratorError) { JSON.dump(string) } + [string, [string], { string => 1 }, { value: string }].each do |object| + error = assert_raise(JSON::GeneratorError) { JSON.generate(object) } + assert_same string, error.invalid_object + assert_kind_of Encoding::UndefinedConversionError, error.cause end end + def test_ascii_in_binary_encoding + string = "ascii".b + assert_equal '"ascii"', string.to_json + assert_equal '"ascii"', JSON.dump(string) + assert_equal '["ascii"]', JSON.generate([string]) + assert_equal '{"ascii":1}', JSON.generate(string => 1) + end + def test_nonutf8_encoding assert_equal("\"5\u{b0}\"", "5\xb0".dup.force_encoding(Encoding::ISO_8859_1).to_json) end @@ -688,6 +1096,436 @@ def test_fragment def test_json_generate_as_json_convert_to_proc object = Object.new - assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: :object_id) + assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: -> (o, is_key) { o.object_id }) + end + + def test_json_generate_as_json_method + object = Object.new + as_json = -> (o, is_key) { o.object_id }.method(:call) + assert_equal object.object_id.to_json, JSON.generate(object, strict: true, as_json: as_json) + end + + def test_state_as_json_method + object = Object.new + state = JSON.state.new(strict: true) + state.as_json = -> (o, is_key) { o.object_id }.method(:call) + assert_kind_of Proc, state.as_json + assert_equal object.object_id.to_json, state.generate(object) + end + + def test_json_generate_as_json_invalid_type + [Object.new, Time.now].each do |as_json| + assert_raise(TypeError) { JSON.generate(Object.new, strict: true, as_json: as_json) } + end + end + + def test_state_as_json_invalid_type + state = JSON.state.new(strict: true) + [Object.new, Time.now].each do |as_json| + assert_raise(TypeError) { state.as_json = as_json } + assert_raise(TypeError) { state.configure(as_json: as_json) } + end + end + + def test_as_json_to_proc_returns_invalid_type + as_json = Object.new + def as_json.to_proc + method(:to_proc) + end + assert_raise(TypeError) { JSON.generate(Object.new, strict: true, as_json: as_json) } + assert_raise(TypeError) { JSON.state.new.as_json = as_json } + end + + def test_as_json_nan_does_not_call_to_json + def (obj = Object.new).to_json(*) + "null" + end + assert_raise(JSON::GeneratorError) do + JSON.generate(Float::NAN, strict: true, as_json: proc { obj }) + end + end + + def assert_float_roundtrip(expected, actual) + assert_equal(expected, JSON.generate(actual)) + assert_equal(actual, JSON.parse(JSON.generate(actual)), "JSON: #{JSON.generate(actual)}") + end + + def test_json_generate_float + assert_float_roundtrip "-1.0", -1.0 + assert_float_roundtrip "1.0", 1.0 + assert_float_roundtrip "0.0", 0.0 + assert_float_roundtrip "12.2", 12.2 + assert_float_roundtrip "2.34375", 7.5 / 3.2 + assert_float_roundtrip "12.0", 12.0 + assert_float_roundtrip "100.0", 100.0 + assert_float_roundtrip "1000.0", 1000.0 + + if RUBY_ENGINE == "jruby" + assert_float_roundtrip "1.7468619377842371E9", 1746861937.7842371 + else + assert_float_roundtrip "1746861937.7842371", 1746861937.7842371 + end + + if RUBY_ENGINE == "ruby" + assert_float_roundtrip "100000000000000.0", 100000000000000.0 + assert_float_roundtrip "1e+15", 1e+15 + assert_float_roundtrip "-100000000000000.0", -100000000000000.0 + assert_float_roundtrip "-1e+15", -1e+15 + assert_float_roundtrip "1111111111111111.1", 1111111111111111.1 + assert_float_roundtrip "1.1111111111111112e+16", 11111111111111111.1 + assert_float_roundtrip "-1111111111111111.1", -1111111111111111.1 + assert_float_roundtrip "-1.1111111111111112e+16", -11111111111111111.1 + + assert_float_roundtrip "-0.000000022471348024634545", -2.2471348024634545e-08 + assert_float_roundtrip "-0.0000000022471348024634545", -2.2471348024634545e-09 + assert_float_roundtrip "-2.2471348024634546e-10", -2.2471348024634545e-10 + end + end + + def test_numbers_of_various_sizes + numbers = [ + 0, 1, -1, 9, -9, 13, -13, 91, -91, 513, -513, 7513, -7513, + 17591, -17591, -4611686018427387904, 4611686018427387903, + 2**62, 2**63, 2**64, -(2**62), -(2**63), -(2**64) + ] + + numbers.each do |number| + assert_equal "[#{number}]", JSON.generate([number]) + end + end + + def test_generate_duplicate_keys_allowed + hash = { foo: 1, "foo" => 2 } + assert_equal %({"foo":1,"foo":2}), JSON.generate(hash, allow_duplicate_key: true) + end + + def test_generate_duplicate_keys_disallowed + assert_duplicate_key({ foo: 1, "foo" => 2 }) + assert_duplicate_key({ "1" => 1, 1 => 2 }) + assert_duplicate_key({ :"1" => 1, 1 => 2 }) + + fake_string = Class.new do + def initialize(str) + @str = str + end + + def to_s + @str.dup + end + end + + assert_duplicate_key({ fake_string.new("a") => 1, fake_string.new("a") => 2 }) + end + + def assert_duplicate_key(hash) + error = assert_raise JSON::GeneratorError do + JSON.generate(hash, allow_duplicate_key: false) + end + assert_equal %(detected duplicate key "#{hash.keys.first}" in #{hash.inspect}), error.message + end + + def test_generate_mixed_keys_without_duplicates + assert_equal %({"1":1,"2":2}), JSON.generate({ "1" => 1, 2 => 2 }, allow_duplicate_key: false) + end + + def test_frozen + state = JSON::State.new.freeze + assert_raise(FrozenError) do + state.configure(max_nesting: 1) + end + setters = state.methods.grep(/\w=$/) + assert_not_empty setters + setters.each do |setter| + assert_raise(FrozenError) do + state.send(setter, 1) + end + end + end + + def test_adversary_encode + fake_string = Class.new(String) do + def to_s + self + end + + def encode(*) + 42 + end + end + + assert_raise TypeError, JSON::GeneratorError do + JSON.generate({fake_string.new("\xff".b) => 1}) + end + + no_to_json = Class.new do + undef_method :to_json + + def initialize(str) + @str = str + end + + def to_s + @str + end + end + + assert_raise TypeError, JSON::GeneratorError do + JSON.generate([no_to_json.new(fake_string.new("\xff".b))]) + end + end + + # The case when the State is frozen is tested in JSONCoderTest#test_nesting_recovery + def test_nesting_recovery + state = JSON::State.new + ary = [] + ary << ary + assert_raise(JSON::NestingError) { state.generate(ary) } + assert_equal 0, state.depth + assert_equal '{"a":1}', state.generate({ a: 1 }) + end + + def test_negative_depth_raises + assert_raise(ArgumentError) do + JSON.generate({"a" => 1}, depth: -1) + end + assert_raise(ArgumentError) do + JSON.state.new(depth: -1) + end + end + + def test_large_depth_raises + assert_raise(RangeError, ArgumentError) do + JSON.generate([[1]], + indent: " " * 5, + array_nl: "\n", + depth: 3_689_348_814_741_910_324, + max_nesting: 0 + ) + end + end + + def test_rfc8785_key_order + assert_rfc8785 '{"a":1,"a b":2}', { 'a' => 1, 'a b' => 2 } + assert_rfc8785 '{"a":2,"a!":1}', { 'a!' => 1, 'a' => 2 } + assert_rfc8785 '{"\n":1,"A":2}', { "\n" => 1, 'A' => 2 } + assert_rfc8785 '[{"a":1,"a b":2}]', [{ 'a b' => 2, 'a' => 1 }] + end + + def test_rfc8785_mixed_key_order + assert_rfc8785 '{"10":2,"9":1}', { '9' => 1, 10 => 2 } + assert_rfc8785 '{"a":1,"b":2}', { a: 1, 'b' => 2 } + end + + def test_rfc8785_key_order_with_as_json + key = Object.new + def key.to_s + raise 'must not sort the original key' + end + as_json = ->(object, is_key) { is_key ? 'a' : object } + options = { rfc8785: true, strict: true, as_json: as_json } + object = [{ 'b' => 2, key => 1 }] + expected = '[{"a":1,"b":2}]' + assert_equal expected, JSON.generate(object, options) + assert_equal expected, object.to_json(options) + state = JSON::State.new(options) + io = StringIO.new + state.generate(object, io) + assert_equal expected, io.string + + assert_equal '{"1":1,"b":2}', JSON.generate({ 'b' => 2, 1 => 1 }, options.merge(strict: false)) + end + + def test_rfc8785_key_order_with_default_external_encoding + # https://www.rfc-editor.org/rfc/rfc8785.html#section-3.2.3 + hash = { "\u20ac" => 1, "\r" => 2, "\ufb33" => 3, '1' => 4, + "\u{1f600}" => 5, "\u0080" => 6, "\u00f6" => 7 } + expected = "{\"\\r\":2,\"1\":4,\"\u0080\":6,\"\u00f6\":7,\"\u20ac\":1,\"\u{1f600}\":5,\"\ufb33\":3}" + verbose = $VERBOSE + previous_encoding = Encoding.default_external + $VERBOSE = nil + outputs = [Encoding::UTF_8, Encoding::US_ASCII].map do |encoding| + Encoding.default_external = encoding + JSON.generate(hash, rfc8785: true) + end + assert_equal [expected, expected], outputs + ensure + Encoding.default_external = previous_encoding + $VERBOSE = verbose + end + + { + 0 => '0', + 1 => '1', + 2**53 - 1 => '9007199254740991', + 2**53 => '9007199254740992', + 2**53 + 1 => '9007199254740992', + 2**53 + 3 => '9007199254740996', + 2**63 - 1 => '9223372036854776000', + 2**68 => '295147905179352830000', + 10**20 => '100000000000000000000', + 10**21 => '1e+21', + Float::MAX.to_i => '1.7976931348623157e+308', + }.each do |integer, expected| + define_method("test_rfc8785_integer_#{integer}") do + [1, -1].each do |sign| + number = sign * integer + json = sign < 0 && integer != 0 ? "-#{expected}" : expected + assert_rfc8785 json, number + assert_rfc8785 %({"n":[#{json}]}), { 'n' => [number] } + assert_equal json, number.to_json(rfc8785: true) + assert_equal json, JSON.generate(number, rfc8785: true, max_nesting: false) + assert_equal json, JSON::Coder.new(rfc8785: true).dump(number) + io = StringIO.new + JSON.dump(number, io, rfc8785: true) + assert_equal json, io.string + assert_equal number.to_s, JSON.generate(number) + end + end + end + + def test_rfc8785_rejects_out_of_range_integers + [Float::MAX.to_i + 1, 10**400].each do |integer| + [integer, -integer].each do |number| + error = assert_raise(JSON::GeneratorError) { JSON.generate(number, rfc8785: true) } + assert_same number, error.invalid_object + assert_equal 'Integer out of range for RFC 8785', error.message + assert_raise(JSON::GeneratorError) { JSON.dump([number], rfc8785: true) } + assert_equal number.to_s, JSON.generate(number) + end + end + end + + { + indent: ' ', space: ' ', space_before: ' ', object_nl: "\n", array_nl: "\n", + ascii_only: true, script_safe: true, allow_nan: true, + }.each do |option, value| + define_method("test_rfc8785_rejects_#{option}") do + options = { rfc8785: true, option => value } + error = assert_raise(ArgumentError) { JSON.generate({ 'a' => [1] }, options) } + assert_equal "#{option} cannot be used with rfc8785", error.message + assert_raise(ArgumentError) { JSON.generate(nil, options.to_a.reverse.to_h) } + assert_raise(ArgumentError) { [1].to_json(options) } + assert_raise(ArgumentError) { { 'a' => 1 }.to_json(options) } + assert_raise(ArgumentError) { JSON::Coder.new(**options).dump([1]) } + + state = JSON::State.new(option => value) + state.rfc8785 = true + io = StringIO.new + assert_raise(ArgumentError) { state.generate([1], io) } + assert_equal '', io.string + assert_raise(ArgumentError) { [1].to_json(state) } + assert_equal 0, state.depth + assert_raise(ArgumentError) { state.freeze.generate([1]) } + end + end + + def test_rfc8785_accepts_disabled_options + options = { rfc8785: true, indent: '', space: '', space_before: '', + object_nl: '', array_nl: '', ascii_only: false, + script_safe: false, allow_nan: false } + assert_equal '["é/",1]', JSON.generate(['é/', 1], options) + end + + def test_rfc8785_rejects_pretty_generation + assert_raise(ArgumentError) { JSON.pretty_generate({ 'a' => 1 }, rfc8785: true) } + end + + def test_rfc8785_dump_disables_allow_nan_by_default + assert_equal '[1]', JSON.dump([1], rfc8785: true) + [Float::NAN, Float::INFINITY, -Float::INFINITY].each do |number| + assert_raise(JSON::GeneratorError) { JSON.dump([number], rfc8785: true) } + end + assert_raise(ArgumentError) { JSON.dump([1], rfc8785: true, allow_nan: true) } + end + + def test_rfc8785_fragments + fragment = JSON::Fragment.new('[1,2]') + assert_rfc8785 '[1,2]', fragment + assert_rfc8785 '[[1,2]]', [fragment] + assert_rfc8785 '{"a":[1,2]}', { 'a' => fragment } + assert_equal '[[1,2]]', JSON.generate([fragment], rfc8785: true, max_nesting: false) + assert_equal '[1,2]', JSON::Coder.new(rfc8785: true).dump(fragment) + assert_equal '[1,2]', fragment.to_json(rfc8785: true) + + # Fragment contents are the caller's responsibility and are inserted as is. + assert_rfc8785 '[1, 2]', JSON::Fragment.new('[1, 2]') + end + + def test_rfc8785_numbers + assert_rfc8785 '-9007199254740992', -9007199254740992 + assert_rfc8785 '0', 0 + assert_rfc8785 '0.000001', 0.000001 + assert_rfc8785 '1e+21', 1e+21 + assert_rfc8785 '9.999999999999997e+22', 9.999999999999997e+22 + assert_rfc8785 '9.999999999999997e-7', 9.999999999999997e-7 + assert_rfc8785 '9007199254740992', 9007199254740992 + assert_rfc8785 '9007199254740994', 9007199254740994 + assert_rfc8785 '9007199254740996', 9007199254740996 + assert_rfc8785 '999999999999999700000', 999999999999999700000 + assert_rfc8785 '999999999999999900000', 999999999999999900000 + assert_rfc8785 '333333333.3333333', 333333333.33333329 + assert_rfc8785 '0.30000000000000004', 0.1 + 0.2 + end + + # RFC 8785 numbers are formatted with Float#to_s, which only gives the + # shortest round-trip digits on JRuby 10.1.3 and later. + SHORTEST_FLOAT_TO_S = RUBY_ENGINE == "ruby" || + (RUBY_ENGINE == "jruby" && Gem::Version.new(JRUBY_VERSION) >= Gem::Version.new("10.1.3")) + + # https://www.rfc-editor.org/rfc/rfc8785.html#appendix-B + { + '0000000000000000' => '0', + '8000000000000000' => '0', + '0000000000000001' => '5e-324', + '8000000000000001' => '-5e-324', + '7fefffffffffffff' => '1.7976931348623157e+308', + 'ffefffffffffffff' => '-1.7976931348623157e+308', + '4340000000000000' => '9007199254740992', + 'c340000000000000' => '-9007199254740992', + '4430000000000000' => '295147905179352830000', + '7fffffffffffffff' => nil, + '7ff0000000000000' => nil, + '44b52d02c7e14af5' => '9.999999999999997e+22', + '44b52d02c7e14af6' => '1e+23', + '44b52d02c7e14af7' => '1.0000000000000001e+23', + '444b1ae4d6e2ef4e' => '999999999999999700000', + '444b1ae4d6e2ef4f' => '999999999999999900000', + '444b1ae4d6e2ef50' => '1e+21', + '3eb0c6f7a0b5ed8c' => '9.999999999999997e-7', + '3eb0c6f7a0b5ed8d' => '0.000001', + '41b3de4355555553' => '333333333.3333332', + '41b3de4355555554' => '333333333.33333325', + '41b3de4355555555' => '333333333.3333333', + '41b3de4355555556' => '333333333.3333334', + '41b3de4355555557' => '333333333.33333343', + 'becbf647612f3696' => '-0.0000033333333333333333', + '43143ff3c1cb0959' => '1424953923781206.2', + }.each do |bits, expected| + define_method("test_rfc8785_number_#{bits}") do + omit "Float#to_s is not always shortest on this engine" unless SHORTEST_FLOAT_TO_S + number = [bits].pack('H*').unpack('G').first + if expected + assert_rfc8785 expected, number + assert_equal number, JSON.parse(JSON.generate(number, rfc8785: true)).to_f + else + assert_raise(JSON::GeneratorError) { JSON.generate(number, rfc8785: true) } + end + end + end + + fixtures_path = File.expand_path('../fixtures/rfc8785/', __FILE__) + Dir[File.join(fixtures_path, "input/*.json")].each do |input| + filename = File.basename(input) + name, _ = File.basename(filename, ".json") + expected = File.join(fixtures_path, 'output', filename) + define_method("test_rfc8785_#{name}") do + assert_rfc8785(File.read(expected, encoding: Encoding::UTF_8), JSON.load_file(input)) + end + end + + private + + def assert_rfc8785(expected, value) + assert_equal(expected, JSON.generate(value, rfc8785: true)) end end diff --git a/test/json/json_generic_object_test.rb b/test/json/json_generic_object_test.rb deleted file mode 100644 index 471534192..000000000 --- a/test/json/json_generic_object_test.rb +++ /dev/null @@ -1,85 +0,0 @@ -# frozen_string_literal: true -require_relative 'test_helper' - -class JSONGenericObjectTest < Test::Unit::TestCase - - def setup - if defined?(GenericObject) - @go = JSON::GenericObject[ :a => 1, :b => 2 ] - else - omit("JSON::GenericObject is not available") - end - end - - def test_attributes - assert_equal 1, @go.a - assert_equal 1, @go[:a] - assert_equal 2, @go.b - assert_equal 2, @go[:b] - assert_nil @go.c - assert_nil @go[:c] - end - - def test_generate_json - switch_json_creatable do - assert_equal @go, JSON(JSON(@go), :create_additions => true) - end - end - - def test_parse_json - assert_kind_of Hash, - JSON( - '{ "json_class": "JSON::GenericObject", "a": 1, "b": 2 }', - :create_additions => true - ) - switch_json_creatable do - assert_equal @go, l = - JSON( - '{ "json_class": "JSON::GenericObject", "a": 1, "b": 2 }', - :create_additions => true - ) - assert_equal 1, l.a - assert_equal @go, - l = JSON('{ "a": 1, "b": 2 }', :object_class => GenericObject) - assert_equal 1, l.a - assert_equal GenericObject[:a => GenericObject[:b => 2]], - l = JSON('{ "a": { "b": 2 } }', :object_class => GenericObject) - assert_equal 2, l.a.b - end - end - - def test_from_hash - result = JSON::GenericObject.from_hash( - :foo => { :bar => { :baz => true }, :quux => [ { :foobar => true } ] }) - assert_kind_of GenericObject, result.foo - assert_kind_of GenericObject, result.foo.bar - assert_equal true, result.foo.bar.baz - assert_kind_of GenericObject, result.foo.quux.first - assert_equal true, result.foo.quux.first.foobar - assert_equal true, GenericObject.from_hash(true) - end - - def test_json_generic_object_load - empty = JSON::GenericObject.load(nil) - assert_kind_of JSON::GenericObject, empty - simple_json = '{"json_class":"JSON::GenericObject","hello":"world"}' - simple = JSON::GenericObject.load(simple_json) - assert_kind_of JSON::GenericObject, simple - assert_equal "world", simple.hello - converting = JSON::GenericObject.load('{ "hello": "world" }') - assert_kind_of JSON::GenericObject, converting - assert_equal "world", converting.hello - - json = JSON::GenericObject.dump(JSON::GenericObject[:hello => 'world']) - assert_equal JSON(json), JSON('{"json_class":"JSON::GenericObject","hello":"world"}') - end - - private - - def switch_json_creatable - JSON::GenericObject.json_creatable = true - yield - ensure - JSON::GenericObject.json_creatable = false - end -end diff --git a/test/json/json_minefield_parser_test.rb b/test/json/json_minefield_parser_test.rb new file mode 100644 index 000000000..a447b8d31 --- /dev/null +++ b/test/json/json_minefield_parser_test.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require_relative 'test_helper' + +class JSONMinefieldParserTest < Test::Unit::TestCase + # Test fixtures from https://github.com/nst/JSONTestSuite + # https://seriot.ch/security/parsing_json.html + + fixtures = Dir[File.join(File.expand_path("../fixtures/minefield", __FILE__), "*.json")] + + class << self + private + + def define_test(name, &block) + define_method("test_#{name}", &block) + end + end + + INVALID_ENCODING_TESTS = %w( + i_string_truncated-utf-8 + i_string_overlong_sequence_6_bytes_null + i_string_overlong_sequence_6_bytes + i_string_overlong_sequence_2_bytes + i_string_not_in_unicode_range + i_string_lone_utf8_continuation_byte + i_string_iso_latin_1 + i_string_invalid_utf-8 + i_string_UTF-8_invalid_sequence + ) + + COMMENT_TESTS = %w( + n_object_trailing_comment + n_object_trailing_comment_slash_open + n_structure_object_with_comment + ).freeze + + DUPLICATE_KEY_TESTS = %w( + y_object_duplicated_key + y_object_duplicated_key_and_value + ).freeze + + # Tests starting with `i_` aren't defined by the spec, we can chose to + # change our behavior, but ideally we'd be consistent across the Java and C + # parsers. + UNDEFINED_FAILING = %w( + i_string_1st_surrogate_but_2nd_missing + i_string_1st_valid_surrogate_2nd_invalid + i_string_UTF-16LE_with_BOM + i_string_incomplete_surrogate_and_escape_valid + i_string_incomplete_surrogates_escape_valid + i_string_invalid_lonely_surrogate + i_string_invalid_surrogate + i_string_inverted_surrogates_U+1D11E + i_string_utf16BE_no_BOM + i_string_utf16LE_no_BOM + i_structure_UTF-8_BOM_empty_object + i_string_lone_second_surrogate + i_string_incomplete_surrogate_pair + i_object_key_lone_2nd_surrogate + ) + + if RUBY_ENGINE == 'jruby' + # The Java parser validate the document encoding + # but the C parser doesn't. + # See: https://github.com/ruby/json/issues/138 + UNDEFINED_FAILING.concat(INVALID_ENCODING_TESTS) + end + + fixtures.each do |path| + payload = File.binread(path) + name = File.basename(path, '.json') + + if COMMENT_TESTS.include?(name) + define_test(name) do + JSON.parse(payload, allow_comments: true) + + assert_raise(JSON::ParserError) do + JSON.parse(payload, allow_comments: false) + end + end + elsif DUPLICATE_KEY_TESTS.include?(name) + define_test(name) do + JSON.parse(payload, allow_duplicate_key: true) + assert_raise(JSON::ParserError) do + JSON.parse(payload, allow_duplicate_key: false) + end + end + elsif name.start_with?("y_") || (name.start_with?("i_") && !UNDEFINED_FAILING.include?(name)) + define_test(name) do + JSON.parse(payload, max_nesting: false, allow_duplicate_key: false, allow_comments: false) + assert true + end + elsif name.start_with?("n_") || (name.start_with?("i_") && UNDEFINED_FAILING.include?(name)) + define_test(name) do + assert_raise(JSON::ParserError) do + JSON.parse(payload, max_nesting: false, allow_comments: true) + end + end + else + raise "Unexpected minefield test: #{name}" + end + end +end diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index ae0f285d3..0549f6f4c 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -1,15 +1,6 @@ # frozen_string_literal: true + require_relative 'test_helper' -require 'stringio' -require 'tempfile' -begin - require 'ostruct' -rescue LoadError -end -begin - require 'bigdecimal' -rescue LoadError -end class JSONParserTest < Test::Unit::TestCase include JSON @@ -47,6 +38,32 @@ def test_parsing assert_equal 'test', parser.parse end + def test_on_load_method + on_load = ->(value) { Integer === value ? value + 1 : value }.method(:call) + assert_equal [2], JSON.parse('[1]', on_load: on_load) + end + + def test_on_load_invalid_type + ['x', Object.new, Time.now].each do |on_load| + assert_raise(TypeError) { JSON.parse('[1]', on_load: on_load) } + end + end + + def test_on_load_invalid_to_proc + on_load = Object.new + def on_load.to_proc + method(:to_proc) + end + assert_raise(TypeError) { JSON.parse('[1]', on_load: on_load) } + end + + def test_on_load_falsy + [nil, false].each do |on_load| + config = JSON::Parser::Config.new(on_load: on_load) + assert_equal [1], config.parse('[1]') + end + end + def test_parser_reset parser = Parser.new('{"a":"b"}') assert_equal({ 'a' => 'b' }, parser.parse) @@ -60,7 +77,8 @@ def test_parse_values assert_equal(-23, parse('-23')) assert_equal(23, parse('23')) assert_in_delta(0.23, parse('0.23'), 1e-2) - assert_in_delta(0.0, parse('0e0'), 1e-2) + assert_equal(0.0, parse('0e0')) + assert_equal(-0.0, parse('-0e0')) assert_equal("", parse('""')) assert_equal("foobar", parse('"foobar"')) end @@ -123,11 +141,60 @@ def test_parse_numbers assert_equal_float(-3.141, parse('-3141.0e-3')) assert_equal_float(-3.141, parse('-3141e-3')) assert_raise(ParserError) { parse('NaN') } - assert parse('NaN', :allow_nan => true).nan? + assert parse('NaN', allow_nan: true).nan? assert_raise(ParserError) { parse('Infinity') } - assert_equal(1.0/0, parse('Infinity', :allow_nan => true)) + assert_equal(1.0/0, parse('Infinity', allow_nan: true)) assert_raise(ParserError) { parse('-Infinity') } - assert_equal(-1.0/0, parse('-Infinity', :allow_nan => true)) + assert_equal(-1.0/0, parse('-Infinity', allow_nan: true)) + capture_output { assert_equal(Float::INFINITY, parse("23456789012E666")) } + + assert_parse_float "1.5e-99999999999999999999" + assert_parse_float "1.5e-9223372036854775808" + assert_parse_float "0.0e-99999999999999999999" + assert_parse_float "-1.5e-99999999999999999999" + assert_parse_float "123.456e-18446744073709551615" + end + + INTEGER_FAST_PATH_BOUNDARIES = [ + "999999999999999999", # 18 digits + "1000000000000000000", # narrowest 19 digits + "9999999999999999999", # widest 19 digits, still inside uint64_t + "-999999999999999999", + "-9223372036854775807", # INT64_MAX, the widest negatable accumulator + "-9223372036854775808", # INT64_MIN, one past what negating a uint64_t covers + "-9223372036854775809", + "-9999999999999999999", # widest negative 19 digits + "10000000000000000000", # narrowest 20 digits + "18446744073709551614", + "18446744073709551615", # UINT64_MAX exactly, the last value the range check admits + "18446744073709551616", # 2**64, first value it must reject + "18446744073709551617", + "19999999999999999999", + "99999999999999999999", # widest 20 digits + "-18446744073709551615", # negatives never take the 20 digit path + "-18446744073709551616", + "-99999999999999999999", + "100000000000000000000", # 21 digits, always a bignum + "-100000000000000000000", + ].freeze + + def test_parse_integer_boundaries + INTEGER_FAST_PATH_BOUNDARIES.each do |literal| + expected = Integer(literal, 10) + + assert_equal(expected, parse(literal)) + end + end + + def test_parse_bignum + bignum = Integer('1234567890' * 10) + assert_equal(bignum, JSON.parse(bignum.to_s)) + assert_equal(bignum.to_f, JSON.parse(bignum.to_s + ".0")) + + bignum = Integer('1234567890' * 50) + assert_equal(bignum, JSON.parse(bignum.to_s)) + bignum_float = EnvUtil.suppress_warning { bignum.to_f } + assert_equal(bignum_float, EnvUtil.suppress_warning { JSON.parse(bignum.to_s + ".0") }) end def test_parse_bigdecimals @@ -157,6 +224,45 @@ def test_parse_complex_objects end end + def test_parse_control_chars_in_string + 0.upto(31) do |ord| + assert_raise JSON::ParserError do + parse(%("#{ord.chr}")) + end + end + end + + def test_parse_allowed_control_chars_in_string + 0.upto(31) do |ord| + assert_equal ord.chr, parse(%("#{ord.chr}"), allow_control_characters: true) + end + end + + def test_parse_control_char_and_backslash + backslash_and_control_char = "\\\t" + assert_raise JSON::ParserError do + JSON.parse(%("#{'a' * 30}#{backslash_and_control_char}"), allow_control_characters: true, allow_invalid_escape: false) + end + + JSON.parse(%("#{'a' * 30}#{backslash_and_control_char}"), allow_control_characters: true, allow_invalid_escape: true) + + assert_equal ["a\u0000b", 2], JSON.parse("[\"a\\\x00b\", 2]", allow_control_characters: true, allow_invalid_escape: true) + end + + def test_parse_invalid_escape + assert_raise JSON::ParserError do + parse(%("fo\\o")) + end + + assert_equal "foo", parse(%("fo\\o"), allow_invalid_escape: true) + end + + def test_parse_invalid_escape_non_ascii + assert_equal "caf\u00e9", parse(%("caf\\\u00e9"), allow_invalid_escape: true) + assert_equal "\u3042", parse(%("\\\u3042"), allow_invalid_escape: true) + assert_equal "\u{1F600}", parse(%("\\\u{1F600}"), allow_invalid_escape: true) + end + def test_parse_arrays assert_equal([1,2,3], parse('[1,2,3]')) assert_equal([1.2,2,3], parse('[1.2,2,3]')) @@ -176,7 +282,9 @@ def test_parse_arrays def test_parse_json_primitive_values assert_raise(JSON::ParserError) { parse('') } assert_raise(TypeError) { parse(nil) } - assert_raise(JSON::ParserError) { parse(' /* foo */ ') } + EnvUtil.suppress_warning do # still no warning in JRuby verions + assert_raise(JSON::ParserError) { parse(' /* foo */ ') } + end assert_equal nil, parse('null') assert_equal false, parse('false') assert_equal true, parse('true') @@ -185,9 +293,9 @@ def test_parse_json_primitive_values assert_equal_float 3.141, parse('3.141'), 1E-3 assert_equal 2 ** 64, parse('18446744073709551616') assert_equal 'foo', parse('"foo"') - assert parse('NaN', :allow_nan => true).nan? - assert parse('Infinity', :allow_nan => true).infinite? - assert parse('-Infinity', :allow_nan => true).infinite? + assert parse('NaN', allow_nan: true).nan? + assert parse('Infinity', allow_nan: true).infinite? + assert parse('-Infinity', allow_nan: true).infinite? end def test_parse_arrays_with_allow_trailing_comma @@ -311,6 +419,34 @@ def test_invalid_unicode_escape assert_raise(JSON::ParserError) { parse('"\uaa"') } assert_raise(JSON::ParserError) { parse('"\uaaa"') } assert_equal "\uaaaa", parse('"\uaaaa"') + + assert_raise(JSON::ParserError) { parse('"\u______"') } + assert_raise(JSON::ParserError) { parse('"\u1_____"') } + assert_raise(JSON::ParserError) { parse('"\u11____"') } + assert_raise(JSON::ParserError) { parse('"\u111___"') } + end + + def test_unicode_followed_by_newline + # Ref: https://github.com/ruby/json/issues/912 + assert_equal "🌌\n".bytes, JSON.parse('"\ud83c\udf0c\n"').bytes + assert_equal "🌌\n", JSON.parse('"\ud83c\udf0c\n"') + assert_predicate JSON.parse('"\ud83c\udf0c\n"'), :valid_encoding? + end + + def test_invalid_surogates + assert_raise(JSON::ParserError) { parse('"\\uD800"') } + assert_raise(JSON::ParserError) { parse('"\\uD800_________________"') } + assert_raise(JSON::ParserError) { parse('"\\uD800\\u0041"') } + assert_raise(JSON::ParserError) { parse('"\\uD800\\u004') } + # Lone trailing surrogate (issue #1069): parser previously returned an + # invalid-UTF-8 String instead of raising. Symmetric to the leading cases + # above. + assert_raise(JSON::ParserError) { parse('"\\uDC00"') } + assert_raise(JSON::ParserError) { parse('"\\uDC00_________________"') } + assert_raise(JSON::ParserError) { parse('"\\uDC00\\uD800"') } + # Valid pair still parses to the astral codepoint U+10000. + assert_predicate JSON.parse('"\\uD800\\uDC00"'), :valid_encoding? + assert_equal "\u{10000}", JSON.parse('"\\uD800\\uDC00"') end def test_parse_big_integers @@ -326,6 +462,40 @@ def test_parse_big_integers assert_equal orig, parse(json5) end + def test_parse_escaped_key + doc = { + "test\r1" => 1, + "entries" => [ + "test\t2" => 2, + "test\n3" => 3, + ] + } + + assert_equal doc, parse(JSON.generate(doc)) + end + + def test_parse_duplicate_key + expected = {"a" => 2} + + assert_equal expected, parse('{"a": 1, "a": 2}', allow_duplicate_key: true) + assert_raise(ParserError) { parse('{"a": 1, "a": 2}', allow_duplicate_key: false) } + assert_raise(ParserError) { parse('{"a": 1, "a": 2}', allow_duplicate_key: false, symbolize_names: true) } + + unless RUBY_ENGINE == 'jruby' + assert_raise(ParserError) do + fake_key = Object.new + JSON.load('{"a": 1, "a": 2}', -> (obj) { obj == "a" ? fake_key : obj }, allow_duplicate_key: false) + end + end + end + + def test_parse_duplicate_key_escape + error = assert_raise(ParserError) do + JSON.parse('{"%s%s%s%s":1,"%s%s%s%s":2}', allow_duplicate_key: false) + end + assert_match "%s%s%s%s", error.message + end + def test_some_wrong_inputs assert_raise(ParserError) { parse('[] bla') } assert_raise(ParserError) { parse('[] 1') } @@ -345,22 +515,17 @@ def test_some_wrong_inputs def test_symbolize_names assert_equal({ "foo" => "bar", "baz" => "quux" }, parse('{"foo":"bar", "baz":"quux"}')) - assert_equal({ :foo => "bar", :baz => "quux" }, - parse('{"foo":"bar", "baz":"quux"}', :symbolize_names => true)) - assert_raise(ArgumentError) do - parse('{}', :symbolize_names => true, :create_additions => true) - end + assert_equal({ foo: "bar", baz: "quux" }, + parse('{"foo":"bar", "baz":"quux"}', symbolize_names: true)) end def test_freeze - assert_predicate parse('{}', :freeze => true), :frozen? - assert_predicate parse('[]', :freeze => true), :frozen? - assert_predicate parse('"foo"', :freeze => true), :frozen? + assert_predicate parse('{}', freeze: true), :frozen? + assert_predicate parse('[]', freeze: true), :frozen? + assert_predicate parse('"foo"', freeze: true), :frozen? - if string_deduplication_available? - assert_same(-'foo', parse('"foo"', :freeze => true)) - assert_same(-'foo', parse('{"foo": 1}', :freeze => true).keys.first) - end + assert_same(-'foo', parse('"foo"', freeze: true)) + assert_same(-'foo', parse('{"foo": 1}', freeze: true).keys.first) end def test_parse_comments @@ -376,7 +541,7 @@ def test_parse_comments JSON assert_equal( { "key1" => "value1", "key2" => "value2", "key3" => "value3" }, - parse(json)) + parse(json, allow_comments: true)) json = <<~JSON { "key1":"value1" /* multi line @@ -385,7 +550,7 @@ def test_parse_comments * comment */ } JSON - assert_raise(ParserError) { parse(json) } + assert_raise(ParserError) { parse(json, allow_comments: true) } json = <<~JSON { "key1":"value1" /* multi line @@ -393,7 +558,7 @@ def test_parse_comments /* legal nested multi line comment start sequence */ } JSON - assert_equal({ "key1" => "value1" }, parse(json)) + assert_equal({ "key1" => "value1" }, parse(json, allow_comments: true)) json = <<~JSON { "key1":"value1" /* multi line @@ -402,59 +567,150 @@ def test_parse_comments and again, throw an Error */ } JSON - assert_raise(ParserError) { parse(json) } + assert_raise(ParserError) { parse(json, allow_comments: true) } json = <<~JSON { "key1":"value1" /*/*/ } JSON - assert_equal({ "key1" => "value1" }, parse(json)) - assert_equal({}, parse('{} /**/')) - assert_raise(ParserError) { parse('{} /* comment not closed') } - assert_raise(ParserError) { parse('{} /*/') } - assert_raise(ParserError) { parse('{} /x wrong comment') } - assert_raise(ParserError) { parse('{} /') } + assert_equal({ "key1" => "value1" }, parse(json, allow_comments: true)) + assert_equal({}, parse('{} /**/', allow_comments: true)) + assert_equal({}, parse('{} // eol comment ending at eof', allow_comments: true)) + assert_raise(ParserError) { parse('{} /* comment not closed', allow_comments: true) } + assert_raise(ParserError) { parse('{} /*/', allow_comments: true) } + assert_raise(ParserError) { parse('{} /x wrong comment', allow_comments: true) } + assert_raise(ParserError) { parse('{} /', allow_comments: true) } + assert_equal({}, parse('/**/ {}', allow_comments: true)) + assert_raise(ParserError) { parse('/**/ {}', allow_comments: false) } end def test_nesting too_deep = '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]' too_deep_ary = eval too_deep assert_raise(JSON::NestingError) { parse too_deep } - assert_raise(JSON::NestingError) { parse too_deep, :max_nesting => 100 } - ok = parse too_deep, :max_nesting => 101 + assert_raise(JSON::NestingError) { parse too_deep, max_nesting: 100 } + ok = parse too_deep, max_nesting: 101 assert_equal too_deep_ary, ok - ok = parse too_deep, :max_nesting => nil + ok = parse too_deep, max_nesting: nil assert_equal too_deep_ary, ok - ok = parse too_deep, :max_nesting => false + ok = parse too_deep, max_nesting: false assert_equal too_deep_ary, ok - ok = parse too_deep, :max_nesting => 0 + ok = parse too_deep, max_nesting: 0 assert_equal too_deep_ary, ok end def test_backslash + assert_raise(JSON::ParserError) do + JSON.parse('"\\') + end + data = [ '\\.(?i:gif|jpe?g|png)$' ] json = '["\\\\.(?i:gif|jpe?g|png)$"]' assert_equal data, parse(json) - # + data = [ '\\"' ] json = '["\\\\\""]' assert_equal data, parse(json) - # + json = '["/"]' data = [ '/' ] assert_equal data, parse(json) - # + json = '["\""]' data = ['"'] assert_equal data, parse(json) - # - json = '["\\\'"]' - data = ["'"] + + json = '["\\/"]' + data = ["/"] assert_equal data, parse(json) json = '["\/"]' data = [ '/' ] assert_equal data, parse(json) + + data = ['"""""""""""""""""""""""""'] + json = '["\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\""]' + assert_equal data, parse(json) + + data = '["This is a "test" of the emergency broadcast system."]' + json = "\"[\\\"This is a \\\"test\\\" of the emergency broadcast system.\\\"]\"" + assert_equal data, parse(json) + + data = '\tThis is a test of the emergency broadcast system.' + json = "\"\\\\tThis is a test of the emergency broadcast system.\"" + assert_equal data, parse(json) + + data = 'This\tis a test of the emergency broadcast system.' + json = "\"This\\\\tis a test of the emergency broadcast system.\"" + assert_equal data, parse(json) + + data = 'This is\ta test of the emergency broadcast system.' + json = "\"This is\\\\ta test of the emergency broadcast system.\"" + assert_equal data, parse(json) + + data = 'This is a test of the emergency broadcast\tsystem.' + json = "\"This is a test of the emergency broadcast\\\\tsystem.\"" + assert_equal data, parse(json) + + data = 'This is a test of the emergency broadcast\tsystem.\n' + json = "\"This is a test of the emergency broadcast\\\\tsystem.\\\\n\"" + assert_equal data, parse(json) + + data = '"' * 15 + json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\"" + assert_equal data, parse(json) + + data = "\"\"\"\"\"\"\"\"\"\"\"\"\"\"a" + json = "\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"a\"" + assert_equal data, parse(json) + + data = "\u0001\u0001\u0001\u0001" + json = "\"\\u0001\\u0001\\u0001\\u0001\"" + assert_equal data, parse(json) + + data = "\u0001a\u0001a\u0001a\u0001a" + json = "\"\\u0001a\\u0001a\\u0001a\\u0001a\"" + assert_equal data, parse(json) + + data = "\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\"" + assert_equal data, parse(json) + + data = "\u0001aa\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\\u0001aa\"" + assert_equal data, parse(json) + + data = "\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa\u0001aa" + json = "\"\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\\u0001aa\"" + assert_equal data, parse(json) + + data = "\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002\u0001a\u0002" + json = "\"\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\\u0001a\\u0002\"" + assert_equal data, parse(json) + + data = "ab\u0002c" + json = "\"ab\\u0002c\"" + assert_equal data, parse(json) + + data = "ab\u0002cab\u0002cab\u0002cab\u0002c" + json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002c\"" + assert_equal data, parse(json) + + data = "ab\u0002cab\u0002cab\u0002cab\u0002cab\u0002cab\u0002c" + json = "\"ab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002cab\\u0002c\"" + assert_equal data, parse(json) + + data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f" + json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\"" + assert_equal data, parse(json) + + data = "\n\t\f\b\n\t\f\b\n\t\f\b\n\t\f\b" + json = "\"\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\"" + assert_equal data, parse(json) + + data = "a\n\t\f\b\n\t\f\b\n\t\f\b\n\t" + json = "\"a\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\\f\\b\\n\\t\"" + assert_equal data, parse(json) end class SubArray < Array @@ -468,20 +724,6 @@ def shifted? end end - class SubArray2 < Array - def to_json(*a) - { - JSON.create_id => self.class.name, - 'ary' => to_a, - }.to_json(*a) - end - - def self.json_create(o) - o.delete JSON.create_id - o['ary'] - end - end - class SubArrayWrapper def initialize @data = [] @@ -504,15 +746,16 @@ def shifted? end def test_parse_array_custom_array_derived_class - res = parse('[1,2]', :array_class => SubArray) + res = parse('[1,2]', array_class: SubArray) assert_equal([1,2], res) assert_equal(SubArray, res.class) assert res.shifted? end def test_parse_array_custom_non_array_derived_class - res = parse('[1,2]', :array_class => SubArrayWrapper) + res = parse('[1,2]', array_class: SubArrayWrapper) assert_equal([1,2], res.data) + assert_equal(1, res[0]) assert_equal(SubArrayWrapper, res.class) assert res.shifted? end @@ -549,59 +792,37 @@ def self.json_create(o) end def test_parse_object_custom_hash_derived_class - res = parse('{"foo":"bar"}', :object_class => SubHash) + res = parse('{"foo":"bar"}', object_class: SubHash) assert_equal({"foo" => "bar"}, res) assert_equal(SubHash, res.class) assert res.item_set? end - if defined?(::OpenStruct) - class SubOpenStruct < OpenStruct - def [](k) - __send__(k) - end - - def []=(k, v) - @item_set = true - __send__("#{k}=", v) - end + class OpenStructLike + def initialize + @attrs = {} + end - def item_set? - @item_set - end + def [](k) + @attrs[k.to_sym] end - def test_parse_object_custom_non_hash_derived_class - res = parse('{"foo":"bar"}', :object_class => SubOpenStruct) - assert_equal "bar", res.foo - assert_equal(SubOpenStruct, res.class) - assert res.item_set? + def []=(k, v) + @attrs[k.to_sym] = v end - def test_parse_generic_object - res = parse( - '{"foo":"bar", "baz":{}}', - :object_class => JSON::GenericObject - ) - assert_equal(JSON::GenericObject, res.class) - assert_equal "bar", res.foo - assert_equal "bar", res["foo"] - assert_equal "bar", res[:foo] - assert_equal "bar", res.to_hash[:foo] - assert_equal(JSON::GenericObject, res.baz.class) + def method_missing(name, *) + @attrs.fetch(name) do + super + end end end - def test_generate_core_subclasses_with_new_to_json - obj = SubHash2["foo" => SubHash2["bar" => true]] - obj_json = JSON(obj) - obj_again = parse(obj_json, :create_additions => true) - assert_kind_of SubHash2, obj_again - assert_kind_of SubHash2, obj_again['foo'] - assert obj_again['foo']['bar'] - assert_equal obj, obj_again - assert_equal ["foo"], - JSON(JSON(SubArray2["foo"]), :create_additions => true) + def test_parse_object_custom_non_hash_derived_class + res = parse('{"foo":"bar"}', object_class: OpenStructLike) + assert_equal "bar", res.foo + assert_equal "bar", res[:foo] + assert_equal(OpenStructLike, res.class) end def test_generate_core_subclasses_with_default_to_json @@ -633,7 +854,7 @@ def test_parse_error_message_length error = assert_raise(JSON::ParserError) do JSON.parse('{"foo": ' + ('A' * 500) + '}') end - assert_operator 60, :>, error.message.bytesize + assert_operator 80, :>, error.message.bytesize end def test_parse_error_incomplete_hash @@ -641,24 +862,177 @@ def test_parse_error_incomplete_hash JSON.parse('{"input":{"firstName":"Bob","lastName":"Mob","email":"bob@example.com"}') end if RUBY_ENGINE == "ruby" - assert_equal %(expected ',' or '}' after object value, got: ''), error.message + assert_equal %(expected ',' or '}' after object value, got: EOF at line 1 column 72), error.message end end def test_parse_error_snippet - omit "C ext only test" unless RUBY_ENGINE == "ruby" + omit "JRuby errors don't contain positions" if RUBY_ENGINE == "jruby" error = assert_raise(JSON::ParserError) { JSON.parse("あああああああああああああああああああああああ") } - assert_equal "unexpected character: 'ああああああああああ'", error.message + assert_equal "unexpected character: 'ああああああああああ' at line 1 column 1", error.message error = assert_raise(JSON::ParserError) { JSON.parse("aあああああああああああああああああああああああ") } - assert_equal "unexpected character: 'aああああああああああ'", error.message + assert_equal "unexpected character: 'aああああああああああ' at line 1 column 1", error.message error = assert_raise(JSON::ParserError) { JSON.parse("abあああああああああああああああああああああああ") } - assert_equal "unexpected character: 'abあああああああああ'", error.message + assert_equal "unexpected character: 'abああああああああああ' at line 1 column 1", error.message error = assert_raise(JSON::ParserError) { JSON.parse("abcあああああああああああああああああああああああ") } - assert_equal "unexpected character: 'abcあああああああああ'", error.message + assert_equal "unexpected character: 'abcあああああああああ' at line 1 column 1", error.message + + error = assert_raise(JSON::ParserError) { JSON.parse("[1,\n@") } + assert_equal "unexpected character: '@' at line 2 column 1", error.message + + error = assert_raise(JSON::ParserError) { JSON.parse("[\n 1,\n @\n]") } + assert_equal "unexpected character: '@' at line 3 column 3", error.message + + error = assert_raise(JSON::ParserError) { JSON.parse("@") } + assert_equal "unexpected character: '@' at line 1 column 1", error.message + + error = assert_raise(JSON::ParserError) { JSON.parse('"a" 日本') } + assert_equal "unexpected token at end of stream '日本' at line 1 column 5", error.message + + error = assert_raise(JSON::ParserError) { JSON.parse('[1, é') } + assert_equal "unexpected character: 'é' at line 1 column 5", error.message + end + + def test_parse_error_snippet_multibyte_boundaries + omit "JRuby errors don't contain positions" if RUBY_ENGINE == "jruby" + + ['é', '日', '😀'].each do |char| + ['', ' ', "\n", "\t", "\r"].each do |suffix| + error = assert_raise(JSON::ParserError) { JSON.parse(char + suffix) } + assert_equal "unexpected character: '#{char}' at line 1 column 1", error.message + end + + (28..32).each do |padding| + prefix = 'a' * padding + fragment = prefix + char * ((32 - padding) / char.bytesize) + error = assert_raise(JSON::ParserError) { JSON.parse(prefix + char * 20) } + assert_equal "unexpected character: '#{fragment}' at line 1 column 1", error.message + assert_predicate error.message, :valid_encoding? + end + end + end + + def test_parse_error_json_path + assert_parse_error_at "$", "xyz" + assert_parse_error_at "$.a", '{"a": xyz}' + assert_parse_error_at "$[3]", '[1, 2, "hi", xyz]' + assert_parse_error_at "$.a[1].b", '{"a": [1, {"b": xyz}]}' + assert_parse_error_at "$.a", '{"a": 1 xyz}' + assert_parse_error_at "$", '{"a": 1, xyz}' + + assert_parse_error_at "$.a.b.c", '{"a": {"b": {"c"' + assert_parse_error_at "$.a.b.c", '{"a": {"b": {"c":' + assert_parse_error_at "$.a.b", '{"a": {"b": {"c": 1, "d' + + assert_parse_error_at "$[4]", '[1,2,3,4,5' + assert_parse_error_at "$[5]", '[1,2,3,4,5,' + assert_parse_error_at "$[5]", '[1,2,3,4,5,]' + + if RUBY_ENGINE != "jruby" + assert_parse_error_at %($["\xff"][1]), %({"\xff":[1,x]}) + end + end + + def test_parse_error_position + error = assert_raise(JSON::ParserError) { JSON.parse("[1,\n@") } + assert_equal 2, error.line + assert_equal 1, error.column + + error = assert_raise(JSON::ParserError) { JSON.parse('{"a": {"b": [1, {"c": 1, "c": 2}]}}') } + assert_equal 1, error.line + assert_equal 17, error.column + end + + def test_parse_error_position_at_newline + omit "JRuby string decoding errors don't contain positions" if RUBY_ENGINE == "jruby" + + ["[1,\n\"ab\ncd\"]", "[1,\n\"日本\ncd\"]"].each do |json| + error = assert_raise(JSON::ParserError) { JSON.parse(json) } + assert_equal 2, error.line + assert_equal 4, error.column + assert_include error.message, 'at line 2 column 4' + end + end + + def test_parse_error_position_with_multibyte_characters + { + '["日本", @]' => [1, 8], + "[\n\"é日😀\", @" => [2, 8], + '{"日本":{"a":1,"a":2}}' => [1, 7], + '["日本",' => [1, 7], + "[1,\n" => [2, 1], + '' => [1, 1], + }.each do |json, (line, column)| + error = assert_raise(JSON::ParserError) { JSON.parse(json) } + assert_equal line, error.line + assert_equal column, error.column + assert_include error.message, "at line #{line} column #{column}" + end + end + + def test_parse_error_json_path_on_load + assert_parse_error_at "$" do + JSON.load('{"a": {"b": {"c":', -> (obj) { + if String === obj + BasicObject.new + else + obj + end + }) + end + + assert_parse_error_at "$.a" do + JSON.load('{"a": {"b": {"c":', -> (obj) { + if obj == "b" + BasicObject.new + else + obj + end + }) + end + end + + def test_parse_error_json_path_key_escaping + assert_parse_error_at '$["hello world"]', '{"hello world": xyz}' + assert_parse_error_at '$["a\"b"]', '{"a\"b": xyz}' + assert_parse_error_at '$[""]', '{"": xyz}' + assert_parse_error_at '$["あ"]', '{"あ": xyz}' + assert_parse_error_at '$.foo["1x"]', '{"foo": {"1x": xyz}}' + end + + def test_parse_error_json_path_symbolize_names + assert_parse_error_at "$.a[1].b" do + JSON.parse('{"a": [1, {"b": xyz}]}', symbolize_names: true) + end + assert_parse_error_at '$["hello world"]' do + JSON.parse('{"hello world": xyz}', symbolize_names: true) + end + assert_parse_error_at "$.x.a" do + JSON.parse('{"x": {"a": 1, "a": 2}}', symbolize_names: true) + end + end + + def test_parse_error_json_path_duplicate_key + assert_parse_error_at "$.a", '{"a": 1, "a": 2}' + assert_parse_error_at "$.x.a", '{"x": {"a": 1, "b": 2, "a": 3}}' + assert_parse_error_at "$.arr[0].a", '{"arr": [{"a": 1, "a": 2}]}' + assert_parse_error_at "$.x.a", '{"x": {"a": 1, "a": 2}}' + end + + def test_parse_error_json_path_resumable + omit "JSON::ResumableParser not available" unless defined?(JSON::ResumableParser) + + parser = JSON::ResumableParser.new + parser << '{"a": [1, {"b": ' + parser.parse + assert_parse_error_at "$.a[1].b" do + parser << 'xyz' + parser.parse + end end def test_parse_leading_slash @@ -668,21 +1042,105 @@ def test_parse_leading_slash end end - private + def test_parse_whitespace_after_newline + assert_equal [], JSON.parse("[\n#{' ' * (8 + 8 + 4 + 3)}]") + end + + def test_frozen + parser_config = JSON::Parser::Config.new({}).freeze + assert_raise FrozenError do + parser_config.send(:initialize, {}) + end + end + + def test_mutating_source_string_during_parsing + expected = ([1] * 100) + [2.3] + ([1] * 100) + source = JSON.generate(expected) + expected.delete_at(100) + + fake_decimal_class = Class.new + fake_decimal_class.define_method(:initialize) do |number| + source.tr!('1', '0') + number.to_f + end + + actual = JSON.parse(source, decimal_class: fake_decimal_class) + actual.delete_at(100) + assert_equal expected, actual + end + + def test_mutating_source_string_with_known_coderange_during_parsing + expected = ([1] * 100) + [2.3] + ([1] * 100) + source = JSON.generate(expected) + source.ascii_only? # caches the coderange, which lets JRuby's String#tr! write in place + expected.delete_at(100) + + fake_decimal_class = Class.new + fake_decimal_class.define_method(:initialize) do |number| + source.tr!('1', '0') + number.to_f + end + + actual = JSON.parse(source, decimal_class: fake_decimal_class) + actual.delete_at(100) + assert_equal expected, actual + end + + def test_adversary_encode + fake_string = Class.new(String) do + def encode(*) + 42 + end + end + + assert_raise TypeError, Encoding::InvalidByteSequenceError do + JSON.parse(fake_string.new("\xff".b, encoding: Encoding::UTF_16)) + end - def string_deduplication_available? - r1 = rand.to_s - r2 = r1.dup - begin - (-r1).equal?(-r2) - rescue NoMethodError - false # No String#-@ + if RUBY_ENGINE != "jruby" + fake_string = Class.new(String) do + attr_reader :encoded_str + + def initialize(...) + super + @encoded_str = JSON.dump(1000.times.to_a) + end + + def encode(*) + @encoded_str + end + end + + fake = fake_string.new("blah", encoding: Encoding::UTF_16) + assert_equal 1000.times.to_a, JSON.parse(fake, on_load: ->(v) { + if v == 20 + fake.encoded_str.clear + end + v + }) end end + private + + def assert_parse_float(float_str) + assert_equal Float(float_str), JSON.parse(float_str) + end + def assert_equal_float(expected, actual, delta = 1e-2) Array === expected and expected = expected.first Array === actual and actual = actual.first assert_in_delta(expected, actual, delta) end + + def assert_parse_error_at(path, json = nil) + error = assert_raise(JSON::ParserError) do + if block_given? + yield + else + JSON.parse(json) + end + end + assert_equal path, error.json_path + end end diff --git a/test/json/json_string_matching_test.rb b/test/json/json_string_matching_test.rb deleted file mode 100644 index 21cd64902..000000000 --- a/test/json/json_string_matching_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true -require_relative 'test_helper' -require 'time' - -class JSONStringMatchingTest < Test::Unit::TestCase - include JSON - - class TestTime < ::Time - def self.json_create(string) - Time.parse(string) - end - - def to_json(*) - %{"#{strftime('%FT%T%z')}"} - end - - def ==(other) - to_i == other.to_i - end - end - - def test_match_date - t = TestTime.new - t_json = [ t ].to_json - time_regexp = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{4}\z/ - assert_equal [ t ], - parse( - t_json, - :create_additions => true, - :match_string => { time_regexp => TestTime } - ) - assert_equal [ t.strftime('%FT%T%z') ], - parse( - t_json, - :match_string => { time_regexp => TestTime } - ) - end -end diff --git a/test/json/ractor_test.rb b/test/json/ractor_test.rb index f857c9a8b..9e28f13c1 100644 --- a/test/json/ractor_test.rb +++ b/test/json/ractor_test.rb @@ -8,10 +8,29 @@ end class JSONInRactorTest < Test::Unit::TestCase + unless Ractor.method_defined?(:value) + module RactorBackport + refine Ractor do + alias_method :value, :take + end + end + + using RactorBackport + end + def test_generate + roundtrip_test(:generate, :parse) + end + + def test_dump + roundtrip_test(:dump, :load) + end + + def roundtrip_test(generator_method, parser_method) pid = fork do - r = Ractor.new do - json = JSON.generate({ + Warning[:experimental] = false + generator = Ractor.new(generator_method) do |generator_method| + json = JSON.public_send(generator_method, { 'a' => 2, 'b' => 3.141, 'c' => 'c', @@ -23,16 +42,57 @@ def test_generate }) JSON.parse(json) end + + parser = Ractor.new(parser_method) do |parser_method| + JSON.public_send(parser_method, '{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' + + '"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}') + end + + actual_json = generator.value + expected_json = parser.value + if expected_json == actual_json + exit 0 + else + puts "Expected:" + puts expected_json + puts "Actual:" + puts actual_json + puts + exit 1 + end + end + _, status = Process.waitpid2(pid) + assert_predicate status, :success? + end + + def test_coder + coder = JSON::Coder.new.freeze + assert Ractor.shareable?(coder) + pid = fork do + Warning[:experimental] = false + r = Ractor.new(coder) do |coder| + json = coder.dump({ + 'a' => 2, + 'b' => 3.141, + 'c' => 'c', + 'd' => [ 1, "b", 3.14 ], + 'e' => { 'foo' => 'bar' }, + 'g' => "\"\0\037", + 'h' => 1000.0, + 'i' => 0.001 + }) + coder.load(json) + end expected_json = JSON.parse('{"a":2,"b":3.141,"c":"c","d":[1,"b",3.14],"e":{"foo":"bar"},' + '"g":"\\"\\u0000\\u001f","h":1000.0,"i":0.001}') - actual_json = r.take + actual_json = r.value if expected_json == actual_json exit 0 else puts "Expected:" puts expected_json - puts "Acutual:" + puts "Actual:" puts actual_json puts exit 1 @@ -41,4 +101,26 @@ def test_generate _, status = Process.waitpid2(pid) assert_predicate status, :success? end + + class NonNative + def initialize(value) + @value = value + end + end + + def test_coder_proc + block = Ractor.shareable_proc { |value| value.as_json } + coder = JSON::Coder.new(&block).freeze + assert Ractor.shareable?(coder) + + pid = fork do + Warning[:experimental] = false + assert_equal [{}], Ractor.new(coder) { |coder| + coder.load('[{}]') + }.value + end + + _, status = Process.waitpid2(pid) + assert_predicate status, :success? + end if Ractor.respond_to?(:shareable_proc) end if defined?(Ractor) && Process.respond_to?(:fork) diff --git a/test/json/resumable_parser_test.rb b/test/json/resumable_parser_test.rb new file mode 100644 index 000000000..dbf3ea7b6 --- /dev/null +++ b/test/json/resumable_parser_test.rb @@ -0,0 +1,777 @@ +# frozen_string_literal: true + +require_relative 'test_helper' + +class JSONResumageParserTest < Test::Unit::TestCase + include JSON + + def setup + omit "JRuby not supported" if RUBY_ENGINE == "jruby" + @parser = new_parser + end + + def test_keyword_arguments + new_parser + new_parser({}) + new_parser(allow_nan: true) + + error = assert_raise(ArgumentError) do + new_parser(doesnt_exist: true, allow_nan: true) + end + assert_equal "unknown keyword: doesnt_exist", error.message + + error = assert_raise(ArgumentError) do + new_parser(doesnt_exist: true, allow_nan: true, a: 1, b: 2) + end + assert_equal "unknown keywords: doesnt_exist, a, b", error.message + end + + def test_value + refute_predicate @parser, :value? + assert_raise(ArgumentError) { @parser.value } + + @parser << '[]' + + refute_predicate @parser, :value? + assert_raise(ArgumentError) { @parser.value } + + assert @parser.parse + + assert_predicate @parser, :value? + assert_equal [], @parser.value + + refute_predicate @parser, :value? + assert_raise(ArgumentError) { @parser.value } + end + + def test_clear + @parser << '[1, 2, 3' + refute @parser.parse + assert_equal '3', @parser.rest + + @parser.clear + + assert_equal '', @parser.rest + refute_predicate @parser, :value? + + @parser << '[1, 2, 3][' + assert @parser.parse + assert_predicate @parser, :value? + assert_equal '[', @parser.rest + + @parser.clear + + assert_equal '', @parser.rest + refute_predicate @parser, :value? + end + + def test_parse_with_empty_buffer_keeps_parser_usable + # parse before any feed must not leak the in_use lock + refute @parser.parse + @parser << '[1, 2, 3]' + assert @parser.parse + assert_equal [1, 2, 3], @parser.value + + # same after a clear with no following feed + @parser.clear + refute @parser.parse + @parser << '[4]' + assert @parser.parse + assert_equal [4], @parser.value + end + + def test_clear_resets_nesting_depth + # An unfinished document leaks a nesting level; #clear must reset it so a later shallow + # document is not rejected with a spurious NestingError. + parser = new_parser(max_nesting: 10) + 10.times do + parser << '[1' # opens an array that is never closed before clear + parser.parse + parser.clear + end + parser << '[1]' + assert parser.parse + assert_equal [1], parser.value + end + + def test_nested_parse_error + parser = new_parser(on_load: ->(o) do + JSON.parse("") #=> raises JSON::ParserError + o + end) + parser << "[1]" + + assert_raise(JSON::ParserError) do + parser.parse + end + end + + def test_on_load_method + on_load = ->(value) { Integer === value ? value + 1 : value }.method(:call) + parser = new_parser(on_load: on_load) + parser << '[1]' + assert parser.parse + assert_equal [2], parser.value + end + + def test_parse_document_direct + @parser << '[true]' + assert_equal true, @parser.parse + assert_equal [true], @parser.value + end + + def test_parse_multiple_documents_direct + @parser << '[true]{}[1, 2, 3]' + + assert_equal true, @parser.parse + assert_equal [true], @parser.value + + assert_equal true, @parser.parse + assert_equal({}, @parser.value) + + assert_equal true, @parser.parse + assert_equal [1, 2, 3], @parser.value + end + + def test_parse_top_level_keywords + assert_resumed_parsing('true') + assert_resumed_parsing('false') + assert_resumed_parsing('null') + + assert_parse_stream([true, false, nil], 'truefalsenull') + end + + def test_parse_top_level_numbers + assert_parse_stream([1, 2, 3], '1 2 3 ') + assert_parse_stream([1, 2], '1 2 3') # Parser can't know if the number is terminated + assert_parse_stream([3], ' ') + assert_parse_stream([1, 2, 3, true], '1 2 3true') + assert_parse_stream([-1, 2.34, 5.0e67], '-1 2.34 5e67 ') + end + + def test_parse_byte_by_byte_array + assert_resumed_parsing('[]') + assert_resumed_parsing('[ ]') + assert_resumed_parsing('[true]') + assert_resumed_parsing('[12]') + assert_resumed_parsing('[ 12 ]') + assert_resumed_parsing('[ 12.3 ]') + assert_resumed_parsing('[ 12.3e12 ]') + assert_resumed_parsing('[ 1e12 ]') + assert_resumed_parsing('[-12]') + assert_resumed_parsing('[ -12 ]') + assert_resumed_parsing('[ -12.3 ]') + assert_resumed_parsing('[ -12.3e12 ]') + assert_resumed_parsing('[ -1e12 ]') + end + + def test_parse_byte_by_byte_object + assert_resumed_parsing('{}') + assert_resumed_parsing('{ }') + assert_resumed_parsing('{"test" : true}') + assert_resumed_parsing('{ "test":12, "value" : { "key": 42} }') + assert_resumed_parsing('{"te\u0000st":true}') + end + + def test_parse_byte_by_byte_string + assert_resumed_parsing(JSON.generate('test')) + assert_resumed_parsing(JSON.generate('te\\st')) + assert_resumed_parsing('"te\\u2028st"') + assert_resumed_parsing(JSON.generate("te\u2028st")) + assert_resumed_parsing(JSON.generate("te \u2028 st")) + + parser = JSON::ResumableParser.new + parser << '"\u12"' + assert_raise JSON::ParserError do + parser.parse + end + end + + def test_parse_byte_by_byte_numbers + assert_resumed_parsing('123 ', trailing_bytes: 1) + end + + def test_large_numbers_split_across_feeds_are_decoded_correctly + { + '12345678901234567890123456789012345678901234567890 ' => 12345678901234567890123456789012345678901234567890, + '-98765432109876543210987654321 ' => -98765432109876543210987654321, + '3.14159265358979323846264338327950288 ' => 3.14159265358979323846264338327950288, + '-1.5e-300 ' => -1.5e-300, + }.each do |doc, expected| + parser = new_parser + value = nil + doc.each_char do |char| + parser << char + value = parser.value if parser.parse + end + assert_equal expected, value, doc.inspect + end + end + + def test_integer_boundaries_split_across_feeds + [ + '9999999999999999999', # widest 19 digits + '-9223372036854775808', # INT64_MIN + '18446744073709551615', # UINT64_MAX exactly + '18446744073709551616', # 2**64, wraps the accumulator to 0 + '99999999999999999999', # widest 20 digits + '-18446744073709551616', + '100000000000000000000', # 21 digits + ].each do |literal| + doc = "#{literal} " + parser = new_parser + value = nil + doc.each_char do |char| + parser << char + value = parser.value if parser.parse + end + assert_equal Integer(literal, 10), value, doc.inspect + end + end + + def test_nul_byte_is_a_syntax_error + # A NUL byte in a structural position must raise, not stall forever waiting for more input + # (peek() returns 0 both at EOS and for a literal NUL byte). + assert_parse_error "\x00" # document value + assert_parse_error "[\x00]" # first array element + assert_parse_error "[1\x00]" # after an array element (',' or ']' expected) + assert_parse_error "[1,\x00]" # array element after ',' + assert_parse_error "{\x00}" # object key + assert_parse_error "{\"a\":1\x00}" # after an object value (',' or '}' expected) + assert_parse_error "{\"a\":1,\x00}" # object key after ',' + end + + def test_nul_after_backslash_in_object_key_is_a_syntax_error + assert_parse_error "{\"key\\\x00\":1}" + assert_parse_error "{\"a\":1,\"key\\\x00\":2}" + end + + def test_nul_after_backslash_in_object_key_across_feeds + @parser << "{\"key\\" + refute @parser.parse + @parser << "\x00\":1}" + assert_raise(JSON::ParserError) { @parser.parse } + end + + def test_incomplete_input_at_structural_positions_resumes + # Counterpart of test_nul_byte_is_a_syntax_error: a genuine EOS at the same positions must + # stay incomplete (return false), not raise -- this is what distinguishes EOS from a NUL. + assert_incomplete "[" + assert_incomplete "[1" + assert_incomplete "[1," + assert_incomplete "{" + assert_incomplete "{\"a\"" + assert_incomplete "{\"a\":1" + assert_incomplete "{\"a\":1," + end + + def test_line_comment_spanning_feed_boundary_is_not_terminated_early + # A `//` line comment is only terminated by a newline. When the newline + # has not arrived yet, the comment must stay incomplete rather than being + # treated as consumed -- otherwise its body, delivered in a later chunk, + # leaks out as parsed values. + values = [] + parser = new_parser(allow_comments: true) + parser << '[1] //' + values << parser.value while parser.parse + + parser << "[2]\n[3]" # [2] belongs to the comment, [3] is a real document + values << parser.value while parser.parse + + assert_equal [[1], [3]], values + end + + def test_line_comment_terminated_by_newline_across_feeds + values = [] + parser = new_parser(allow_comments: true) + parser << '[1] //co' + values << parser.value while parser.parse + + parser << "mment\n[2]" + values << parser.value while parser.parse + + assert_equal [[1], [2]], values + end + + def test_block_comment_spanning_feed_boundary_is_not_terminated_early + # A `/* */` block comment whose closing `*/` has not arrived yet must stay + # incomplete, mirroring the line-comment behaviour above. + values = [] + parser = new_parser(allow_comments: true) + parser << '[1] /*' + values << parser.value while parser.parse + + parser << '[2]*/[3]' # [2] belongs to the comment, [3] is a real document + values << parser.value while parser.parse + + assert_equal [[1], [3]], values + end + + def test_trailing_comma_split_across_feed_boundary + # With allow_trailing_comma the closing bracket may arrive in a later chunk + # than the comma; consuming the comma must not lose the ability to close. + parser = new_parser(allow_trailing_comma: true) + parser << '[1,' + refute parser.parse + parser << ']' + assert parser.parse + assert_equal [1], parser.value + + parser = new_parser(allow_trailing_comma: true) + parser << '{"a":1,' + refute parser.parse + parser << '}' + assert parser.parse + assert_equal({ "a" => 1 }, parser.value) + + # The boundary can also fall after an inner comma, then after the outer one. + parser = new_parser(allow_trailing_comma: true) + parser << '[[1,' + refute parser.parse + parser << '],]' + assert parser.parse + assert_equal [[1]], parser.value + end + + def test_trailing_comma_byte_by_byte + parser = new_parser(allow_trailing_comma: true) + '[1, 2, ]'.each_char { |c| parser << c; parser.parse } + assert_equal [1, 2], parser.value + + parser = new_parser(allow_trailing_comma: true) + '{ "a": 1, }'.each_char { |c| parser << c; parser.parse } + assert_equal({ "a" => 1 }, parser.value) + end + + def test_comment_after_comma_split_across_feed_boundary + # A comment right after a ',' straddling a feed boundary must not drop the + # comma: the value/key it separates must still be parsed on resume. + # The array case needs allow_trailing_comma: without it the array comma path + # commits its phase before eating the comment, so only the trailing-comma + # path exercises the eat-before-commit bug (the object path always did). + parser = new_parser(allow_comments: true, allow_trailing_comma: true) + parser << '[1,/*' + refute parser.parse + parser << '*/2]' + assert parser.parse + assert_equal [1, 2], parser.value + + parser = new_parser(allow_comments: true) + parser << '{"a":1,/*' + refute parser.parse + parser << '*/"b":2}' + assert parser.parse + assert_equal({ "a" => 1, "b" => 2 }, parser.value) + end + + def test_comment_after_container_open_split_across_feed_boundary + # A comment right after '[' or '{' straddling a feed boundary must not drop + # the opening token: it is consumed before its frame is pushed, so the + # suspension must resume from the bracket, not from inside the comment. + parser = new_parser(allow_comments: true) + parser << '[/*' + refute parser.parse + parser << '*/1]' + assert parser.parse + assert_equal [1], parser.value + + parser = new_parser(allow_comments: true) + parser << '{/*' + refute parser.parse + parser << '*/"a":1}' + assert parser.parse + assert_equal({ "a" => 1 }, parser.value) + + parser = new_parser(allow_comments: true) + parser << '[ /*' + refute parser.parse + parser << '*/ ]' + assert parser.parse + assert_equal [], parser.value + + # The boundary can even split the comment marker itself. + parser = new_parser(allow_comments: true) + parser << '[/' + refute parser.parse + parser << '**/1]' + assert parser.parse + assert_equal [1], parser.value + + # Line comments suspend the same way when their newline hasn't arrived. + parser = new_parser(allow_comments: true) + parser << '[//' + refute parser.parse + parser << "x\n1]" + assert parser.parse + assert_equal [1], parser.value + end + + def test_rest + @parser << '[1, 2, 3, "unterminated string' + refute @parser.parse + assert_equal '"unterminated string', @parser.rest + end + + def test_feed_frozen_multibyte_chunks + @parser << '{"message":"日本'.freeze + refute @parser.parse + @parser << '語のつづき"}'.freeze + assert @parser.parse + value = @parser.value + assert_equal({ "message" => "日本語のつづき" }, value) + assert_equal Encoding::UTF_8, value["message"].encoding + end + + def test_eos + assert_predicate @parser, :eos? + + @parser << '[1, 2, 3]' + refute_predicate @parser, :eos? + + assert @parser.parse + assert_predicate @parser, :eos? + + @parser << '123' + refute_predicate @parser, :eos? + + refute @parser.parse + refute_predicate @parser, :eos? + + @parser << ' ' + assert @parser.parse + assert_equal 123, @parser.value + assert_predicate @parser, :eos? + + refute @parser.parse + assert_predicate @parser, :eos? + end + + def test_empty_predicate + # empty? is defined on the state left after parsing everything that + # could be parsed from the fed bytes, so drain with parse/value first. + { + '' => true, # nothing fed: vacuously empty + '{"a":1}' => true, + '{"a":1}{"b":2}' => true, + '{"a":1} ' => true, # trailing whitespace + '{"a":1}{"b":2' => false, # inside a number token + '{"a":1}{"b":' => false, # right after a colon (token boundary) + '{"a":1}{' => false, # right after an object open + '{"a":1,' => false, # right after a comma (token boundary) + '"abc' => false, # inside a string token + '[1,2' => false, # unclosed array + }.each do |json, expected| + parser = new_parser + parser << json + parser.value while parser.parse + assert_equal expected, parser.empty?, "expected #{json.inspect} to be empty? == #{expected}" + end + end + + def test_empty_predicate_with_undrained_buffer + @parser << '{"a":1}{"b":2}' + assert @parser.parse + refute_predicate @parser, :empty? # second document still in the buffer + assert_equal({ "a" => 1 }, @parser.value) + assert @parser.parse + assert_equal({ "b" => 2 }, @parser.value) + assert_predicate @parser, :empty? + end + + def test_empty_predicate_with_pending_value + # A fully parsed document awaiting retrieval with #value is not empty. + @parser << '{"a":1}' + assert @parser.parse + refute_predicate @parser, :empty? + assert_equal({ "a" => 1 }, @parser.value) + assert_predicate @parser, :empty? + end + + def test_empty_predicate_across_feeds + @parser << '{"a' # chunk boundary inside a string literal + refute @parser.parse + refute_predicate @parser, :empty? + + @parser << '":1' + refute @parser.parse + refute_predicate @parser, :empty? + + @parser << '}' + assert @parser.parse + refute_predicate @parser, :empty? # value not retrieved yet + assert_equal({ "a" => 1 }, @parser.value) + assert_predicate @parser, :empty? + end + + def test_partial_value_predicate + { + '' => false, + '{"a":1}' => false, + '{"a":1}{"b":2}' => false, + '{"a":1} ' => false, + '{"a":1}{"b":2' => true, # inside a number token + '{"a":1}{"b":' => true, # right after a colon (token boundary) + # The tokenizer rewinds to the token start on EOS, so nothing is + # registered yet for a lone '{' or an unterminated top-level string: + # partial_value returns nil and partial_value? agrees. The truncation + # is still observable through the buffer: eos? is false, rest isn't + # empty. + '{"a":1}{' => false, # right after an object open + '"abc' => false, # inside a string token + '{"a":1,' => true, # right after a comma (token boundary) + '[1,2' => true, # unclosed array + }.each do |json, expected| + parser = new_parser + parser << json + parser.value while parser.parse + assert_equal expected, parser.partial_value?, "expected #{json.inspect} to be partial_value? == #{expected}" + assert_equal !parser.partial_value.nil?, parser.partial_value?, "partial_value?/partial_value mismatch for #{json.inspect}" + end + end + + def test_partial_value + assert_nil @parser.partial_value + assert_partial_value [1, 2, 3], '[1, 2, 3, "unterminated string' + assert_partial_value({ "a" => 1, "b" => { "c" => nil } }, '{ "a": 1, "b": { "c": "unterminated string') + assert_partial_value({ "a" => 1, "b" => { "c" => nil } }, '{ "a": 1, "b": { "c"') + assert_partial_value([1, { "a" => 1, "b" => { "c" => nil } }], '[1, { "a": 1, "b": { "c"') + end + + def test_partial_value_collapses_nested_incomplete_containers + # partial_value rebuilds the open containers on a scratch value stack; folding + # an empty inner container pushes a value, so that stack must hold more than its + # live size or the push reallocates the scratch buffer. + assert_partial_value({ "abc" => {} }, '{"abc":{"d') + assert_partial_value({ "a" => { "b" => { "c" => {} } } }, '{"a":{"b":{"c":{"e') + assert_partial_value([1, { "a" => {} }], '[1,{"a":{"d') + assert_partial_value({ "a" => [1, { "b" => [2, { "c" => nil }] }] }, '{"a":[1,{"b":[2,{"c"') + assert_partial_value([1, [2, [3, { "x" => nil }]]], '[1,[2,[3,{"x":[') + end + + def test_partial_value_issue_1005 + data = <<~JSON + [ + [] + ] + JSON + data.each_line do |line| + @parser << line + @parser.parse + @parser.partial_value # This unexpected parse error doesn't happen if we comment this out + end + assert_equal [[]], @parser.value + end + + def test_partial_value_missing + assert_nil @parser.partial_value + end + + def test_reentrency_prevented + called = false + parser = nil + callback = ->(o) do + unless called + called = true + parser.parse + end + o + end + parser = new_parser(on_load: callback) + parser << '[]' + error = assert_raise ArgumentError do + parser.parse + end + assert_equal "ResumableParser can't be used recursively", error.message + + called = false + parser = nil + callback = ->(o) do + unless called + called = true + parser.partial_value + end + o + end + parser = new_parser(on_load: callback) + parser << '[]' + error = assert_raise ArgumentError do + parser.parse + end + assert_equal "ResumableParser can't be used recursively", error.message + end + + def test_reentrency_prevented_in_partial_value + parser = nil + callback = ->(o) do + # Arrays are only built while partial_value runs (the scalars were pushed by the + # earlier parse); re-entering here used to corrupt/free the shared frame stack. + parser.parse if o.is_a?(Array) + o + end + parser = new_parser(on_load: callback) + parser << '[1, [2, 3,' + parser.parse + error = assert_raise ArgumentError do + parser.partial_value + end + assert_equal "ResumableParser can't be used recursively", error.message + + # The in_use lock must be released even though partial_value raised. + refute_predicate parser, :value? + end + + def test_feed_during_callback_prevented + parser = nil + callback = ->(o) do + parser << '99' if o == 1 # feeding while a parse is running must be rejected + o + end + parser = new_parser(on_load: callback) + parser << '[1, 2, 3]' + error = assert_raise ArgumentError do + parser.parse + end + assert_equal "ResumableParser can't be used recursively", error.message + + # the lock is released, so the parser stays usable + parser = new_parser + parser << '[1, 2, 3]' + assert parser.parse + assert_equal [1, 2, 3], parser.value + end + + def test_exception_unlock_parser + called = false + parser = nil + callback = ->(o) do + unless called + called = true + raise "oops" + end + o + end + parser = new_parser(on_load: callback) + parser << '[][1]' + assert_raise RuntimeError do + parser.parse + end + + assert parser.parse + assert_equal [1], parser.value + end + + def test_spill_rvalue_stack + expected = [1] * 1000 + @parser << JSON.dump(expected) + assert @parser.parse + assert_equal expected, @parser.value + end + + def test_spill_frames_stack + json = '[' * 1000 + ']' * 1000 + expected = JSON.parse(json, max_nesting: 1000) + @parser = new_parser(max_nesting: 1000) + @parser << json + assert @parser.parse + assert_equal expected, @parser.value + end + + def test_buffer_shrink + doc1 = '{"a":"' + ("x" * 800) + '"} {' # >= 512 bytes + doc2 = '"b":1} ' + + parser = JSON::ResumableParser.new({}) + + parser << doc1 # internal buffer becomes a *shared* string here + parser.parse # consume doc1 -> >50% of a >=512B buffer is now consumed + parser.value + + parser << doc2 # buffer is shrinked + parser.parse + parser.value + end + + def test_parsed_bytes + chunk = '[1, 2, 3, 4, tru' + @parser << chunk + refute @parser.parse + assert_equal chunk.bytesize, @parser.parsed_bytes + + @parser << 'e][]' + assert @parser.parse + assert_equal chunk.bytesize + 2, @parser.parsed_bytes + + assert @parser.parse + assert_equal 2, @parser.parsed_bytes + + @parser << chunk + refute @parser.parse + assert_equal chunk.bytesize, @parser.parsed_bytes + @parser.clear + assert_equal 0, @parser.parsed_bytes + end + + def test_parse_error_message + error = assert_parse_error("\n\n[plop\nfoo", "unexpected character: 'plop'") + assert_equal 0, error.line + assert_equal 0, error.column + end + + private + + def assert_parse_error(json, expected_error_message = nil) + parser = new_parser + parser << json + error = assert_raise(JSON::ParserError, "expected a parse error for #{json.inspect}") do + parser.parse + end + if expected_error_message + assert_equal expected_error_message, error.message + end + error + end + + def assert_incomplete(json) + parser = new_parser + parser << json + refute(parser.parse, "expected #{json.inspect} not to produce a value") + end + + def assert_partial_value(expected, json) + parser = new_parser + parser << json + refute parser.parse + 2.times do + assert_equal expected, parser.partial_value + end + end + + def assert_resumed_parsing(json, parser = @parser, trailing_bytes: 0) + expected = JSON.parse(json) + + last_parsed_byte_index = 0 + json.each_byte do |byte| + parser << byte.chr + last_parsed_byte_index += 1 + break if parser.parse + end + actual = parser.value + assert_equal expected, actual + remaining_bytes = (json.bytesize - last_parsed_byte_index) + assert_equal 0, remaining_bytes, "unconsumed bytes: #{actual.inspect}, remaining: #{json.byteslice(-1, remaining_bytes).inspect}" + assert_equal json.bytesize - trailing_bytes, parser.parsed_bytes + end + + def assert_parse_stream(expected, json, parser = @parser) + actual = [] + parser << json + while parser.parse + actual << parser.value + end + assert_equal(expected, actual) + end + + def new_parser(...) + JSON::ResumableParser.new(...) + end +end diff --git a/test/json/test_helper.rb b/test/json/test_helper.rb index d849e28b9..222ba86df 100644 --- a/test/json/test_helper.rb +++ b/test/json/test_helper.rb @@ -1,7 +1,41 @@ $LOAD_PATH.unshift(File.expand_path('../../../ext', __FILE__), File.expand_path('../../../lib', __FILE__)) +if ENV["JSON_COVERAGE"] + # This test helper is loaded inside Ruby's own test suite, so we try to not mess it up. + require 'coverage' + + branches_supported = Coverage.respond_to?(:supported?) && Coverage.supported?(:branches) + + # Coverage module must be started before SimpleCov to work around the cyclic require order. + # Track both branches and lines, or else SimpleCov misleadingly reports 0/0 = 100% for non-branching files. + Coverage.start(lines: true, + branches: branches_supported) + + require 'simplecov' + SimpleCov.start do + # Enabling both coverage types to let SimpleCov know to output them together in reports + enable_coverage :line + enable_coverage :branch if branches_supported + + # Can't always trust SimpleCov to find files implicitly + track_files 'lib/**/*.rb' + + add_filter 'lib/json/truffle_ruby' unless RUBY_ENGINE == 'truffleruby' + add_filter 'test/' + + coverage_dir '.coverage' + end +end + require 'json' require 'test/unit' +require 'stringio' +require 'tempfile' +require 'zlib' +begin + require 'bigdecimal' +rescue LoadError +end if ENV["JSON_COMPACT"] if GC.respond_to?(:verify_compaction_references) diff --git a/tools/fuzz.rb b/tools/fuzz.rb deleted file mode 100755 index 826193019..000000000 --- a/tools/fuzz.rb +++ /dev/null @@ -1,131 +0,0 @@ -require 'json' - -class Fuzzer - def initialize(n, freqs = {}) - sum = freqs.inject(0.0) { |s, x| s + x.last } - freqs.each_key { |x| freqs[x] /= sum } - s = 0.0 - freqs.each_key do |x| - freqs[x] = s .. (s + t = freqs[x]) - s += t - end - @freqs = freqs - @n = n - @alpha = (0..0xff).to_a - end - - def random_string - s = '' - 30.times { s << @alpha[rand(@alpha.size)] } - s - end - - def pick - r = rand - found = @freqs.find { |k, f| f.include? rand } - found && found.first - end - - def make_pick - k = pick - case - when k == Hash, k == Array - k.new - when k == true, k == false, k == nil - k - when k == String - random_string - when k == Fixnum - rand(2 ** 30) - 2 ** 29 - when k == Bignum - rand(2 ** 70) - 2 ** 69 - end - end - - def fuzz(current = nil) - if @n > 0 - case current - when nil - @n -= 1 - current = fuzz [ Hash, Array ][rand(2)].new - when Array - while @n > 0 - @n -= 1 - current << case p = make_pick - when Array, Hash - fuzz(p) - else - p - end - end - when Hash - while @n > 0 - @n -= 1 - current[random_string] = case p = make_pick - when Array, Hash - fuzz(p) - else - p - end - end - end - end - current - end -end - -class MyState < JSON.state - WS = " \r\t\n" - - def initialize - super( - :indent => make_spaces, - :space => make_spaces, - :space_before => make_spaces, - :object_nl => make_spaces, - :array_nl => make_spaces, - :max_nesting => false - ) - end - - def make_spaces - s = '' - rand(1).times { s << WS[rand(WS.size)] } - s - end -end - -n = (ARGV.shift || 500).to_i -loop do - fuzzer = Fuzzer.new(n, - Hash => 25, - Array => 25, - String => 10, - Fixnum => 10, - Bignum => 10, - nil => 5, - true => 5, - false => 5 - ) - o1 = fuzzer.fuzz - json = JSON.generate o1, MyState.new - if $DEBUG - puts "-" * 80 - puts json, json.size - else - puts json.size - end - begin - o2 = JSON.parse(json, :max_nesting => false) - rescue JSON::ParserError => e - puts "Caught #{e.class}: #{e.message}\n#{e.backtrace * "\n"}" - puts "o1 = #{o1.inspect}", "json = #{json}", "json_str = #{json.inspect}" - puts "locals = #{local_variables.inspect}" - exit - end - if o1 != o2 - puts "mismatch", "o1 = #{o1.inspect}", "o2 = #{o2.inspect}", - "json = #{json}", "json_str = #{json.inspect}" - puts "locals = #{local_variables.inspect}" - end -end