aboutsummaryrefslogtreecommitdiffstats
path: root/ext/json/lib
diff options
context:
space:
mode:
authornaruse <naruse@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2009-05-05 02:32:49 +0000
committernaruse <naruse@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2009-05-05 02:32:49 +0000
commit4f364c6bf7f5bcc064b5a12b235fd63b200359a0 (patch)
tree057f7862f43f36e096a5037c9fcdcd1259a4c5d4 /ext/json/lib
parent1735892962c9e05ff679fc32f933a66141b2cbaf (diff)
downloadruby-4f364c6bf7f5bcc064b5a12b235fd63b200359a0.tar.gz
Update to JSON 1.1.4.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@23346 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'ext/json/lib')
-rwxr-xr-x[-rw-r--r--]ext/json/lib/json.rb162
-rw-r--r--ext/json/lib/json/add/core.rb2
-rwxr-xr-x[-rw-r--r--]ext/json/lib/json/common.rb0
-rwxr-xr-x[-rw-r--r--]ext/json/lib/json/editor.rb13
-rw-r--r--ext/json/lib/json/ext.rb2
-rw-r--r--ext/json/lib/json/pure.rb22
-rw-r--r--ext/json/lib/json/pure/generator.rb394
-rw-r--r--ext/json/lib/json/pure/parser.rb269
-rw-r--r--ext/json/lib/json/version.rb2
9 files changed, 127 insertions, 739 deletions
diff --git a/ext/json/lib/json.rb b/ext/json/lib/json.rb
index 640baaebb6..35fab1e11d 100644..100755
--- a/ext/json/lib/json.rb
+++ b/ext/json/lib/json.rb
@@ -77,57 +77,121 @@ require 'json/common'
#
# == Speed Comparisons
#
-# I have created some benchmark results (see the benchmarks subdir of the
-# package) for the JSON-Parser to estimate the speed up in the C extension:
-#
-# JSON::Pure::Parser:: 28.90 calls/second
-# JSON::Ext::Parser:: 505.50 calls/second
-#
-# This is ca. <b>17.5</b> times the speed of the pure Ruby implementation.
-#
-# I have benchmarked the JSON-Generator as well. This generates a few more
-# values, because there are different modes, that also influence the achieved
+# I have created some benchmark results (see the benchmarks/data-p4-3Ghz
+# subdir of the package) for the JSON-parser to estimate the speed up in the C
+# extension:
+#
+# Comparing times (call_time_mean):
+# 1 ParserBenchmarkExt#parser 900 repeats:
+# 553.922304770 ( real) -> 21.500x
+# 0.001805307
+# 2 ParserBenchmarkYAML#parser 1000 repeats:
+# 224.513358139 ( real) -> 8.714x
+# 0.004454078
+# 3 ParserBenchmarkPure#parser 1000 repeats:
+# 26.755020642 ( real) -> 1.038x
+# 0.037376163
+# 4 ParserBenchmarkRails#parser 1000 repeats:
+# 25.763381731 ( real) -> 1.000x
+# 0.038814780
+# calls/sec ( time) -> speed covers
+# secs/call
+#
+# In the table above 1 is JSON::Ext::Parser, 2 is YAML.load with YAML
+# compatbile JSON document, 3 is is JSON::Pure::Parser, and 4 is
+# ActiveSupport::JSON.decode. The ActiveSupport JSON-decoder converts the
+# input first to YAML and then uses the YAML-parser, the conversion seems to
+# slow it down so much that it is only as fast as the JSON::Pure::Parser!
+#
+# If you look at the benchmark data you can see that this is mostly caused by
+# the frequent high outliers - the median of the Rails-parser runs is still
+# overall smaller than the median of the JSON::Pure::Parser runs:
+#
+# Comparing times (call_time_median):
+# 1 ParserBenchmarkExt#parser 900 repeats:
+# 800.592479481 ( real) -> 26.936x
+# 0.001249075
+# 2 ParserBenchmarkYAML#parser 1000 repeats:
+# 271.002390644 ( real) -> 9.118x
+# 0.003690004
+# 3 ParserBenchmarkRails#parser 1000 repeats:
+# 30.227910865 ( real) -> 1.017x
+# 0.033082008
+# 4 ParserBenchmarkPure#parser 1000 repeats:
+# 29.722384421 ( real) -> 1.000x
+# 0.033644676
+# calls/sec ( time) -> speed covers
+# secs/call
+#
+# I have benchmarked the JSON-Generator as well. This generated a few more
+# values, because there are different modes that also influence the achieved
# speed:
#
-# * JSON::Pure::Generator:
-# generate:: 35.06 calls/second
-# pretty_generate:: 34.00 calls/second
-# fast_generate:: 41.06 calls/second
-#
-# * JSON::Ext::Generator:
-# generate:: 492.11 calls/second
-# pretty_generate:: 348.85 calls/second
-# fast_generate:: 541.60 calls/second
-#
-# * Speedup Ext/Pure:
-# generate safe:: 14.0 times
-# generate pretty:: 10.3 times
-# generate fast:: 13.2 times
-#
-# The rails framework includes a generator as well, also it seems to be rather
-# slow: I measured only 23.87 calls/second which is slower than any of my pure
-# generator results. Here a comparison of the different speedups with the Rails
-# measurement as the divisor:
-#
-# * Speedup Pure/Rails:
-# generate safe:: 1.5 times
-# generate pretty:: 1.4 times
-# generate fast:: 1.7 times
-#
-# * Speedup Ext/Rails:
-# generate safe:: 20.6 times
-# generate pretty:: 14.6 times
-# generate fast:: 22.7 times
-#
-# To achieve the fastest JSON text output, you can use the
-# fast_generate/fast_unparse methods. Beware, that this will disable the
-# checking for circular Ruby data structures, which may cause JSON to go into
-# an infinite loop.
+# Comparing times (call_time_mean):
+# 1 GeneratorBenchmarkExt#generator_fast 1000 repeats:
+# 547.354332608 ( real) -> 15.090x
+# 0.001826970
+# 2 GeneratorBenchmarkExt#generator_safe 1000 repeats:
+# 443.968212317 ( real) -> 12.240x
+# 0.002252414
+# 3 GeneratorBenchmarkExt#generator_pretty 900 repeats:
+# 375.104545883 ( real) -> 10.341x
+# 0.002665923
+# 4 GeneratorBenchmarkPure#generator_fast 1000 repeats:
+# 49.978706968 ( real) -> 1.378x
+# 0.020008521
+# 5 GeneratorBenchmarkRails#generator 1000 repeats:
+# 38.531868759 ( real) -> 1.062x
+# 0.025952543
+# 6 GeneratorBenchmarkPure#generator_safe 1000 repeats:
+# 36.927649925 ( real) -> 1.018x 7 (>=3859)
+# 0.027079979
+# 7 GeneratorBenchmarkPure#generator_pretty 1000 repeats:
+# 36.272134441 ( real) -> 1.000x 6 (>=3859)
+# 0.027569373
+# calls/sec ( time) -> speed covers
+# secs/call
+#
+# In the table above 1-3 are JSON::Ext::Generator methods. 4, 6, and 7 are
+# JSON::Pure::Generator methods and 5 is the Rails JSON generator. It is now a
+# bit faster than the generator_safe and generator_pretty methods of the pure
+# variant but slower than the others.
+#
+# To achieve the fastest JSON text output, you can use the fast_generate
+# method. Beware, that this will disable the checking for circular Ruby data
+# structures, which may cause JSON to go into an infinite loop.
+#
+# Here are the median comparisons for completeness' sake:
+#
+# Comparing times (call_time_median):
+# 1 GeneratorBenchmarkExt#generator_fast 1000 repeats:
+# 708.258020939 ( real) -> 16.547x
+# 0.001411915
+# 2 GeneratorBenchmarkExt#generator_safe 1000 repeats:
+# 569.105020353 ( real) -> 13.296x
+# 0.001757145
+# 3 GeneratorBenchmarkExt#generator_pretty 900 repeats:
+# 482.825371244 ( real) -> 11.280x
+# 0.002071142
+# 4 GeneratorBenchmarkPure#generator_fast 1000 repeats:
+# 62.717626652 ( real) -> 1.465x
+# 0.015944481
+# 5 GeneratorBenchmarkRails#generator 1000 repeats:
+# 43.965681162 ( real) -> 1.027x
+# 0.022745013
+# 6 GeneratorBenchmarkPure#generator_safe 1000 repeats:
+# 43.929073409 ( real) -> 1.026x 7 (>=3859)
+# 0.022763968
+# 7 GeneratorBenchmarkPure#generator_pretty 1000 repeats:
+# 42.802514491 ( real) -> 1.000x 6 (>=3859)
+# 0.023363113
+# calls/sec ( time) -> speed covers
+# secs/call
#
# == Examples
#
-# To create a JSON text from a ruby data structure, you
-# can call JSON.generate (or JSON.unparse) like that:
+# To create a JSON text from a ruby data structure, you can call JSON.generate
+# like that:
#
# json = JSON.generate [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
# # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]"
@@ -210,8 +274,8 @@ require 'json/common'
# }
# ]
#
-# There are also the methods Kernel#j for unparse, and Kernel#jj for
-# pretty_unparse output to the console, that work analogous to Core Ruby's p
+# 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.
#
# The script tools/server.rb contains a small example if you want to test, how
@@ -230,6 +294,4 @@ module JSON
require 'json/pure'
end
end
-
- JSON_LOADED = true
end
diff --git a/ext/json/lib/json/add/core.rb b/ext/json/lib/json/add/core.rb
index 7121a77ff1..4423e7ad75 100644
--- a/ext/json/lib/json/add/core.rb
+++ b/ext/json/lib/json/add/core.rb
@@ -96,7 +96,7 @@ class Struct
def to_json(*args)
klass = self.class.name
- klass.nil? and raise JSON::JSONError, "Only named structs are supported!"
+ klass.to_s.empty? and raise JSON::JSONError, "Only named structs are supported!"
{
'json_class' => klass,
'v' => values,
diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb
index 1bd0ad5fbd..1bd0ad5fbd 100644..100755
--- a/ext/json/lib/json/common.rb
+++ b/ext/json/lib/json/common.rb
diff --git a/ext/json/lib/json/editor.rb b/ext/json/lib/json/editor.rb
index ad86fdb15e..9e05f44b5b 100644..100755
--- a/ext/json/lib/json/editor.rb
+++ b/ext/json/lib/json/editor.rb
@@ -769,7 +769,12 @@ module JSON
iter.type, iter.content = 'FalseClass', 'false'
end
when 'Numeric'
- iter.content = (Integer(value) rescue Float(value) rescue 0).to_s
+ iter.content =
+ if value == 'Infinity'
+ value
+ else
+ (Integer(value) rescue Float(value) rescue 0).to_s
+ end
when 'String'
iter.content = value
when 'Hash', 'Array'
@@ -937,7 +942,11 @@ module JSON
type = types[type_input.active]
@content = case type
when 'Numeric'
- Integer(value_input.text) rescue Float(value_input.text) rescue 0
+ if (t = value_input.text) == 'Infinity'
+ 1 / 0.0
+ else
+ Integer(t) rescue Float(t) rescue 0
+ end
else
value_input.text
end.to_s
diff --git a/ext/json/lib/json/ext.rb b/ext/json/lib/json/ext.rb
index ff4fa42329..719e56025c 100644
--- a/ext/json/lib/json/ext.rb
+++ b/ext/json/lib/json/ext.rb
@@ -10,4 +10,6 @@ module JSON
JSON.parser = Parser
JSON.generator = Generator
end
+
+ JSON_LOADED = true
end
diff --git a/ext/json/lib/json/pure.rb b/ext/json/lib/json/pure.rb
deleted file mode 100644
index 6af8705c5b..0000000000
--- a/ext/json/lib/json/pure.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'json/common'
-require 'json/pure/parser'
-require 'json/pure/generator'
-
-module JSON
- # Swap consecutive bytes of _string_ in place.
- def self.swap!(string) # :nodoc:
- 0.upto(string.size / 2) do |i|
- break unless string[2 * i + 1]
- string[2 * i], string[2 * i + 1] = string[2 * i + 1], string[2 * i]
- end
- string
- end
-
- # This module holds all the modules/classes that implement JSON's
- # functionality in pure ruby.
- module Pure
- $DEBUG and warn "Using pure library for JSON."
- JSON.parser = Parser
- JSON.generator = Generator
- end
-end
diff --git a/ext/json/lib/json/pure/generator.rb b/ext/json/lib/json/pure/generator.rb
deleted file mode 100644
index 4a9b05f545..0000000000
--- a/ext/json/lib/json/pure/generator.rb
+++ /dev/null
@@ -1,394 +0,0 @@
-module JSON
- MAP = {
- "\x0" => '\u0000',
- "\x1" => '\u0001',
- "\x2" => '\u0002',
- "\x3" => '\u0003',
- "\x4" => '\u0004',
- "\x5" => '\u0005',
- "\x6" => '\u0006',
- "\x7" => '\u0007',
- "\b" => '\b',
- "\t" => '\t',
- "\n" => '\n',
- "\xb" => '\u000b',
- "\f" => '\f',
- "\r" => '\r',
- "\xe" => '\u000e',
- "\xf" => '\u000f',
- "\x10" => '\u0010',
- "\x11" => '\u0011',
- "\x12" => '\u0012',
- "\x13" => '\u0013',
- "\x14" => '\u0014',
- "\x15" => '\u0015',
- "\x16" => '\u0016',
- "\x17" => '\u0017',
- "\x18" => '\u0018',
- "\x19" => '\u0019',
- "\x1a" => '\u001a',
- "\x1b" => '\u001b',
- "\x1c" => '\u001c',
- "\x1d" => '\u001d',
- "\x1e" => '\u001e',
- "\x1f" => '\u001f',
- '"' => '\"',
- '\\' => '\\\\',
- '/' => '\/',
- } # :nodoc:
-
- # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
- # UTF16 big endian characters as \u????, and return it.
- def utf8_to_json(string) # :nodoc:
- string = string.dup.force_encoding(Encoding::ASCII_8BIT)
- string.gsub!(/["\\\/\x0-\x1f]/) { MAP[$&] }
- string.gsub!(/(
- (?:
- [\xc2-\xdf][\x80-\xbf] |
- [\xe0-\xef][\x80-\xbf]{2} |
- [\xf0-\xf4][\x80-\xbf]{3}
- )+ |
- [\x80-\xc1\xf5-\xff] # invalid
- )/nx) { |c|
- c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
- c.unpack("U*").map{|c|
- c>0xFFFF ? ('\ud%03x\ud%03x'%[0x7C0+c/1024,0xC00+c%1024]) : ('\u%04x'%c)
- }.join("")
- }
- string
- end
- module_function :utf8_to_json
-
- module Pure
- module Generator
- # This class is used to create State instances, that are use to hold data
- # while generating a JSON text from a a Ruby data structure.
- class State
- # Creates a State object from _opts_, which ought to be Hash to create
- # a new State instance configured by _opts_, something else to create
- # an unconfigured instance. If _opts_ is a State object, it is just
- # returned.
- def self.from_state(opts)
- case opts
- when self
- opts
- when Hash
- new(opts)
- else
- new
- end
- end
-
- # 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: ''),
- # * *check_circular*: true if checking for circular data structures
- # should be done (the default), false otherwise.
- # * *check_circular*: true if checking for circular data structures
- # should be done, false (the default) otherwise.
- # * *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.
- def initialize(opts = {})
- @seen = {}
- @indent = ''
- @space = ''
- @space_before = ''
- @object_nl = ''
- @array_nl = ''
- @check_circular = true
- @allow_nan = false
- configure opts
- end
-
- # This string is used to indent levels in the JSON text.
- attr_accessor :indent
-
- # This string is used to insert a space between the tokens in a JSON
- # string.
- attr_accessor :space
-
- # This string is used to insert a space before the ':' in JSON objects.
- attr_accessor :space_before
-
- # This string is put at the end of a line that holds a JSON object (or
- # Hash).
- attr_accessor :object_nl
-
- # This string is put at the end of a line that holds a JSON array.
- attr_accessor :array_nl
-
- # This integer returns the maximum level of data structure nesting in
- # the generated JSON, max_nesting = 0 if no maximum is checked.
- attr_accessor :max_nesting
-
- def check_max_nesting(depth) # :nodoc:
- return if @max_nesting.zero?
- current_nesting = depth + 1
- current_nesting > @max_nesting and
- raise NestingError, "nesting of #{current_nesting} is too deep"
- end
-
- # Returns true, if circular data structures should be checked,
- # otherwise returns false.
- def check_circular?
- @check_circular
- end
-
- # Returns true if NaN, Infinity, and -Infinity should be considered as
- # valid JSON and output.
- def allow_nan?
- @allow_nan
- end
-
- # Returns _true_, if _object_ was already seen during this generating
- # run.
- def seen?(object)
- @seen.key?(object.__id__)
- end
-
- # Remember _object_, to find out if it was already encountered (if a
- # cyclic data structure is if a cyclic data structure is rendered).
- def remember(object)
- @seen[object.__id__] = true
- end
-
- # Forget _object_ for this generating run.
- def forget(object)
- @seen.delete object.__id__
- end
-
- # Configure this State instance with the Hash _opts_, and return
- # itself.
- def configure(opts)
- @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)
- @check_circular = !!opts[:check_circular] if opts.key?(:check_circular)
- @allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
- if !opts.key?(:max_nesting) # defaults to 19
- @max_nesting = 19
- elsif opts[:max_nesting]
- @max_nesting = opts[:max_nesting]
- else
- @max_nesting = 0
- end
- self
- end
-
- # Returns the configuration instance variables as a hash, that can be
- # passed to the configure method.
- def to_h
- result = {}
- for iv in %w[indent space space_before object_nl array_nl check_circular allow_nan max_nesting]
- result[iv.intern] = instance_variable_get("@#{iv}")
- end
- result
- end
- end
-
- module GeneratorMethods
- module Object
- # 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.
- def to_json(*) to_s.to_json end
- end
-
- module Hash
- # Returns a JSON string containing a JSON object, that is unparsed from
- # this Hash instance.
- # _state_ is a JSON::State object, that can also be used to configure the
- # produced JSON string output further.
- # _depth_ is used to find out nesting depth, to indent accordingly.
- def to_json(state = nil, depth = 0, *)
- if state
- state = JSON.state.from_state(state)
- state.check_max_nesting(depth)
- json_check_circular(state) { json_transform(state, depth) }
- else
- json_transform(state, depth)
- end
- end
-
- private
-
- def json_check_circular(state)
- if state and state.check_circular?
- state.seen?(self) and raise JSON::CircularDatastructure,
- "circular data structures not supported!"
- state.remember self
- end
- yield
- ensure
- state and state.forget self
- end
-
- def json_shift(state, depth)
- state and not state.object_nl.empty? or return ''
- state.indent * depth
- end
-
- def json_transform(state, depth)
- delim = ','
- delim << state.object_nl if state
- result = '{'
- result << state.object_nl if state
- result << map { |key,value|
- s = json_shift(state, depth + 1)
- s << key.to_s.to_json(state, depth + 1)
- s << state.space_before if state
- s << ':'
- s << state.space if state
- s << value.to_json(state, depth + 1)
- }.join(delim)
- result << state.object_nl if state
- result << json_shift(state, depth)
- result << '}'
- result
- end
- end
-
- module Array
- # Returns a JSON string containing a JSON array, that is unparsed from
- # this Array instance.
- # _state_ is a JSON::State object, that can also be used to configure the
- # produced JSON string output further.
- # _depth_ is used to find out nesting depth, to indent accordingly.
- def to_json(state = nil, depth = 0, *)
- if state
- state = JSON.state.from_state(state)
- state.check_max_nesting(depth)
- json_check_circular(state) { json_transform(state, depth) }
- else
- json_transform(state, depth)
- end
- end
-
- private
-
- def json_check_circular(state)
- if state and state.check_circular?
- state.seen?(self) and raise JSON::CircularDatastructure,
- "circular data structures not supported!"
- state.remember self
- end
- yield
- ensure
- state and state.forget self
- end
-
- def json_shift(state, depth)
- state and not state.array_nl.empty? or return ''
- state.indent * depth
- end
-
- def json_transform(state, depth)
- delim = ','
- delim << state.array_nl if state
- result = '['
- result << state.array_nl if state
- result << map { |value|
- json_shift(state, depth + 1) << value.to_json(state, depth + 1)
- }.join(delim)
- result << state.array_nl if state
- result << json_shift(state, depth)
- result << ']'
- result
- end
- end
-
- module Integer
- # Returns a JSON string representation for this Integer number.
- def to_json(*) to_s end
- end
-
- module Float
- # Returns a JSON string representation for this Float number.
- def to_json(state = nil, *)
- case
- when infinite?
- if !state || state.allow_nan?
- to_s
- else
- raise GeneratorError, "#{self} not allowed in JSON"
- end
- when nan?
- if !state || state.allow_nan?
- to_s
- else
- raise GeneratorError, "#{self} not allowed in JSON"
- end
- else
- to_s
- end
- end
- end
-
- module String
- # 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????.
- def to_json(*)
- '"' << JSON.utf8_to_json(self) << '"'
- end
-
- # Module that holds the extinding 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
- end
-
- module FalseClass
- # Returns a JSON string for false: 'false'.
- def to_json(*) 'false' end
- end
-
- module NilClass
- # Returns a JSON string for nil: 'null'.
- def to_json(*) 'null' end
- end
- end
- end
- end
-end
diff --git a/ext/json/lib/json/pure/parser.rb b/ext/json/lib/json/pure/parser.rb
deleted file mode 100644
index f5ff574d11..0000000000
--- a/ext/json/lib/json/pure/parser.rb
+++ /dev/null
@@ -1,269 +0,0 @@
-require 'strscan'
-
-module JSON
- module Pure
- # This class implements the JSON parser that is used to parse a JSON string
- # into a Ruby data structure.
- class Parser < StringScanner
- STRING = /" ((?:[^\x0-\x1f"\\] |
- \\["\\\/bfnrt] |
- \\u[0-9a-fA-F]{4} |
- \\[\x20-\xff])*)
- "/nx
- INTEGER = /(-?0|-?[1-9]\d*)/
- FLOAT = /(-?
- (?:0|[1-9]\d*)
- (?:
- \.\d+(?i:e[+-]?\d+) |
- \.\d+ |
- (?i:e[+-]?\d+)
- )
- )/x
- NAN = /NaN/
- INFINITY = /Infinity/
- MINUS_INFINITY = /-Infinity/
- OBJECT_OPEN = /\{/
- OBJECT_CLOSE = /\}/
- ARRAY_OPEN = /\[/
- ARRAY_CLOSE = /\]/
- PAIR_DELIMITER = /:/
- COLLECTION_DELIMITER = /,/
- TRUE = /true/
- FALSE = /false/
- NULL = /null/
- IGNORE = %r(
- (?:
- //[^\n\r]*[\n\r]| # line comments
- /\* # c-style comments
- (?:
- [^*/]| # normal chars
- /[^*]| # slashes that do not start a nested comment
- \*[^/]| # asterisks that do not end this comment
- /(?=\*/) # single slash before this comment's end
- )*
- \*/ # the End of this comment
- |[ \t\r\n]+ # whitespaces: space, horicontal tab, lf, cr
- )+
- )mx
-
- UNPARSED = Object.new
-
- # Creates a new JSON::Pure::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 19.
- # * *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.
- # * *create_additions*: If set to false, the Parser doesn't create
- # additions even if a matchin class and create_id was found. This option
- # defaults to true.
- def initialize(source, opts = {})
- super
- if !opts.key?(:max_nesting) # defaults to 19
- @max_nesting = 19
- elsif opts[:max_nesting]
- @max_nesting = opts[:max_nesting]
- else
- @max_nesting = 0
- end
- @allow_nan = !!opts[:allow_nan]
- ca = true
- ca = opts[:create_additions] if opts.key?(:create_additions)
- @create_id = ca ? JSON.create_id : nil
- end
-
- alias source string
-
- # Parses the current JSON string _source_ and returns the complete data
- # structure as a result.
- def parse
- reset
- obj = nil
- until eos?
- case
- when scan(OBJECT_OPEN)
- obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
- @current_nesting = 1
- obj = parse_object
- when scan(ARRAY_OPEN)
- obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
- @current_nesting = 1
- obj = parse_array
- when skip(IGNORE)
- ;
- else
- raise ParserError, "source '#{peek(20)}' not in JSON!"
- end
- end
- obj or raise ParserError, "source did not contain any JSON!"
- obj
- end
-
- private
-
- # Unescape characters in strings.
- UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr }
- UNESCAPE_MAP.update({
- ?" => '"',
- ?\\ => '\\',
- ?/ => '/',
- ?b => "\b",
- ?f => "\f",
- ?n => "\n",
- ?r => "\r",
- ?t => "\t",
- ?u => nil,
- })
-
- def parse_string
- if scan(STRING)
- return '' if self[1].empty?
- self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do |c|
- if u = UNESCAPE_MAP[$&[1]]
- u
- else # \uXXXX
- res = []
- stack = nil
- [c.delete!('\\\\u')].pack("H*").unpack("n*").each do |c|
- case c
- when 0xD800..0xDBFF
- raise JSON::ParserError, "partial character in source" if stack
- stack = c
- when 0xDC00..0xDFFF
- raise JSON::ParserError,
- "partial character in source" unless (0xD800..0xDBFF).include?(stack)
- res << (stack << 10) - 0x35fdc00 + c
- stack = nil
- else
- raise JSON::ParserError, "partial character in source" if stack
- res << c
- end
- end
- raise JSON::ParserError, "partial character in source" if stack
- res.pack("U*")
- end
- end.force_encoding("UTF-8")
- else
- UNPARSED
- end
- end
-
- def parse_value
- case
- when scan(FLOAT)
- Float(self[1])
- when scan(INTEGER)
- Integer(self[1])
- when scan(TRUE)
- true
- when scan(FALSE)
- false
- when scan(NULL)
- nil
- when (string = parse_string) != UNPARSED
- string
- when scan(ARRAY_OPEN)
- @current_nesting += 1
- ary = parse_array
- @current_nesting -= 1
- ary
- when scan(OBJECT_OPEN)
- @current_nesting += 1
- obj = parse_object
- @current_nesting -= 1
- obj
- when @allow_nan && scan(NAN)
- NaN
- when @allow_nan && scan(INFINITY)
- Infinity
- when @allow_nan && scan(MINUS_INFINITY)
- MinusInfinity
- else
- UNPARSED
- end
- end
-
- def parse_array
- raise NestingError, "nesting of #@current_nesting is to deep" if
- @max_nesting.nonzero? && @current_nesting > @max_nesting
- result = []
- delim = false
- until eos?
- case
- when (value = parse_value) != UNPARSED
- delim = false
- result << value
- skip(IGNORE)
- if scan(COLLECTION_DELIMITER)
- delim = true
- elsif match?(ARRAY_CLOSE)
- ;
- else
- raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
- end
- when scan(ARRAY_CLOSE)
- if delim
- raise ParserError, "expected next element in array at '#{peek(20)}'!"
- end
- break
- when skip(IGNORE)
- ;
- else
- raise ParserError, "unexpected token in array at '#{peek(20)}'!"
- end
- end
- result
- end
-
- def parse_object
- raise NestingError, "nesting of #@current_nesting is to deep" if
- @max_nesting.nonzero? && @current_nesting > @max_nesting
- result = {}
- delim = false
- until eos?
- case
- when (string = parse_string) != UNPARSED
- skip(IGNORE)
- unless scan(PAIR_DELIMITER)
- raise ParserError, "expected ':' in object at '#{peek(20)}'!"
- end
- skip(IGNORE)
- unless (value = parse_value).equal? UNPARSED
- result[string] = value
- delim = false
- skip(IGNORE)
- if scan(COLLECTION_DELIMITER)
- delim = true
- elsif match?(OBJECT_CLOSE)
- ;
- else
- raise ParserError, "expected ',' or '}' in object at '#{peek(20)}'!"
- end
- else
- raise ParserError, "expected value in object at '#{peek(20)}'!"
- end
- when scan(OBJECT_CLOSE)
- if delim
- raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!"
- end
- if @create_id and klassname = result[@create_id]
- klass = JSON.deep_const_get klassname
- break unless klass and klass.json_creatable?
- result = klass.json_create(result)
- end
- break
- when skip(IGNORE)
- ;
- else
- raise ParserError, "unexpected token in object at '#{peek(20)}'!"
- end
- end
- result
- end
- end
- end
-end
diff --git a/ext/json/lib/json/version.rb b/ext/json/lib/json/version.rb
index acf8217048..e2764b0c6e 100644
--- a/ext/json/lib/json/version.rb
+++ b/ext/json/lib/json/version.rb
@@ -1,6 +1,6 @@
module JSON
# JSON version
- VERSION = '1.1.3'
+ VERSION = '1.1.4'
VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
VERSION_MINOR = VERSION_ARRAY[1] # :nodoc: