oj-2.5.3/0000755000004100000410000000000012263716750012172 5ustar www-datawww-dataoj-2.5.3/lib/0000755000004100000410000000000012263716750012740 5ustar www-datawww-dataoj-2.5.3/lib/oj.rb0000644000004100000410000000253612263716750013703 0ustar www-datawww-data# Optimized JSON (Oj), as the name implies was written to provide speed # optimized JSON handling. # # Oj has several dump or serialization modes which control how Objects are # converted to JSON. These modes are set with the :mode option in either the # default options or as one of the options to the dump() method. # # - :strict mode will only allow the 7 basic JSON types to be serialized. Any other Object # will raise and Exception. # # - :null mode replaces any Object that is not one of the JSON types is replaced by a JSON null. # # - :object mode will dump any Object as a JSON Object with keys that match # the Ruby Object's variable names without the '@' character. This is the # highest performance mode. # # - :compat mode is is the compatible with other systems. It will serialize # any Object but will check to see if the Object implements a to_hash() or # to_json() method. If either exists that method is used for serializing the # Object. The to_hash() is more flexible and produces more consistent output # so it has a preference over the to_json() method. If neither the to_json() # or to_hash() methods exist then the Oj internal Object variable encoding # is used. module Oj end require 'oj/version' require 'oj/bag' require 'oj/error' require 'oj/mimic' require 'oj/saj' require 'oj/schandler' require 'oj/oj' # C extension oj-2.5.3/lib/oj/0000755000004100000410000000000012263716750013350 5ustar www-datawww-dataoj-2.5.3/lib/oj/schandler.rb0000644000004100000410000000313512263716750015642 0ustar www-datawww-datamodule Oj # A Simple Callback Parser (SCP) for JSON. The Oj::ScHandler class should be # subclassed and then used with the Oj.sc_parse() method. The Scp methods will # then be called as the file is parsed. The handler does not have to be a # subclass of the ScHandler class as long as it responds to the desired # methods. # # @example # # require 'oj' # # class MyHandler < ::Oj::ScHandler # def initialize() # @hash_cnt = 0 # end # # def start_hash() # @hash_cnt += 1 # end # end # # cnt = MyHandler.new() # File.open('any.json', 'r') do |f| # Oj.sc_parse(cnt, f) # end # # To make the desired methods active while parsing the desired method should # be made public in the subclasses. If the methods remain private they will # not be called during parsing. # # def hash_start(); end # def hash_end(); end # def array_start(); end # def array_end(); end # def add_value(value); end # def error(message, line, column); end # class ScHandler # Create a new instance of the ScHandler class. def initialize() end # To make the desired methods active while parsing the desired method should # be made public in the subclasses. If the methods remain private they will # not be called during parsing. private def hash_start() end def hash_end() end def array_start() end def array_end() end def add_value(value) end def hash_set(h, key, value) end def array_append(a, value) end end # ScHandler end # Oj oj-2.5.3/lib/oj/saj.rb0000644000004100000410000000277112263716750014461 0ustar www-datawww-datamodule Oj # A SAX style parse handler for JSON hence the acronym SAJ for Simple API for # JSON. The Oj::Saj handler class should be subclassed and then used with # the Oj.sajkey_parse() method. The Saj methods will then be called as the # file is parsed. # # @example # # require 'oj' # # class MySaj < ::Oj::Saj # def initialize() # @hash_cnt = 0 # end # # def start_hash(key) # @hash_cnt += 1 # end # end # # cnt = MySaj.new() # File.open('any.xml', 'r') do |f| # Oj.saj_parse(cnt, f) # end # # To make the desired methods active while parsing the desired method should # be made public in the subclasses. If the methods remain private they will # not be called during parsing. # # def hash_start(key); end # def hash_end(key); end # def array_start(key); end # def array_end(key); end # def add_value(value, key); end # def error(message, line, column); end # class Saj # Create a new instance of the Saj handler class. def initialize() end # To make the desired methods active while parsing the desired method should # be made public in the subclasses. If the methods remain private they will # not be called during parsing. private def hash_start(key) end def hash_end(key) end def array_start(key) end def array_end(key) end def add_value(value, key) end def error(message, line, column) end end # Saj end # Oj oj-2.5.3/lib/oj/version.rb0000644000004100000410000000010712263716750015360 0ustar www-datawww-data module Oj # Current version of the module. VERSION = '2.5.3' end oj-2.5.3/lib/oj/bag.rb0000644000004100000410000000671512263716750014437 0ustar www-datawww-data module Oj # A generic class that is used only for storing attributes. It is the base # Class for auto-generated classes in the storage system. Instance variables # are added using the instance_variable_set() method. All instance variables # can be accessed using the variable name (without the @ prefix). No setters # are provided as the Class is intended for reading only. class Bag # The initializer can take multiple arguments in the form of key values # where the key is the variable name and the value is the variable # value. This is intended for testing purposes only. # @example Oj::Bag.new(:@x => 42, :@y => 57) # @param [Hash] args instance variable symbols and their values def initialize(args={ }) args.each do |k,v| self.instance_variable_set(k, v) end end # Replaces the Object.respond_to?() method. # @param [Symbol] m method symbol # @return [Boolean] true for any method that matches an instance # variable reader, otherwise false. def respond_to?(m) return true if super at_m = ('@' + m.to_s).to_sym instance_variables.include?(at_m) end # Handles requests for variable values. Others cause an Exception to be # raised. # @param [Symbol] m method symbol # @return [Boolean] the value of the specified instance variable. # @raise [ArgumentError] if an argument is given. Zero arguments expected. # @raise [NoMethodError] if the instance variable is not defined. def method_missing(m, *args, &block) raise ArgumentError.new("wrong number of arguments (#{args.size} for 0) to method #{m}") unless args.nil? or args.empty? at_m = ('@' + m.to_s).to_sym raise NoMethodError.new("undefined method #{m}", m) unless instance_variable_defined?(at_m) instance_variable_get(at_m) end # Replaces eql?() with something more reasonable for this Class. # @param [Object] other Object to compare self to # @return [Boolean] true if each variable and value are the same, otherwise false. def eql?(other) return false if (other.nil? or self.class != other.class) ova = other.instance_variables iv = instance_variables return false if ova.size != iv.size iv.each do |vid| return false if instance_variable_get(vid) != other.instance_variable_get(vid) end true end alias == eql? # Define a new class based on the Oj::Bag class. This is used internally in # the Oj module and is available to service wrappers that receive XML # requests that include Objects of Classes not defined in the storage # process. # @param [String] classname Class name or symbol that includes Module names. # @return [Object] an instance of the specified Class. # @raise [NameError] if the classname is invalid. def self.define_class(classname) classname = classname.to_s unless classname.is_a?(String) tokens = classname.split('::').map { |n| n.to_sym } raise NameError.new("Invalid classname '#{classname}") if tokens.empty? m = Object tokens[0..-2].each do |sym| if m.const_defined?(sym) m = m.const_get(sym) else c = Module.new m.const_set(sym, c) m = c end end sym = tokens[-1] if m.const_defined?(sym) c = m.const_get(sym) else c = Class.new(Oj::Bag) m.const_set(sym, c) end c end end # Bag end # Oj oj-2.5.3/lib/oj/mimic.rb0000644000004100000410000000074012263716750014774 0ustar www-datawww-data module Oj def self.mimic_loaded(mimic_paths=[]) $LOAD_PATH.each do |d| next unless File.exist?(d) offset = d.size() + 1 Dir.glob(File.join(d, '**', '*.rb')).each do |file| next unless file[offset..-1].start_with?('json') $LOADED_FEATURES << file unless $LOADED_FEATURES.include?(file) end end mimic_paths.each { |p| $LOADED_FEATURES << p } $LOADED_FEATURES << 'json' unless $LOADED_FEATURES.include?('json') end end oj-2.5.3/lib/oj/error.rb0000644000004100000410000000104112263716750015022 0ustar www-datawww-data module Oj class Error < StandardError end # Error # An Exception that is raised as a result of a parse error while parsing a JSON document. class ParseError < Error end # ParseError # An Exception that is raised as a result of a path being too deep. class DepthError < Error end # DepthError # An Exception that is raised if a file fails to load. class LoadError < Error end # LoadError # An Exception that is raised if there is a conflict with mimicing JSON class MimicError < Error end # MimicError end # Oj oj-2.5.3/metadata.yml0000644000004100000410000000470012263716750014476 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: oj version: !ruby/object:Gem::Version version: 2.5.3 platform: ruby authors: - Peter Ohler autorequire: bindir: bin cert_chain: [] date: 2014-01-03 00:00:00.000000000 Z dependencies: [] description: 'The fastest JSON parser and object serializer. ' email: peter@ohler.com executables: [] extensions: - ext/oj/extconf.rb extra_rdoc_files: - README.md files: - LICENSE - README.md - ext/oj/buf.h - ext/oj/cache8.c - ext/oj/cache8.h - ext/oj/circarray.c - ext/oj/circarray.h - ext/oj/compat.c - ext/oj/dump.c - ext/oj/encode.h - ext/oj/err.c - ext/oj/err.h - ext/oj/extconf.rb - ext/oj/fast.c - ext/oj/hash.c - ext/oj/hash.h - ext/oj/hash_test.c - ext/oj/object.c - ext/oj/odd.c - ext/oj/odd.h - ext/oj/oj.c - ext/oj/oj.h - ext/oj/parse.c - ext/oj/parse.h - ext/oj/resolve.c - ext/oj/resolve.h - ext/oj/saj.c - ext/oj/scp.c - ext/oj/strict.c - ext/oj/val_stack.c - ext/oj/val_stack.h - lib/oj.rb - lib/oj/bag.rb - lib/oj/error.rb - lib/oj/mimic.rb - lib/oj/saj.rb - lib/oj/schandler.rb - lib/oj/version.rb - test/a.rb - test/bug.rb - test/debian_test.rb - test/e.rb - test/files.rb - test/foo.rb - test/lots.rb - test/mj.rb - test/perf.rb - test/perf_compat.rb - test/perf_fast.rb - test/perf_object.rb - test/perf_saj.rb - test/perf_scp.rb - test/perf_simple.rb - test/perf_strict.rb - test/sample.rb - test/sample/change.rb - test/sample/dir.rb - test/sample/doc.rb - test/sample/file.rb - test/sample/group.rb - test/sample/hasprops.rb - test/sample/layer.rb - test/sample/line.rb - test/sample/oval.rb - test/sample/rect.rb - test/sample/shape.rb - test/sample/text.rb - test/sample_json.rb - test/struct.rb - test/test_compat.rb - test/test_fast.rb - test/test_gc.rb - test/test_mimic.rb - test/test_mimic_after.rb - test/test_object.rb - test/test_saj.rb - test/test_scp.rb - test/test_strict.rb - test/test_writer.rb - test/tests.rb - test/x.rb homepage: http://www.ohler.com/oj licenses: - MIT - GPL-3.0 metadata: {} post_install_message: rdoc_options: - "--main" - README.md require_paths: - lib - ext required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: oj rubygems_version: 2.2.0 signing_key: specification_version: 4 summary: A fast JSON parser and serializer. test_files: [] has_rdoc: true oj-2.5.3/test/0000755000004100000410000000000012263716750013151 5ustar www-datawww-dataoj-2.5.3/test/perf_simple.rb0000644000004100000410000001602612263716750016010 0ustar www-datawww-data#!/usr/bin/env ruby -wW1 # encoding: UTF-8 $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' require 'yajl' require 'json' require 'json/pure' require 'json/ext' require 'msgpack' require 'oj' require 'ox' class Jazz def initialize() @boolean = true @number = 58 @string = "A string" @array = [true, false, nil] @hash = { 'one' => 1, 'two' => 2 } end def to_json() %{ { "boolean":#{@boolean}, "number":#{@number}, "string":#{@string}, "array":#{@array}, "hash":#{@hash}, } } end def to_hash() { 'boolean' => @boolean, 'number' => @number, 'string' => @string, 'array' => @array, 'hash' => @hash, } end def to_msgpack(out='') to_hash().to_msgpack(out) end end $indent = 2 $iter = 10000 $with_object = true $with_bignum = true $with_nums = true opts = OptionParser.new opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i } opts.on("-o", "without objects") { $with_object = false } opts.on("-b", "without bignum") { $with_bignum = false } opts.on("-n", "without numbers") { $with_nums = false } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) if $with_nums obj = { 'a' => 'Alpha', 'b' => true, 'c' => 12345, 'd' => [ true, [false, [12345, nil], 3.967, ['something', false], nil]], 'e' => { 'one' => 1, 'two' => 2 }, 'f' => nil, } obj['g'] = Jazz.new() if $with_object obj['h'] = 12345678901234567890123456789 if $with_bignum else obj = { 'a' => 'Alpha', 'b' => true, 'c' => '12345', 'd' => [ true, [false, ['12345', nil], '3.967', ['something', false], nil]], 'e' => { 'one' => '1', 'two' => '2' }, 'f' => nil, } end Oj.default_options = { :indent => $indent, :mode => :object } s = Oj.dump(obj) xml = Ox.dump(obj, :indent => $indent) puts # Put Oj in strict mode so it only create JSON native types instead of the # original Ruby Objects. None of the other packages other than Ox support # Object recreation so no need for Oj to do it in the performance tests. Oj.default_options = { :mode => :strict } parse_results = { :oj => 0.0, :yajl => 0.0, :msgpack => 0.0, :pure => 0.0, :ext => 0.0, :ox => 0.0 } start = Time.now $iter.times do Oj.load(s) end dt = Time.now - start base_dt = dt parse_results[:oj] = dt puts "%d Oj.load()s in %0.3f seconds or %0.1f loads/msec" % [$iter, dt, $iter/dt/1000.0] start = Time.now $iter.times do Yajl::Parser.parse(s) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'Yajl' end parse_results[:yajl] = dt puts "%d Yajl::Parser.parse()s in %0.3f seconds or %0.1f parses/msec" % [$iter, dt, $iter/dt/1000.0] begin JSON.parser = JSON::Ext::Parser start = Time.now $iter.times do JSON.parse(s) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'JSON::Ext' end parse_results[:ext] = dt puts "%d JSON::Ext::Parser parse()s in %0.3f seconds or %0.1f parses/msec" % [$iter, dt, $iter/dt/1000.0] rescue Exception => e puts "JSON::Ext failed: #{e.class}: #{e.message}" end begin JSON.parser = JSON::Pure::Parser start = Time.now $iter.times do JSON.parse(s) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'JSON::Pure' end parse_results[:pure] = dt puts "%d JSON::Pure::Parser parse()s in %0.3f seconds or %0.1f parses/msec" % [$iter, dt, $iter/dt/1000.0] rescue Exception => e puts "JSON::Pure failed: #{e.class}: #{e.message}" end begin mp = MessagePack.pack(obj) start = Time.now $iter.times do MessagePack.unpack(mp) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'MessagePack' end parse_results[:msgpack] = dt puts "%d MessagePack.unpack()s in %0.3f seconds or %0.1f packs/msec" % [$iter, dt, $iter/dt/1000.0] rescue Exception => e puts "MessagePack failed: #{e.class}: #{e.message}" end start = Time.now $iter.times do Ox.load(xml) end dt = Time.now - start parse_results[:ox] = dt puts "%d Ox.load()s in %0.3f seconds or %0.1f loads/msec" % [$iter, dt, $iter/dt/1000.0] puts "Parser results:" puts "gem seconds parses/msec X faster than #{base_name} (higher is better)" parse_results.each do |name,dt| if 0.0 == dt puts "#{name} failed to generate JSON" next end puts "%-7s %6.3f %5.1f %4.1f" % [name, dt, $iter/dt/1000.0, base_dt/dt] end puts # Back to object mode for best performance when dumping. Oj.default_options = { :indent => $indent, :mode => :object } dump_results = { :oj => 0.0, :yajl => 0.0, :msgpack => 0.0, :pure => 0.0, :ext => 0.0, :ox => 0.0 } start = Time.now $iter.times do Oj.dump(obj) end dt = Time.now - start base_dt = dt base_name = 'Oj' parse_results[:oj] = dt puts "%d Oj.dump()s in %0.3f seconds or %0.1f dumps/msec" % [$iter, dt, $iter/dt/1000.0] start = Time.now $iter.times do Yajl::Encoder.encode(obj) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'Yajl' end parse_results[:yajl] = dt puts "%d Yajl::Encoder.encode()s in %0.3f seconds or %0.1f encodes/msec" % [$iter, dt, $iter/dt/1000.0] begin JSON.parser = JSON::Ext::Parser start = Time.now $iter.times do JSON.generate(obj) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'JSON::Ext' end parse_results[:pure] = dt puts "%d JSON::Ext generate()s in %0.3f seconds or %0.1f generates/msec" % [$iter, dt, $iter/dt/1000.0] rescue Exception => e parse_results[:ext] = 0.0 puts "JSON::Ext failed: #{e.class}: #{e.message}" end begin JSON.parser = JSON::Pure::Parser start = Time.now $iter.times do JSON.generate(obj) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'JSON::Pure' end parse_results[:pure] = dt puts "%d JSON::Pure generate()s in %0.3f seconds or %0.1f generates/msec" % [$iter, dt, $iter/dt/1000.0] rescue Exception => e parse_results[:pure] = 0.0 puts "JSON::Pure failed: #{e.class}: #{e.message}" end begin start = Time.now $iter.times do MessagePack.pack(obj) end dt = Time.now - start if base_dt < dt base_dt = dt base_name = 'MessagePack' end parse_results[:msgpack] = dt puts "%d Msgpack()s in %0.3f seconds or %0.1f unpacks/msec" % [$iter, dt, $iter/dt/1000.0] rescue Exception => e parse_results[:msgpack] = 0.0 puts "MessagePack failed: #{e.class}: #{e.message}" end start = Time.now $iter.times do Ox.dump(obj) end dt = Time.now - start parse_results[:ox] = dt puts "%d Ox.dump()s in %0.3f seconds or %0.1f dumps/msec" % [$iter, dt, $iter/dt/1000.0] puts "Parser results:" puts "gem seconds dumps/msec X faster than #{base_name} (higher is better)" parse_results.each do |name,dt| if 0.0 == dt puts "#{name} failed to generate JSON" next end puts "%-7s %6.3f %5.1f %4.1f" % [name, dt, $iter/dt/1000.0, base_dt/dt] end puts oj-2.5.3/test/tests.rb0000755000004100000410000007101012263716750014642 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'test/unit' require 'stringio' require 'date' require 'bigdecimal' require 'oj' $ruby = RUBY_DESCRIPTION.split(' ')[0] $ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition') def hash_eql(h1, h2) return false if h1.size != h2.size h1.keys.each do |k| return false unless h1[k] == h2[k] end true end class Jam attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def eql?(o) self.class == o.class && @x == o.x && @y == o.y end alias == eql? end# Jam class Jeez < Jam def initialize(x, y) super end def to_json() %{{"json_class":"#{self.class}","x":#{@x},"y":#{@y}}} end def self.json_create(h) self.new(h['x'], h['y']) end end# Jeez # contributed by sauliusg to fix as_json class Orange < Jam def initialize(x, y) super end def as_json() { :json_class => self.class, :x => @x, :y => @y } end def self.json_create(h) self.new(h['x'], h['y']) end end class Melon < Jam def initialize(x, y) super end def as_json() "#{x} #{y}" end def self.json_create(h) self.new(h['x'], h['y']) end end class Jazz < Jam def initialize(x, y) super end def to_hash() { 'json_class' => self.class.to_s, 'x' => @x, 'y' => @y } end def self.json_create(h) self.new(h['x'], h['y']) end end# Jazz class Range def to_hash() { 'begin' => self.begin, 'end' => self.end, 'exclude_end' => self.exclude_end? } end end # Range # define the symbol class ActiveSupport end class RailsLike attr_accessor :x def initialize(x) @x = x end def to_json(options = nil) Oj.dump(self, :mode => :compat) end end # RailsLike class Juice < ::Test::Unit::TestCase def test0_get_options opts = Oj.default_options() assert_equal({ :indent=>0, :second_precision=>9, :circular=>false, :auto_define=>false, :symbol_keys=>false, :class_cache=>true, :escape_mode=>:json, :mode=>:object, :time_format=>:unix, :bigdecimal_as_decimal=>true, :bigdecimal_load=>:auto, :allow_gc=>true, :create_id=>'json_class'}, opts) end def test0_set_options orig = { :indent=>0, :second_precision=>9, :circular=>false, :auto_define=>false, :symbol_keys=>false, :class_cache=>true, :escape_mode=>:ascii, :mode=>:object, :time_format=>:unix, :bigdecimal_as_decimal=>true, :bigdecimal_load=>:auto, :allow_gc=>true, :create_id=>'json_class'} o2 = { :indent=>4, :second_precision=>7, :circular=>true, :auto_define=>true, :symbol_keys=>true, :class_cache=>false, :escape_mode=>:json, :mode=>:compat, :time_format=>:ruby, :bigdecimal_as_decimal=>false, :bigdecimal_load=>:bigdecimal, :allow_gc=>false, :create_id=>nil} o3 = { :indent => 4 } Oj.default_options = o2 opts = Oj.default_options() assert_equal(o2, opts); Oj.default_options = o3 # see if it throws an exception Oj.default_options = orig # return to original end def test_nil dump_and_load(nil, false) end def test_true dump_and_load(true, false) end def test_false dump_and_load(false, false) end def test_fixnum dump_and_load(0, false) dump_and_load(12345, false) dump_and_load(-54321, false) dump_and_load(1, false) end def test_float dump_and_load(0.0, false) dump_and_load(12345.6789, false) dump_and_load(70.35, false) dump_and_load(-54321.012, false) dump_and_load(2.48e16, false) dump_and_load(2.48e100 * 1.0e10, false) dump_and_load(-2.48e100 * 1.0e10, false) dump_and_load(1/0.0, false) # NaN does not always == NaN json = Oj.dump(0/0.0) assert_equal('NaN', json) loaded = Oj.load(json); assert_equal(true, loaded.nan?) end def test_string dump_and_load('', false) dump_and_load('abc', false) dump_and_load("abc\ndef", false) dump_and_load("a\u0041", false) assert_equal("a\u0000a", dump_and_load("a\u0000a", false)) end def test_string_object dump_and_load('abc', false) dump_and_load(':abc', false) end def test_encode opts = Oj.default_options Oj.default_options = { :ascii_only => false } unless 'jruby' == $ruby dump_and_load("ぴーたー", false) end Oj.default_options = { :ascii_only => true } json = Oj.dump("ぴーたー") assert_equal(%{"\\u3074\\u30fc\\u305f\\u30fc"}, json) unless 'jruby' == $ruby dump_and_load("ぴーたー", false) end Oj.default_options = opts end def test_unicode # hits the 3 normal ranges and one extended surrogate pair json = %{"\\u019f\\u05e9\\u3074\\ud834\\udd1e"} obj = Oj.load(json) json2 = Oj.dump(obj, :ascii_only => true) assert_equal(json, json2) end def test_array dump_and_load([], false) dump_and_load([true, false], false) dump_and_load(['a', 1, nil], false) dump_and_load([[nil]], false) dump_and_load([[nil], 58], false) end # rails encoding tests def test_does_not_escape_entities_by_default # use Oj to create the hash since some Rubies don't deal nicely with unicode. json = %{{"key":"I <3 this\\u2028space"}} hash = Oj.load(json) out = Oj.dump(hash) assert_equal(json, out) end def test_escapes_entities_by_default_when_configured_to_do_so hash = {'key' => "I <3 this"} Oj.default_options = {:escape_mode => :xss_safe} out = Oj.dump hash assert_equal(%{{"key":"I \\u003c3 this"}}, out) end def test_escapes_entities_when_asked_to hash = {'key' => "I <3 this"} out = Oj.dump(hash, :escape_mode => :xss_safe) assert_equal(%{{"key":"I \\u003c3 this"}}, out) end def test_does_not_escape_entities_when_not_asked_to hash = {'key' => "I <3 this"} out = Oj.dump(hash, :escape_mode => :json) assert_equal(%{{"key":"I <3 this"}}, out) end def test_escapes_common_xss_vectors hash = {'key' => ""} out = Oj.dump(hash, :escape_mode => :xss_safe) assert_equal(%{{"key":"\\u003cscript\\u003ealert(123) \\u0026\\u0026 formatHD()\\u003c\\/script\\u003e"}}, out) end # Symbol def test_symbol_strict begin Oj.dump(:abc, :mode => :strict) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_symbol_null json = Oj.dump(:abc, :mode => :null) assert_equal('null', json) end def test_symbol_compat json = Oj.dump(:abc, :mode => :compat) assert_equal('"abc"', json) end def test_symbol_object Oj.default_options = { :mode => :object } #dump_and_load(''.to_sym, false) dump_and_load(:abc, false) dump_and_load(':xyz'.to_sym, false) end # Time def test_time_strict t = Time.local(2012, 1, 5, 23, 58, 7) begin Oj.dump(t, :mode => :strict) assert(false) rescue Exception assert(true) end end def test_time_null t = Time.local(2012, 1, 5, 23, 58, 7) json = Oj.dump(t, :mode => :null) assert_equal('null', json) end def test_unix_time_compat t = Time.xmlschema("2012-01-05T23:58:07.123456000+09:00") #t = Time.local(2012, 1, 5, 23, 58, 7, 123456) json = Oj.dump(t, :mode => :compat) assert_equal(%{1325775487.123456000}, json) end def test_unix_time_compat_precision t = Time.xmlschema("2012-01-05T23:58:07.123456789+09:00") #t = Time.local(2012, 1, 5, 23, 58, 7, 123456) json = Oj.dump(t, :mode => :compat, :second_precision => 5) assert_equal(%{1325775487.12346}, json) t = Time.xmlschema("2012-01-05T23:58:07.999600+09:00") json = Oj.dump(t, :mode => :compat, :second_precision => 3) assert_equal(%{1325775488.000}, json) end def test_unix_time_compat_early t = Time.xmlschema("1954-01-05T00:00:00.123456789+00:00") json = Oj.dump(t, :mode => :compat, :second_precision => 5) assert_equal(%{-504575999.87654}, json) end def test_unix_time_compat_1970 t = Time.xmlschema("1970-01-01T00:00:00.123456789+00:00") json = Oj.dump(t, :mode => :compat, :second_precision => 5) assert_equal(%{0.12346}, json) end def test_ruby_time_compat t = Time.xmlschema("2012-01-05T23:58:07.123456000+09:00") json = Oj.dump(t, :mode => :compat, :time_format => :ruby) #assert_equal(%{"2012-01-05 23:58:07 +0900"}, json) assert_equal(%{"#{t.to_s}"}, json) end def test_xml_time_compat begin t = Time.new(2012, 1, 5, 23, 58, 7.123456000, 34200) json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema) assert_equal(%{"2012-01-05T23:58:07.123456000+09:30"}, json) rescue Exception # some Rubies (1.8.7) do not allow the timezome to be set t = Time.local(2012, 1, 5, 23, 58, 7, 123456) json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema) tz = t.utc_offset # Ruby does not handle a %+02d properly so... sign = '+' if 0 > tz sign = '-' tz = -tz end assert_equal(%{"2012-01-05T23:58:07.123456000%s%02d:%02d"} % [sign, tz / 3600, tz / 60 % 60], json) end end def test_xml_time_compat_no_secs begin t = Time.new(2012, 1, 5, 23, 58, 7.0, 34200) json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema) assert_equal(%{"2012-01-05T23:58:07+09:30"}, json) rescue Exception # some Rubies (1.8.7) do not allow the timezome to be set t = Time.local(2012, 1, 5, 23, 58, 7, 0) json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema) tz = t.utc_offset # Ruby does not handle a %+02d properly so... sign = '+' if 0 > tz sign = '-' tz = -tz end assert_equal(%{"2012-01-05T23:58:07%s%02d:%02d"} % [sign, tz / 3600, tz / 60 % 60], json) end end def test_xml_time_compat_precision begin t = Time.new(2012, 1, 5, 23, 58, 7.123456789, 32400) json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema, :second_precision => 5) assert_equal(%{"2012-01-05T23:58:07.12346+09:00"}, json) rescue Exception # some Rubies (1.8.7) do not allow the timezome to be set t = Time.local(2012, 1, 5, 23, 58, 7, 123456) json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema, :second_precision => 5) tz = t.utc_offset # Ruby does not handle a %+02d properly so... sign = '+' if 0 > tz sign = '-' tz = -tz end assert_equal(%{"2012-01-05T23:58:07.12346%s%02d:%02d"} % [sign, tz / 3600, tz / 60 % 60], json) end end def test_xml_time_compat_precision_round begin t = Time.new(2012, 1, 5, 23, 58, 7.9996, 32400) json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema, :second_precision => 3) assert_equal(%{"2012-01-05T23:58:08+09:00"}, json) rescue Exception # some Rubies (1.8.7) do not allow the timezome to be set t = Time.local(2012, 1, 5, 23, 58, 7, 999600) json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema, :second_precision => 3) tz = t.utc_offset # Ruby does not handle a %+02d properly so... sign = '+' if 0 > tz sign = '-' tz = -tz end assert_equal(%{"2012-01-05T23:58:08%s%02d:%02d"} % [sign, tz / 3600, tz / 60 % 60], json) end end def test_xml_time_compat_zulu begin t = Time.new(2012, 1, 5, 23, 58, 7.0, 0) json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema) assert_equal(%{"2012-01-05T23:58:07Z"}, json) rescue Exception # some Rubies (1.8.7) do not allow the timezome to be set t = Time.utc(2012, 1, 5, 23, 58, 7, 0) json = Oj.dump(t, :mode => :compat, :time_format => :xmlschema) #tz = t.utc_offset assert_equal(%{"2012-01-05T23:58:07Z"}, json) end end def test_time_object t = Time.now() Oj.default_options = { :mode => :object } dump_and_load(t, false) end def test_time_object_early t = Time.xmlschema("1954-01-05T00:00:00.123456") Oj.default_options = { :mode => :object } dump_and_load(t, false) end # Class def test_class_strict begin Oj.dump(Juice, :mode => :strict) assert(false) rescue Exception assert(true) end end def test_class_null json = Oj.dump(Juice, :mode => :null) assert_equal('null', json) end def test_class_compat json = Oj.dump(Juice, :mode => :compat) assert_equal(%{"Juice"}, json) end def test_class_object Oj.default_options = { :mode => :object } dump_and_load(Juice, false) end # Hash def test_hash Oj.default_options = { :mode => :strict } dump_and_load({}, false) dump_and_load({ 'true' => true, 'false' => false}, false) dump_and_load({ 'true' => true, 'array' => [], 'hash' => { }}, false) end def test_non_str_hash_strict begin Oj.dump({ 1 => true, 0 => false }, :mode => :strict) assert(false) rescue Exception assert(true) end end def test_non_str_hash_null begin Oj.dump({ 1 => true, 0 => false }, :mode => :null) assert(false) rescue Exception assert(true) end end def test_non_str_hash_compat json = Oj.dump({ 1 => true, 0 => false }, :mode => :compat) h = Oj.load(json, :mode => :strict) assert_equal({ "1" => true, "0" => false }, h) end def test_non_str_hash_object Oj.default_options = { :mode => :object } json = Oj.dump({ 1 => true, :sim => nil }) h = Oj.load(json, :mode => :strict) assert_equal({"^#1" => [1, true], ":sim" => nil}, h) h = Oj.load(json) assert_equal({ 1 => true, :sim => nil }, h) end def test_mixed_hash_object Oj.default_options = { :mode => :object } json = Oj.dump({ 1 => true, 'nil' => nil, :sim => 4 }) h = Oj.load(json, :mode => :strict) assert_equal({"^#1" => [1, true], "nil" => nil, ":sim" => 4}, h) h = Oj.load(json) assert_equal({ 1 => true, 'nil' => nil, :sim => 4 }, h) end # Object with to_json() def test_json_object_strict obj = Jeez.new(true, 58) begin Oj.dump(obj, :mode => :strict) assert(false) rescue Exception assert(true) end end def test_json_object_null obj = Jeez.new(true, 58) json = Oj.dump(obj, :mode => :null) assert_equal('null', json) end def test_json_object_compat Oj.default_options = { :mode => :compat } obj = Jeez.new(true, 58) json = Oj.dump(obj, :indent => 2) assert(%{{"json_class":"Jeez","x":true,"y":58}} == json || %{{"json_class":"Jeez","y":58,"x":true}} == json) dump_and_load(obj, false) end def test_json_object_create_id Oj.default_options = { :mode => :compat, :create_id => 'kson_class' } expected = Jeez.new(true, 58) json = %{{"kson_class":"Jeez","x":true,"y":58}} obj = Oj.load(json) assert_equal(expected, obj) Oj.default_options = { :create_id => 'json_class' } end def test_json_object_object obj = Jeez.new(true, 58) json = Oj.dump(obj, :mode => :object, :indent => 2) assert(%{{ "^o":"Jeez", "x":true, "y":58 }} == json || %{{ "^o":"Jeez", "y":58, "x":true }} == json) obj2 = Oj.load(json, :mode => :object) assert_equal(obj, obj2) end # Object with to_hash() def test_to_hash_object_strict obj = Jazz.new(true, 58) begin Oj.dump(obj, :mode => :strict) assert(false) rescue Exception assert(true) end end def test_to_hash_object_null obj = Jazz.new(true, 58) json = Oj.dump(obj, :mode => :null) assert_equal('null', json) end def test_to_hash_object_compat obj = Jazz.new(true, 58) json = Oj.dump(obj, :mode => :compat, :indent => 2) h = Oj.load(json, :mode => :strict) assert_equal(obj.to_hash, h) end def test_to_hash_object_object obj = Jazz.new(true, 58) json = Oj.dump(obj, :mode => :object, :indent => 2) assert(%{{ "^o":"Jazz", "x":true, "y":58 }} == json || %{{ "^o":"Jazz", "y":58, "x":true }} == json) obj2 = Oj.load(json, :mode => :object) assert_equal(obj, obj2) end # Object with as_json() # contributed by sauliusg def test_as_json_object_strict obj = Orange.new(true, 58) begin Oj.dump(obj, :mode => :strict) assert(false) rescue Exception assert(true) end end def test_as_json_object_null obj = Orange.new(true, 58) json = Oj.dump(obj, :mode => :null) assert_equal('null', json) end def test_as_json_object_compat_hash Oj.default_options = { :mode => :compat } obj = Orange.new(true, 58) json = Oj.dump(obj, :indent => 2) assert(!json.nil?) dump_and_load(obj, false) end def test_as_json_object_compat_non_hash Oj.default_options = { :mode => :compat } obj = Melon.new(true, 58) json = Oj.dump(obj, :indent => 2) assert_equal(%{"true 58"}, json) end def test_as_json_object_object obj = Orange.new(true, 58) json = Oj.dump(obj, :mode => :object, :indent => 2) assert(%{{ "^o":"Orange", "x":true, "y":58 }} == json || %{{ "^o":"Orange", "y":58, "x":true }} == json) obj2 = Oj.load(json, :mode => :object) assert_equal(obj, obj2) end # Object without to_json() or to_hash() def test_object_strict obj = Jam.new(true, 58) begin Oj.dump(obj, :mode => :strict) assert(false) rescue Exception assert(true) end end def test_object_null obj = Jam.new(true, 58) json = Oj.dump(obj, :mode => :null) assert_equal('null', json) end def test_object_compat obj = Jam.new(true, 58) json = Oj.dump(obj, :mode => :compat, :indent => 2) assert(%{{ "x":true, "y":58 }} == json || %{{ "y":58, "x":true }} == json) end def test_object_object obj = Jam.new(true, 58) json = Oj.dump(obj, :mode => :object, :indent => 2) assert(%{{ "^o":"Jam", "x":true, "y":58 }} == json || %{{ "^o":"Jam", "y":58, "x":true }} == json) obj2 = Oj.load(json, :mode => :object) assert_equal(obj, obj2) end def test_object_object_no_cache obj = Jam.new(true, 58) json = Oj.dump(obj, :mode => :object, :indent => 2) assert(%{{ "^o":"Jam", "x":true, "y":58 }} == json || %{{ "^o":"Jam", "y":58, "x":true }} == json) obj2 = Oj.load(json, :mode => :object, :class_cache => false) assert_equal(obj, obj2) end # Exception def test_exception err = nil begin raise StandardError.new('A Message') assert(false) rescue Exception => e err = e end json = Oj.dump(err, :mode => :object, :indent => 2) #puts "*** #{json}" e2 = Oj.load(json, :mode => :strict) assert_equal(err.class.to_s, e2['^o']) assert_equal(err.message, e2['~mesg']) assert_equal(err.backtrace, e2['~bt']) e2 = Oj.load(json, :mode => :object) if RUBY_VERSION.start_with?('1.8') || 'rubinius' == $ruby assert_equal(e.class, e2.class); assert_equal(e.message, e2.message); assert_equal(e.backtrace, e2.backtrace); else assert_equal(e, e2); end end # Range def test_range_strict begin Oj.dump(1..7, :mode => :strict) assert(false) rescue Exception assert(true) end end def test_range_null json = Oj.dump(1..7, :mode => :null) assert_equal('null', json) end def test_range_compat json = Oj.dump(1..7, :mode => :compat) h = Oj.load(json, :mode => :strict) assert_equal({'begin' => 1, 'end' => 7, 'exclude_end' => false}, h) json = Oj.dump(1...7, :mode => :compat) h = Oj.load(json, :mode => :strict) assert_equal({'begin' => 1, 'end' => 7, 'exclude_end' => true}, h) end def test_range_object unless RUBY_VERSION.start_with?('1.8') Oj.default_options = { :mode => :object } json = Oj.dump(1..7, :mode => :object, :indent => 0) if 'rubinius' == $ruby assert(%{{"^O":"Range","begin":1,"end":7,"exclude_end?":false}} == json) elsif 'jruby' == $ruby assert(%{{"^O":"Range","begin":1,"end":7,"exclude_end?":false}} == json) else assert_equal(%{{"^u":["Range",1,7,false]}}, json) end dump_and_load(1..7, false) dump_and_load(1..1, false) dump_and_load(1...7, false) end end # BigNum def test_bignum_strict json = Oj.dump(7 ** 55, :mode => :strict) assert_equal('30226801971775055948247051683954096612865741943', json) end def test_bignum_null json = Oj.dump(7 ** 55, :mode => :null) assert_equal('30226801971775055948247051683954096612865741943', json) end def test_bignum_compat json = Oj.dump(7 ** 55, :mode => :compat) b = Oj.load(json, :mode => :strict) assert_equal(30226801971775055948247051683954096612865741943, b) end def test_bignum_object dump_and_load(7 ** 55, false) end # BigDecimal def test_bigdecimal_strict mode = Oj.default_options[:mode] Oj.default_options = {:mode => :strict} dump_and_load(BigDecimal.new('3.14159265358979323846'), false) Oj.default_options = {:mode => mode} end def test_bigdecimal_null mode = Oj.default_options[:mode] Oj.default_options = {:mode => :null} dump_and_load(BigDecimal.new('3.14159265358979323846'), false) Oj.default_options = {:mode => mode} end def test_bigdecimal_compat orig = BigDecimal.new('80.51') json = Oj.dump(orig, :mode => :compat, :bigdecimal_as_decimal => false) bg = Oj.load(json, :mode => :compat) assert_equal(orig.to_s, bg) orig = BigDecimal.new('3.14159265358979323846') json = Oj.dump(orig, :mode => :compat, :bigdecimal_as_decimal => false) bg = Oj.load(json, :mode => :compat) assert_equal(orig.to_s, bg) end def test_bigdecimal_load orig = BigDecimal.new('80.51') json = Oj.dump(orig, :mode => :compat, :bigdecimal_as_decimal => true) bg = Oj.load(json, :mode => :compat, :bigdecimal_load => true) assert_equal(BigDecimal, bg.class) assert_equal(orig, bg) end def test_float_load orig = BigDecimal.new('80.51') json = Oj.dump(orig, :mode => :compat, :bigdecimal_as_decimal => true) bg = Oj.load(json, :mode => :compat, :bigdecimal_load => :float) assert_equal(Float, bg.class) assert_equal(orig.to_f, bg) end def test_bigdecimal_compat_to_json orig = BigDecimal.new('80.51') BigDecimal.send(:define_method, :to_json) do %{"this is big"} end json = Oj.dump(orig, :mode => :compat) bg = Oj.load(json, :mode => :compat) assert_equal("this is big", bg) BigDecimal.send(:remove_method, :to_json) # cleanup end def test_bigdecimal_object mode = Oj.default_options[:mode] Oj.default_options = {:mode => :object} dump_and_load(BigDecimal.new('3.14159265358979323846'), false) Oj.default_options = {:mode => mode} # Infinity is the same for Float and BigDecimal json = Oj.dump(BigDecimal.new('Infinity'), :mode => :object) assert_equal('Infinity', json) json = Oj.dump(BigDecimal.new('-Infinity'), :mode => :object) assert_equal('-Infinity', json) end # Date def test_date_strict begin Oj.dump(Date.new(2012, 6, 19), :mode => :strict) assert(false) rescue Exception assert(true) end end def test_date_null json = Oj.dump(Date.new(2012, 6, 19), :mode => :null) assert_equal('null', json) end def test_date_compat orig = Date.new(2012, 6, 19) json = Oj.dump(orig, :mode => :compat) x = Oj.load(json, :mode => :compat) # Some Rubies implement Date as data and some as a real Object. Either are # okay for the test. if x.is_a?(String) assert_equal(orig.to_s, x) else # better be a Hash assert_equal({"year" => orig.year, "month" => orig.month, "day" => orig.day, "start" => orig.start}, x) end end def test_date_object dump_and_load(Date.new(2012, 6, 19), false) end # DateTime def test_datetime_strict begin Oj.dump(DateTime.new(2012, 6, 19, 20, 19, 27), :mode => :strict) assert(false) rescue Exception assert(true) end end def test_datetime_null json = Oj.dump(DateTime.new(2012, 6, 19, 20, 19, 27), :mode => :null) assert_equal('null', json) end def test_datetime_compat orig = DateTime.new(2012, 6, 19, 20, 19, 27) json = Oj.dump(orig, :mode => :compat) x = Oj.load(json, :mode => :compat) # Some Rubies implement Date as data and some as a real Object. Either are # okay for the test. assert_equal(orig.to_s, x) end def test_datetime_object dump_and_load(DateTime.new(2012, 6, 19), false) end # autodefine Oj::Bag def test_bag json = %{{ "^o":"Jem", "x":true, "y":58 }} obj = Oj.load(json, :mode => :object, :auto_define => true) assert_equal('Jem', obj.class.name) assert_equal(true, obj.x) assert_equal(58, obj.y) end # Circular def test_circular_object obj = Jam.new(nil, 58) obj.x = obj json = Oj.dump(obj, :mode => :object, :indent => 2, :circular => true) assert(%{{ "^o":"Jam", "^i":1, "x":"^r1", "y":58 }} == json || %{{ "^o":"Jam", "^i":1, "y":58, "x":"^r1" }} == json) obj2 = Oj.load(json, :mode => :object, :circular => true) assert_equal(obj2.x.__id__, obj2.__id__) end def test_circular_hash h = { 'a' => 7 } h['b'] = h json = Oj.dump(h, :mode => :object, :indent => 2, :circular => true) ha = Oj.load(json, :mode => :strict) assert_equal({'^i' => 1, 'a' => 7, 'b' => '^r1'}, ha) Oj.load(json, :mode => :object, :circular => true) assert_equal(h['b'].__id__, h.__id__) end def test_circular_array a = [7] a << a json = Oj.dump(a, :mode => :object, :indent => 2, :circular => true) assert_equal(%{[ "^i1", 7, "^r1" ]}, json) a2 = Oj.load(json, :mode => :object, :circular => true) assert_equal(a2[1].__id__, a2.__id__) end def test_circular h = { 'a' => 7 } obj = Jam.new(h, 58) obj.x['b'] = obj json = Oj.dump(obj, :mode => :object, :indent => 2, :circular => true) ha = Oj.load(json, :mode => :strict) assert_equal({'^o' => 'Jam', '^i' => 1, 'x' => { '^i' => 2, 'a' => 7, 'b' => '^r1' }, 'y' => 58 }, ha) Oj.load(json, :mode => :object, :circular => true) assert_equal(obj.x.__id__, h.__id__) assert_equal(h['b'].__id__, obj.__id__) end # Stream Deeply Nested def test_deep_nest #unless 'jruby' == RUBY_DESCRIPTION.split(' ')[0] begin n = 10000 Oj.strict_load('[' * n + ']' * n) rescue Exception => e assert(false, e.message) end end # Stream IO def test_io_string src = { 'x' => true, 'y' => 58, 'z' => [1, 2, 3]} output = StringIO.open("", "w+") Oj.to_stream(output, src) input = StringIO.new(output.string()) obj = Oj.load(input, :mode => :strict) assert_equal(src, obj) end def test_io_file src = { 'x' => true, 'y' => 58, 'z' => [1, 2, 3]} filename = 'open_file_test.json' File.open(filename, "w") { |f| Oj.to_stream(f, src) } f = File.new(filename) obj = Oj.load(f, :mode => :strict) f.close() assert_equal(src, obj) end # symbol_keys option def test_symbol_keys json = %{{ "x":true, "y":58, "z": [1,2,3] } } obj = Oj.load(json, :mode => :strict, :symbol_keys => true) assert_equal({ :x => true, :y => 58, :z => [1, 2, 3]}, obj) end # comments def test_comment_slash json = %{{ "x":true,//three "y":58, "z": [1,2, 3 // six ]} } obj = Oj.load(json, :mode => :strict) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment_c json = %{{ "x"/*one*/:/*two*/true, "y":58, "z": [1,2,3]} } obj = Oj.load(json, :mode => :strict) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment json = %{{ "x"/*one*/:/*two*/true,//three "y":58/*four*/, "z": [1,2/*five*/, 3 // six ] } } obj = Oj.load(json, :mode => :strict) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end # Rails re-call test. Active support recalls the json dumper when the to_json # method is called. This mimics that and verifies Oj detects it. def test_rails_like obj = RailsLike.new(3) json = Oj.dump(obj, :mode => :compat) assert_equal('{"x":3}', json) end def dump_and_load(obj, trace=false) json = Oj.dump(obj, :indent => 2) puts json if trace loaded = Oj.load(json); assert_equal(obj, loaded) loaded end end oj-2.5.3/test/test_scp.rb0000755000004100000410000001230312263716750015324 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'test/unit' require 'bigdecimal' require 'oj' require 'pp' $json = %{{ "array": [ { "num" : 3, "string": "message", "hash" : { "h2" : { "a" : [ 1, 2, 3 ] } } } ], "boolean" : true }} class NoHandler < Oj::ScHandler def initialize() end end class AllHandler < Oj::ScHandler attr_accessor :calls def initialize() @calls = [] end def hash_start() @calls << [:hash_start] {} end def hash_end() @calls << [:hash_end] end def array_start() @calls << [:array_start] [] end def array_end() @calls << [:array_end] end def add_value(value) @calls << [:add_value, value] end def hash_set(h, key, value) @calls << [:hash_set, key, value] end def array_append(a, value) @calls << [:array_append, value] end end # AllHandler class ScpTest < ::Test::Unit::TestCase def test_nil handler = AllHandler.new() json = %{null} Oj.sc_parse(handler, json) assert_equal([[:add_value, nil]], handler.calls) end def test_true handler = AllHandler.new() json = %{true} Oj.sc_parse(handler, json) assert_equal([[:add_value, true]], handler.calls) end def test_false handler = AllHandler.new() json = %{false} Oj.sc_parse(handler, json) assert_equal([[:add_value, false]], handler.calls) end def test_string handler = AllHandler.new() json = %{"a string"} Oj.sc_parse(handler, json) assert_equal([[:add_value, 'a string']], handler.calls) end def test_fixnum handler = AllHandler.new() json = %{12345} Oj.sc_parse(handler, json) assert_equal([[:add_value, 12345]], handler.calls) end def test_float handler = AllHandler.new() json = %{12345.6789} Oj.sc_parse(handler, json) assert_equal([[:add_value, 12345.6789]], handler.calls) end def test_float_exp handler = AllHandler.new() json = %{12345.6789e7} Oj.sc_parse(handler, json) assert_equal(1, handler.calls.size) assert_equal(:add_value, handler.calls[0][0]) assert_equal((12345.6789e7 * 10000).to_i, (handler.calls[0][1] * 10000).to_i) end def test_array_empty handler = AllHandler.new() json = %{[]} Oj.sc_parse(handler, json) assert_equal([[:array_start], [:array_end], [:add_value, []]], handler.calls) end def test_array handler = AllHandler.new() json = %{[true,false]} Oj.sc_parse(handler, json) assert_equal([[:array_start], [:array_append, true], [:array_append, false], [:array_end], [:add_value, []]], handler.calls) end def test_hash_empty handler = AllHandler.new() json = %{{}} Oj.sc_parse(handler, json) assert_equal([[:hash_start], [:hash_end], [:add_value, {}]], handler.calls) end def test_hash handler = AllHandler.new() json = %{{"one":true,"two":false}} Oj.sc_parse(handler, json) assert_equal([[:hash_start], [:hash_set, 'one', true], [:hash_set, 'two', false], [:hash_end], [:add_value, {}]], handler.calls) end def test_hash_sym handler = AllHandler.new() json = %{{"one":true,"two":false}} Oj.sc_parse(handler, json, :symbol_keys => true) assert_equal([[:hash_start], [:hash_set, :one, true], [:hash_set, :two, false], [:hash_end], [:add_value, {}]], handler.calls) end def test_full handler = AllHandler.new() Oj.sc_parse(handler, $json) assert_equal([[:hash_start], [:array_start], [:hash_start], [:hash_set, "num", 3], [:hash_set, "string", "message"], [:hash_start], [:hash_start], [:array_start], [:array_append, 1], [:array_append, 2], [:array_append, 3], [:array_end], [:hash_set, "a", []], [:hash_end], [:hash_set, "h2", {}], [:hash_end], [:hash_set, "hash", {}], [:hash_end], [:array_append, {}], [:array_end], [:hash_set, "array", []], [:hash_set, "boolean", true], [:hash_end], [:add_value, {}]], handler.calls) end def test_none handler = NoHandler.new() Oj.sc_parse(handler, $json) end def test_fixnum_bad handler = AllHandler.new() json = %{12345xyz} begin Oj.sc_parse(handler, json) rescue Exception => e assert_equal("unexpected character at line 1, column 6 [parse.c:637]", e.message) end end end oj-2.5.3/test/files.rb0000755000004100000410000000076012263716750014606 0ustar www-datawww-data#!/usr/bin/env ruby -wW2 if $0 == __FILE__ $: << '.' $: << '..' $: << '../lib' $: << '../ext' end require 'pp' require 'sample/file' require 'sample/dir' def files(dir) d = ::Sample::Dir.new(dir) Dir.new(dir).each do |fn| next if fn.start_with?('.') filename = File.join(dir, fn) #filename = '.' == dir ? fn : File.join(dir, fn) if File.directory?(filename) d << files(filename) else d << ::Sample::File.new(filename) end end #pp d d end oj-2.5.3/test/perf_strict.rb0000755000004100000410000001026112263716750016025 0ustar www-datawww-data#!/usr/bin/env ruby -wW1 # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' require 'perf' require 'oj' $verbose = false $indent = 0 $iter = 20000 $with_bignum = false $with_nums = true $size = 0 opts = OptionParser.new opts.on("-v", "verbose") { $verbose = true } opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i } opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i } opts.on("-b", "with bignum") { $with_bignum = true } opts.on("-n", "without numbers") { $with_nums = false } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) if $with_nums $obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array 'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash 'f' => nil, # nil 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } $obj['g'] = 12345678901234567890123456789 if $with_bignum else $obj = { 'a' => 'Alpha', 'b' => true, 'c' => '12345', 'd' => [ true, [false, ['12345', nil], '3.967', ['something', false], nil]], 'e' => { 'zero' => '0', 'one' => '1', 'two' => '2' }, 'f' => nil, 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } end Oj.default_options = { :indent => $indent, :mode => :strict } if 0 < $size o = $obj $obj = [] (4 * $size).times do $obj << o end end $json = Oj.dump($obj) $obj_json = Oj.dump($obj, :mode => :object) #puts "*** size: #{$obj_json.size}" $failed = {} # key is same as String used in tests later def capture_error(tag, orig, load_key, dump_key, &blk) begin obj = blk.call(orig) raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj rescue Exception => e $failed[tag] = "#{e.class}: #{e.message}" end end # Verify that all packages dump and load correctly and return the same Object as the original. capture_error('Oj:strict', $obj, 'load', 'dump') { |o| Oj.strict_load(Oj.dump(o, :mode => :strict)) } capture_error('Yajl', $obj, 'encode', 'parse') { |o| require 'yajl'; Yajl::Parser.parse(Yajl::Encoder.encode(o)) } capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| require 'json' require 'json/ext' JSON.generator = JSON::Ext::Generator JSON.parser = JSON::Ext::Parser JSON.parse(JSON.generate(o)) } capture_error('JSON::Pure', $obj, 'generate', 'parse') { |o| require 'json/pure' JSON.generator = JSON::Pure::Generator JSON.parser = JSON::Pure::Parser JSON.parse(JSON.generate(o)) } if $verbose puts "json:\n#{$json}\n" puts "object json:\n#{$obj_json}\n" puts "Oj loaded object:\n#{Oj.strict_load($json)}\n" puts "Yajl loaded object:\n#{Yajl::Parser.parse($json)}\n" puts "JSON loaded object:\n#{JSON::Ext::Parser.new($json).parse}\n" end puts '-' * 80 puts "Strict Parse Performance" perf = Perf.new() unless $failed.has_key?('JSON::Ext') perf.add('JSON::Ext', 'parse') { JSON.parse($json) } perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser } end unless $failed.has_key?('JSON::Pure') perf.add('JSON::Pure', 'parse') { JSON.parse($json) } perf.before('JSON::Pure') { JSON.parser = JSON::Pure::Parser } end unless $failed.has_key?('Oj:strict') perf.add('Oj:strict', 'strict_load') { Oj.strict_load($json) } end perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) } unless $failed.has_key?('Yajl') perf.run($iter) puts puts '-' * 80 puts unless $failed.empty? puts "The following packages were not included for the reason listed" $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" } end oj-2.5.3/test/lots.rb0000755000004100000410000000310012263716750014454 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'oj' module One module Two module Three class Empty def initialize() end def eql?(o) self.class == o.class end alias == eql? def to_hash() {'json_class' => "#{self.class.name}"} end def to_json(*a) %{{"json_class":"#{self.class.name}"}} end def self.json_create(h) self.new() end end # Empty end # Three end # Two end # One $obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array 'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash 'f' => nil, # nil 'g' => One::Two::Three::Empty.new(), 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } $obj = [$obj]*10000 Oj.default_options = { :indent => 2, :mode => :compat } $json = Oj.dump($obj, :mode => :compat) $result = nil 100.times { |i| print(".") if (0 == i % 10) $result = Oj.compat_load($json) } oj-2.5.3/test/perf_fast.rb0000755000004100000410000001420212263716750015451 0ustar www-datawww-data#!/usr/bin/env ruby -wW1 # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' require 'yajl' require 'perf' require 'json' require 'json/ext' require 'oj' $verbose = false $indent = 0 $iter = 10000 $gets = 0 $fetch = false $write = false $read = false opts = OptionParser.new opts.on("-v", "verbose") { $verbose = true } opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i } opts.on("-g", "--gets [Int]", Integer, "number of gets") { |i| $gets = i } opts.on("-f", "fetch") { $fetch = true } opts.on("-w", "write") { $write = true } opts.on("-r", "read") { $read = true } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) # This just navigates to each leaf of the JSON structure. def dig(obj, &blk) case obj when Array obj.each { |e| dig(e, &blk) } when Hash obj.values.each { |e| dig(e, &blk) } else blk.yield(obj) end end $obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, {'12345' => 12345, 'nil' => nil}, 3.967, { 'x' => 'something', 'y' => false, 'z' => true}, nil]], # mix it up array 'e' => { 'one' => 1, 'two' => 2 }, # hash 'f' => nil, # nil 'g' => 12345678901234567890123456789, # big number 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } Oj.default_options = { :indent => $indent, :mode => :compat } $json = Oj.dump($obj) $failed = {} # key is same as String used in tests later def capture_error(tag, orig, load_key, dump_key, &blk) begin obj = blk.call(orig) raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj rescue Exception => e $failed[tag] = "#{e.class}: #{e.message}" end end # Verify that all packages dump and load correctly and return the same Object as the original. capture_error('Oj::Doc', $obj, 'load', 'dump') { |o| Oj::Doc.open(Oj.dump(o, :mode => :strict)) { |f| f.fetch() } } capture_error('Yajl', $obj, 'encode', 'parse') { |o| Yajl::Parser.parse(Yajl::Encoder.encode(o)) } capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| JSON.generator = JSON::Ext::Generator; JSON::Ext::Parser.new(JSON.generate(o)).parse } if $verbose puts "json:\n#{$json}\n" end puts '-' * 80 puts "Parse Performance" perf = Perf.new() perf.add('Oj::Doc', 'parse') { Oj::Doc.open($json) {|f| } } unless $failed.has_key?('Oj::Doc') perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) } unless $failed.has_key?('Yajl') perf.add('JSON::Ext', 'parse') { JSON::Ext::Parser.new($json).parse } unless $failed.has_key?('JSON::Ext') perf.run($iter) puts '-' * 80 puts "JSON generation Performance" Oj::Doc.open($json) do |doc| perf = Perf.new() perf.add('Oj::Doc', 'dump') { doc.dump() } perf.add('Yajl', 'encode') { Yajl::Encoder.encode($obj) } perf.add('JSON::Ext', 'fast_generate') { JSON.fast_generate($obj) } perf.before('JSON::Ext') { JSON.generator = JSON::Ext::Generator } perf.run($iter) end if 0 < $gets puts '-' * 80 puts "Parse and get all values Performance" perf = Perf.new() perf.add('Oj::Doc', 'parse') { Oj::Doc.open($json) {|f| $gets.times { f.each_value() {} } } } unless $failed.has_key?('Oj::Doc') perf.add('Yajl', 'parse') { $gets.times { dig(Yajl::Parser.parse($json)) {} } } unless $failed.has_key?('Yajl') perf.add('JSON::Ext', 'parse') { $gets.times { dig(JSON::Ext::Parser.new($json).parse) {} } } unless $failed.has_key?('JSON::Ext') perf.run($iter) end if $fetch puts '-' * 80 puts "fetch nested Performance" json_hash = Oj.load($json, :mode => :strict) Oj::Doc.open($json) do |fast| #puts "*** C fetch: #{fast.fetch('/d/2/4/y')}" #puts "*** Ruby fetch: #{json_hash.fetch('d', []).fetch(1, []).fetch(3, []).fetch('x', nil)}" perf = Perf.new() perf.add('Oj::Doc', 'fetch') { fast.fetch('/d/2/4/x'); fast.fetch('/h/a/b/c/d/e/f/g'); fast.fetch('/i/1/1/1/1/1/1/1') } # version that fails gracefully perf.add('Ruby', 'fetch') do json_hash.fetch('d', []).fetch(1, []).fetch(3, []).fetch('x', nil) json_hash.fetch('h', {}).fetch('a', {}).fetch('b', {}).fetch('c', {}).fetch('d', {}).fetch('e', {}).fetch('f', {}).fetch('g', {}) json_hash.fetch('i', []).fetch(0, []).fetch(0, []).fetch(0, []).fetch(0, []).fetch(0, []).fetch(0, []).fetch(0, nil) end # version that raises if the path is incorrect # perf.add('Ruby', 'fetch') { $fetch.times { json_hash['d'][1][3][1] } } perf.run($iter) end end if $write puts '-' * 80 puts "JSON write to file Performance" Oj::Doc.open($json) do |doc| perf = Perf.new() perf.add('Oj::Doc', 'dump') { doc.dump(nil, 'oj.json') } perf.add('Yajl', 'encode') { File.open('yajl.json', 'w') { |f| Yajl::Encoder.encode($obj, f) } } perf.add('JSON::Ext', 'fast_generate') { File.open('json_ext.json', 'w') { |f| f.write(JSON.fast_generate($obj)) } } perf.before('JSON::Ext') { JSON.generator = JSON::Ext::Generator } perf.run($iter) end end if $read puts '-' * 80 puts "JSON read from file Performance" Oj::Doc.open($json) { |doc| doc.dump(nil, 'oj.json') } File.open('yajl.json', 'w') { |f| Yajl::Encoder.encode($obj, f) } JSON.generator = JSON::Ext::Generator File.open('json_ext.json', 'w') { |f| f.write(JSON.fast_generate($obj)) } Oj::Doc.open($json) do |doc| perf = Perf.new() perf.add('Oj::Doc', 'open_file') { ::Oj::Doc.open_file('oj.json') } perf.add('Yajl', 'decode') { Yajl::decoder.decode(File.read('yajl.json')) } perf.add('JSON::Ext', '') { JSON.parse(File.read('json_ext.json')) } perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser } perf.run($iter) end end unless $failed.empty? puts "The following packages were not included for the reason listed" $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" } end oj-2.5.3/test/struct.rb0000755000004100000410000000122112263716750015021 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'oj' A = Struct.new(:a,:b,:c,:d) B = Struct.new(:e,:f) obj = [A.new(55, B.new(1, 'X'), B.new(2, 'Y'), 3)] s = Oj.dump(obj, :mode => :object) 100000.times do Oj.load(s, :mode => :object) # ds = Oj.dump(o, :mode => :object) # if ds != s # puts ds # raise "holy crap" # end end oj-2.5.3/test/foo.rb0000644000004100000410000000120312263716750014255 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'oj' reltypes={} Oj::Doc.open_file('foo.json') do |doc| doc.each_child do |target| puts "#{target.local_key} is #{target.local_key.class}" target.each_leaf do |score| reltype=score.local_key reltypes[reltype] = (reltypes[reltype] || 0) + 1 end end end oj-2.5.3/test/perf.rb0000644000004100000410000000463612263716750014443 0ustar www-datawww-data class Perf def initialize() @items = [] end def add(title, op, &blk) @items << Item.new(title, op, &blk) end def before(title, &blk) @items.each do |i| if title == i.title i.set_before(&blk) break end end end def run(iter) base = Item.new(nil, nil) { } base.run(iter, 0.0) @items.each do |i| i.run(iter, base.duration) if i.error.nil? puts "#{i.title}.#{i.op} #{iter} times in %0.3f seconds or %0.3f #{i.op}/sec." % [i.duration, iter / i.duration] else puts "***** #{i.title}.#{i.op} failed! #{i.error}" end end summary() end def summary() fastest = nil slowest = nil width = 6 @items.each do |i| next if i.duration.nil? width = i.title.size if width < i.title.size end iva = @items.clone iva.delete_if { |i| i.duration.nil? } iva = iva.sort_by { |i| i.duration } puts puts "Summary:" puts "%*s time (secs) rate (ops/sec)" % [width, 'System'] puts "#{'-' * width} ----------- --------------" iva.each do |i| if i.duration.nil? else puts "%*s %11.3f %14.3f" % [width, i.title, i.duration, i.rate ] end end puts puts "Comparison Matrix\n(performance factor, 2.0 means row is twice as fast as column)" puts ([' ' * width] + iva.map { |i| "%*s" % [width, i.title] }).join(' ') puts (['-' * width] + iva.map { |i| '-' * width }).join(' ') iva.each do |i| line = ["%*s" % [width, i.title]] iva.each do |o| line << "%*.2f" % [width, o.duration / i.duration] end puts line.join(' ') end puts end class Item attr_accessor :title attr_accessor :op attr_accessor :blk attr_accessor :duration attr_accessor :rate attr_accessor :error def initialize(title, op, &blk) @title = title @blk = blk @op = op @duration = nil @rate = nil @error = nil @before = nil end def set_before(&blk) @before = blk end def run(iter, base) begin GC.start @before.call unless @before.nil? start = Time.now iter.times { @blk.call } @duration = Time.now - start - base @duration = 0.0 if @duration < 0.0 @rate = iter / @duration rescue Exception => e @error = "#{e.class}: #{e.message}" end end end # Item end # Perf oj-2.5.3/test/e.rb0000755000004100000410000000034712263716750013731 0ustar www-datawww-data#!/usr/bin/env ruby $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'oj' json = %{"\xc2\xa9\xc3\x98"} puts "original:\n#{json}" str = Oj.load(json) puts "out: #{str}" oj-2.5.3/test/perf_saj.rb0000755000004100000410000000624412263716750015300 0ustar www-datawww-data#!/usr/bin/env ruby -wW1 # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' require 'yajl' require 'perf' require 'json' require 'json/ext' require 'oj' $verbose = false $indent = 0 $iter = 10000 $gets = 0 $fetch = false $write = false $read = false opts = OptionParser.new opts.on("-v", "verbose") { $verbose = true } opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i } opts.on("-g", "--gets [Int]", Integer, "number of gets") { |i| $gets = i } opts.on("-f", "fetch") { $fetch = true } opts.on("-w", "write") { $write = true } opts.on("-r", "read") { $read = true } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) class AllSaj < Oj::Saj def initialize() end def hash_start(key) end def hash_end(key) end def array_start(key) end def array_end(key) end def add_value(value, key) end end # AllSaj class NoSaj < Oj::Saj def initialize() end end # NoSaj saj_handler = AllSaj.new() no_saj = NoSaj.new() $obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, {'12345' => 12345, 'nil' => nil}, 3.967, { 'x' => 'something', 'y' => false, 'z' => true}, nil]], # mix it up array 'e' => { 'one' => 1, 'two' => 2 }, # hash 'f' => nil, # nil 'g' => 12345678901234567890123456789, # big number 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } Oj.default_options = { :indent => $indent, :mode => :compat } $json = Oj.dump($obj) $failed = {} # key is same as String used in tests later def capture_error(tag, orig, load_key, dump_key, &blk) begin obj = blk.call(orig) raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj rescue Exception => e $failed[tag] = "#{e.class}: #{e.message}" end end # Verify that all packages dump and load correctly and return the same Object as the original. capture_error('Yajl', $obj, 'encode', 'parse') { |o| Yajl::Parser.parse(Yajl::Encoder.encode(o)) } capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| JSON.generator = JSON::Ext::Generator; JSON::Ext::Parser.new(JSON.generate(o)).parse } if $verbose puts "json:\n#{$json}\n" end puts '-' * 80 puts "Parse Performance" perf = Perf.new() perf.add('Oj::Saj', 'all') { Oj.saj_parse(saj_handler, $json) } perf.add('Oj::Saj', 'none') { Oj.saj_parse(no_saj, $json) } perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) } unless $failed.has_key?('Yajl') perf.add('JSON::Ext', 'parse') { JSON::Ext::Parser.new($json).parse } unless $failed.has_key?('JSON::Ext') perf.run($iter) unless $failed.empty? puts "The following packages were not included for the reason listed" $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" } end oj-2.5.3/test/test_saj.rb0000755000004100000410000001066112263716750015321 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'test/unit' require 'bigdecimal' require 'oj' require 'pp' $json = %{{ "array": [ { "num" : 3, "string": "message", "hash" : { "h2" : { "a" : [ 1, 2, 3 ] } } } ], "boolean" : true }} class AllSaj < Oj::Saj attr_accessor :calls def initialize() @calls = [] end def hash_start(key) @calls << [:hash_start, key] end def hash_end(key) @calls << [:hash_end, key] end def array_start(key) @calls << [:array_start, key] end def array_end(key) @calls << [:array_end, key] end def add_value(value, key) @calls << [:add_value, value, key] end def error(message, line, column) @calls << [:error, message, line, column] end end # AllSaj class SajTest < ::Test::Unit::TestCase def test_nil handler = AllSaj.new() json = %{null} Oj.saj_parse(handler, json) assert_equal([[:add_value, nil, nil]], handler.calls) end def test_true handler = AllSaj.new() json = %{true} Oj.saj_parse(handler, json) assert_equal([[:add_value, true, nil]], handler.calls) end def test_false handler = AllSaj.new() json = %{false} Oj.saj_parse(handler, json) assert_equal([[:add_value, false, nil]], handler.calls) end def test_string handler = AllSaj.new() json = %{"a string"} Oj.saj_parse(handler, json) assert_equal([[:add_value, 'a string', nil]], handler.calls) end def test_fixnum handler = AllSaj.new() json = %{12345} Oj.saj_parse(handler, json) assert_equal([[:add_value, 12345, nil]], handler.calls) end def test_float handler = AllSaj.new() json = %{12345.6789} Oj.saj_parse(handler, json) assert_equal([[:add_value, 12345.6789, nil]], handler.calls) end def test_float_exp handler = AllSaj.new() json = %{12345.6789e7} Oj.saj_parse(handler, json) assert_equal(1, handler.calls.size) assert_equal(:add_value, handler.calls[0][0]) assert_equal((12345.6789e7 * 10000).to_i, (handler.calls[0][1] * 10000).to_i) end def test_array_empty handler = AllSaj.new() json = %{[]} Oj.saj_parse(handler, json) assert_equal([[:array_start, nil], [:array_end, nil]], handler.calls) end def test_array handler = AllSaj.new() json = %{[true,false]} Oj.saj_parse(handler, json) assert_equal([[:array_start, nil], [:add_value, true, nil], [:add_value, false, nil], [:array_end, nil]], handler.calls) end def test_hash_empty handler = AllSaj.new() json = %{{}} Oj.saj_parse(handler, json) assert_equal([[:hash_start, nil], [:hash_end, nil]], handler.calls) end def test_hash handler = AllSaj.new() json = %{{"one":true,"two":false}} Oj.saj_parse(handler, json) assert_equal([[:hash_start, nil], [:add_value, true, 'one'], [:add_value, false, 'two'], [:hash_end, nil]], handler.calls) end def test_full handler = AllSaj.new() Oj.saj_parse(handler, $json) assert_equal([[:hash_start, nil], [:array_start, 'array'], [:hash_start, nil], [:add_value, 3, 'num'], [:add_value, 'message', 'string'], [:hash_start, 'hash'], [:hash_start, 'h2'], [:array_start, 'a'], [:add_value, 1, nil], [:add_value, 2, nil], [:add_value, 3, nil], [:array_end, 'a'], [:hash_end, 'h2'], [:hash_end, 'hash'], [:hash_end, nil], [:array_end, 'array'], [:add_value, true, 'boolean'], [:hash_end, nil]], handler.calls) end def test_fixnum_bad handler = AllSaj.new() json = %{12345xyz} Oj.saj_parse(handler, json) assert_equal([[:add_value, 12345, nil], [:error, "invalid format, extra characters at line 1, column 6 [saj.c:705]", 1, 6]], handler.calls) end end oj-2.5.3/test/a.rb0000755000004100000410000000204312263716750013720 0ustar www-datawww-data#!/usr/bin/env ruby -wW1 # encoding: UTF-8 $: << File.dirname(__FILE__) $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'pp' require 'oj' require 'perf' obj = [[1],[2],[3],[4],[5],[6],[7],[8],[9]] obj = [[],[],[],[],[],[],[],[],[]] obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, [12345, nil], 3.967, ['something', false], nil]], # mix it up array 'e' => { 'one' => 1, 'two' => 2 }, # hash 'f' => nil, # nil 'g' => 12345678901234567890123456789, # big number 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } json = Oj.dump(obj, mode: :compat) puts json #pp Oj.saj_parse(nil, json) pp Oj.t_parse(json) if true perf = Perf.new() perf.add('SAJ', 'oj') { Oj.saj_parse(nil, json) } perf.add('T', 'oj') { Oj.t_parse(json) } perf.add('load', 'oj') { Oj.load(json) } perf.run(10000) end oj-2.5.3/test/x.rb0000755000004100000410000000451512263716750013755 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'oj' # Oj is not able to deserialize all classes that are a subclass of a Ruby # Exception. Only exception that take one required string argument in the # initialize() method are supported. This is an example of how to write an # Exception subclass that supports both a single string intializer and an # Exception as an argument. Additional optional arguments can be added as well. # # The reason for this restriction has to do with a design decision on the part # of the Ruby developers. Exceptions are special Objects. They do not follow the # rules of other Objects. Exceptions have 'mesg' and a 'bt' attribute. Note that # these are not '@mesg' and '@bt'. They can not be set using the normal C or # Ruby calls. The only way I have found to set the 'mesg' attribute is through # the initializer. Unfortunately that means any subclass that provides a # different initializer can not be automatically decoded. A way around this is # to use a create function but this example shows an alternative. class WrapException < StandardError attr_reader :original def initialize(msg_or_err) if msg_or_err.is_a?(Exception) super(msg_or_err.message) @original = msg_or_err set_backtrace(msg_or_err.backtrace) else super(message) @original = nil end end end e = WrapException.new(RuntimeError.new("Something broke.")) json = Oj.dump(e, :mode => :object) puts "original:\n#{json}" # outputs: # original: # {"^o":"WrapException","original":{"^o":"RuntimeError","~mesg":"Something broke.","~bt":null},"~mesg":"Something broke.","~bt":null} e2 = Oj.load(json, :mode => :object) puts "dumped, loaded, and dumped again:\n#{Oj.dump(e2, :mode => :object)}" # outputs: # original: {"^o":"WrapException","original":{"^o":"RuntimeError","~mesg":"Something broke.","~bt":null},"~mesg":"Something broke.","~bt":null} # dumped, loaded, and dumped again: # {"^o":"WrapException","original":{"^o":"RuntimeError","~mesg":"Something broke.","~bt":null},"~mesg":"Something broke.","~bt":null} oj-2.5.3/test/test_fast.rb0000755000004100000410000002510412263716750015477 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'test/unit' require 'bigdecimal' require 'oj' $json1 = %{{ "array": [ { "num" : 3, "string": "message", "hash" : { "h2" : { "a" : [ 1, 2, 3 ] } } } ], "boolean" : true }} class DocTest < ::Test::Unit::TestCase def test_nil json = %{null} Oj::Doc.open(json) do |doc| assert_equal(NilClass, doc.type) assert_equal(nil, doc.fetch()) end end def test_true json = %{true} Oj::Doc.open(json) do |doc| assert_equal(TrueClass, doc.type) assert_equal(true, doc.fetch()) end end def test_false json = %{false} Oj::Doc.open(json) do |doc| assert_equal(FalseClass, doc.type) assert_equal(false, doc.fetch()) end end def test_string json = %{"a string"} Oj::Doc.open(json) do |doc| assert_equal(String, doc.type) assert_equal('a string', doc.fetch()) end end def test_fixnum json = %{12345} Oj::Doc.open(json) do |doc| assert_equal(Fixnum, doc.type) assert_equal(12345, doc.fetch()) end end def test_float json = %{12345.6789} Oj::Doc.open(json) do |doc| assert_equal(Float, doc.type) assert_equal(12345.6789, doc.fetch()) end end def test_float_exp json = %{12345.6789e7} Oj::Doc.open(json) do |doc| assert_equal(Float, doc.type) #assert_equal(12345.6789e7, doc.fetch()) assert_equal(12345.6789e7.to_i, doc.fetch().to_i) end end def test_array_empty json = %{[]} Oj::Doc.open(json) do |doc| assert_equal(Array, doc.type) assert_equal([], doc.fetch()) end end def test_array json = %{[true,false]} Oj::Doc.open(json) do |doc| assert_equal(Array, doc.type) assert_equal([true, false], doc.fetch()) end end def test_hash_empty json = %{{}} Oj::Doc.open(json) do |doc| assert_equal(Hash, doc.type) assert_equal({}, doc.fetch()) end end def test_hash json = %{{"one":true,"two":false}} Oj::Doc.open(json) do |doc| assert_equal(Hash, doc.type) assert_equal({'one' => true, 'two' => false}, doc.fetch()) end end # move() and where?() def test_move_hash json = %{{"one":{"two":false}}} Oj::Doc.open(json) do |doc| doc.move('/one') assert_equal('/one', doc.where?) doc.move('/one/two') assert_equal('/one/two', doc.where?) end end def test_move_array json = %{[1,[2,true]]} Oj::Doc.open(json) do |doc| doc.move('/1') assert_equal('/1', doc.where?) doc.move('/2/1') assert_equal('/2/1', doc.where?) end end def test_move Oj::Doc.open($json1) do |doc| [ '/', '/array', '/boolean', '/array/1/hash/h2/a/3', ].each do |p| doc.move(p) assert_equal(p, doc.where?) end begin doc.move('/array/x') rescue Exception assert_equal('/', doc.where?) assert(true) end end end def test_move_relative Oj::Doc.open($json1) do |doc| [['/', 'array', '/array'], ['/array', '1/num', '/array/1/num'], ['/array/1/hash', 'h2/a', '/array/1/hash/h2/a'], ['/array/1', 'hash/h2/a/2', '/array/1/hash/h2/a/2'], ['/array/1/hash', '../string', '/array/1/string'], ['/array/1/hash', '..', '/array/1'], ].each do |start,path,where| doc.move(start) doc.move(path) assert_equal(where, doc.where?) end end end def test_type Oj::Doc.open($json1) do |doc| [['/', Hash], ['/array', Array], ['/array/1', Hash], ['/array/1/num', Fixnum], ['/array/1/string', String], ['/array/1/hash/h2/a', Array], ['/array/1/hash/../num', Fixnum], ['/array/1/hash/../..', Array], ].each do |path,type| assert_equal(type, doc.type(path)) end end end def test_local_key Oj::Doc.open($json1) do |doc| [['/', nil], ['/array', 'array'], ['/array/1', 1], ['/array/1/num', 'num'], ['/array/1/string', 'string'], ['/array/1/hash/h2/a', 'a'], ['/array/1/hash/../num', 'num'], ['/array/1/hash/..', 1], ['/array/1/hash/../..', 'array'], ].each do |path,key| doc.move(path) assert_equal(key, doc.local_key()) end end end def test_fetch_move Oj::Doc.open($json1) do |doc| [['/array/1/num', 3], ['/array/1/string', 'message'], ['/array/1/hash/h2/a', [1, 2, 3]], ['/array/1/hash/../num', 3], ['/array/1/hash/..', {'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}], ['/array/1/hash/../..', [{'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}]], ['/array/1', {'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}], ['/array', [{'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}]], ['/', {'array' => [{'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}], 'boolean' => true}], ].each do |path,val| doc.move(path) assert_equal(val, doc.fetch()) end end end def test_fetch_path Oj::Doc.open($json1) do |doc| [['/array/1/num', 3], ['/array/1/string', 'message'], ['/array/1/hash/h2/a', [1, 2, 3]], ['/array/1/hash/../num', 3], ['/array/1/hash/..', {'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}], ['/array/1/hash/../..', [{'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}]], ['/array/1', {'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}], ['/array', [{'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}]], ['/', {'array' => [{'num' => 3, 'string' => 'message', 'hash' => {'h2' => {'a' => [1, 2, 3]}}}], 'boolean' => true}], ].each do |path,val| assert_equal(val, doc.fetch(path)) end end end def test_home Oj::Doc.open($json1) do |doc| doc.move('/array/1/num') doc.home() assert_equal('/', doc.where?) end end def test_each_value_root Oj::Doc.open($json1) do |doc| values = [] doc.each_value() { |v| values << v.to_s } assert_equal(['1', '2', '3', '3', 'message', 'true'], values.sort) end end def test_each_value_move Oj::Doc.open($json1) do |doc| doc.move('/array/1/hash') values = [] doc.each_value() { |v| values << v.to_s } assert_equal(['1', '2', '3'], values.sort) end end def test_each_value_path Oj::Doc.open($json1) do |doc| values = [] doc.each_value('/array/1/hash') { |v| values << v.to_s } assert_equal(['1', '2', '3'], values.sort) end end def test_each_child_move Oj::Doc.open($json1) do |doc| locations = [] doc.move('/array/1/hash/h2/a') doc.each_child() { |d| locations << d.where? } assert_equal(['/array/1/hash/h2/a/1', '/array/1/hash/h2/a/2', '/array/1/hash/h2/a/3'], locations) locations = [] doc.move('/array/1') doc.each_child() { |d| locations << d.where? } assert_equal(['/array/1/num', '/array/1/string', '/array/1/hash'], locations) end end def test_each_child_path Oj::Doc.open($json1) do |doc| locations = [] doc.each_child('/array/1/hash/h2/a') { |d| locations << d.where? } assert_equal(['/array/1/hash/h2/a/1', '/array/1/hash/h2/a/2', '/array/1/hash/h2/a/3'], locations) locations = [] doc.each_child('/array/1') { |d| locations << d.where? } assert_equal(['/array/1/num', '/array/1/string', '/array/1/hash'], locations) end end def test_size Oj::Doc.open('[1,2,3]') do |doc| assert_equal(4, doc.size) end Oj::Doc.open('{"a":[1,2,3]}') do |doc| assert_equal(5, doc.size) end end def test_open_file filename = 'open_file_test.json' File.open(filename, 'w') { |f| f.write('{"a":[1,2,3]}') } Oj::Doc.open_file(filename) do |doc| assert_equal(5, doc.size) end end def test_open_close json = %{{"a":[1,2,3]}} doc = Oj::Doc.open(json) assert_equal(Oj::Doc, doc.class) assert_equal(5, doc.size) assert_equal('/', doc.where?) doc.move('a/1') doc.home() assert_equal(2, doc.fetch('/a/2')) assert_equal(2, doc.fetch('a/2')) doc.close() begin doc.home() rescue Exception assert(true) end end def test_file_open_close filename = 'open_file_test.json' File.open(filename, 'w') { |f| f.write('{"a":[1,2,3]}') } doc = Oj::Doc.open_file(filename) assert_equal(Oj::Doc, doc.class) assert_equal(5, doc.size) assert_equal('/', doc.where?) doc.move('a/1') doc.home() assert_equal(2, doc.fetch('/a/2')) assert_equal(2, doc.fetch('a/2')) doc.close() begin doc.home() rescue Exception assert(true) end end def test_open_no_close json = %{{"a":[1,2,3]}} doc = Oj::Doc.open(json) assert_equal(Oj::Doc, doc.class) assert_equal(5, doc.size) assert_equal('/', doc.where?) doc.move('a/1') doc.home() assert_equal(2, doc.fetch('/a/2')) assert_equal(2, doc.fetch('a/2')) doc = nil GC.start # a print statement confirms close is called end def test_dump Oj::Doc.open('[1,[2,3]]') do |doc| assert_equal('[1,[2,3]]', doc.dump()) end Oj::Doc.open('[1,[2,3]]') do |doc| assert_equal('[2,3]', doc.dump('/2')) end end def test_each_leaf results = Oj::Doc.open('[1,[2,3]]') do |doc| h = {} doc.each_leaf() { |d| h[d.where?] = d.fetch() } h end assert_equal({'/1' => 1, '/2/1' => 2, '/2/2' => 3}, results) end def test_each_leaf_hash results = Oj::Doc.open('{"a":{"x":2},"b":{"y":4}}') do |doc| h = {} doc.each_leaf() { |d| h[d.where?] = d.fetch() } h end assert_equal({'/a/x' => 2, '/b/y' => 4}, results) end def test_comment json = %{{ "x"/*one*/:/*two*/true,//three "y":58/*four*/, "z": [1,2/*five*/, 3 // six ] } } results = Oj::Doc.open(json) do |doc| h = {} doc.each_leaf() { |d| h[d.where?] = d.fetch() } h end assert_equal({'/x' => true, '/y' => 58, '/z/1' => 1, '/z/2' => 2, '/z/3' => 3}, results) end end # DocTest oj-2.5.3/test/perf_object.rb0000755000004100000410000000746512263716750015777 0ustar www-datawww-data#!/usr/bin/env ruby -wW1 $: << '.' $: << '../lib' $: << '../ext' if __FILE__ == $0 if (i = ARGV.index('-I')) x,path = ARGV.slice!(i, 2) $: << path end end require 'optparse' require 'ox' require 'oj' require 'perf' require 'sample' require 'files' $circular = false $indent = 0 $allow_gc = true do_sample = false do_files = false do_load = false do_dump = false do_read = false do_write = false $iter = 1000 $mult = 1 opts = OptionParser.new opts.on("-c", "circular options") { $circular = true } opts.on("-x", "use sample instead of files") { do_sample = true } opts.on("-g", "no GC during parsing") { $allow_gc = false } opts.on("-s", "load and dump as sample Ruby object") { do_sample = true } opts.on("-f", "load and dump as files Ruby object") { do_files = true } opts.on("-l", "load") { do_load = true } opts.on("-d", "dump") { do_dump = true } opts.on("-r", "read") { do_read = true } opts.on("-w", "write") { do_write = true } opts.on("-a", "load, dump, read and write") { do_load = true; do_dump = true; do_read = true; do_write = true } opts.on("-i", "--iterations [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-m", "--multiply [Int]", Integer, "multiplier") { |i| $mult = i } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) $obj = nil $xml = nil $mars = nil $json = nil unless do_load || do_dump || do_read || do_write do_load = true do_dump = true do_read = true do_write = true end # prepare all the formats for input if files.empty? $obj = [] $mult.times do $obj << (do_sample ? sample_doc(2) : files('..')) end $mars = Marshal.dump($obj) $xml = Ox.dump($obj, :indent => $indent, :circular => $circular) $json = Oj.dump($obj, :indent => $indent, :circular => $circular) File.open('sample.xml', 'w') { |f| f.write($xml) } File.open('sample.json', 'w') { |f| f.write($json) } File.open('sample.marshal', 'w') { |f| f.write($mars) } else puts "loading and parsing #{files}\n\n" data = files.map do |f| $xml = File.read(f) $obj = Ox.load($xml); $mars = Marshal.dump($obj) $json = Oj.dump($obj, :indent => $indent, :circular => $circular) end end Oj.default_options = { :mode => :object, :indent => $indent, :circular => $circular, :allow_gc => $allow_gc } #puts "json: #{$json.size}" #puts "xml: #{$xml.size}" #puts "marshal: #{$mars.size}" if do_load puts '-' * 80 puts "Load Performance" perf = Perf.new() perf.add('Oj.object', 'load') { Oj.object_load($json) } perf.add('Ox', 'load') { Ox.load($xml, :mode => :object) } perf.add('Marshal', 'load') { Marshal.load($mars) } perf.run($iter) end if do_dump puts '-' * 80 puts "Dump Performance" perf = Perf.new() perf.add('Oj', 'dump') { Oj.dump($obj) } perf.add('Ox', 'dump') { Ox.dump($obj, :indent => $indent, :circular => $circular) } perf.add('Marshal', 'dump') { Marshal.dump($obj) } perf.run($iter) end if do_read puts '-' * 80 puts "Read from file Performance" perf = Perf.new() perf.add('Oj', 'load') { Oj.load_file('sample.json') } perf.add('Ox', 'load_file') { Ox.load_file('sample.xml', :mode => :object) } perf.add('Marshal', 'load') { Marshal.load(File.new('sample.marshal')) } perf.run($iter) end if do_write puts '-' * 80 puts "Write to file Performance" perf = Perf.new() perf.add('Oj', 'to_file') { Oj.to_file('sample.json', $obj) } perf.add('Ox', 'to_file') { Ox.to_file('sample.xml', $obj, :indent => $indent, :circular => $circular) } perf.add('Marshal', 'dump') { Marshal.dump($obj, File.new('sample.marshal', 'w')) } perf.run($iter) end oj-2.5.3/test/test_object.rb0000755000004100000410000002171212263716750016011 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'test/unit' require 'stringio' require 'date' require 'bigdecimal' require 'oj' $ruby = RUBY_DESCRIPTION.split(' ')[0] $ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition') class Jeez attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def eql?(o) self.class == o.class && @x == o.x && @y == o.y end alias == eql? def to_json(*a) %{{"json_class":"#{self.class}","x":#{@x},"y":#{@y}}} end def self.json_create(h) self.new(h['x'], h['y']) end end # Jeez module One module Two module Three class Deep def initialize() end def eql?(o) self.class == o.class end alias == eql? def to_hash() {'json_class' => "#{self.class.name}"} end def to_json(*a) %{{"json_class":"#{self.class.name}"}} end def self.json_create(h) self.new() end end # Deep end # Three end # Two end # One class Stuck < Struct.new(:a, :b) end def hash_eql(h1, h2) return false if h1.size != h2.size h1.keys.each do |k| return false unless h1[k] == h2[k] end true end class ObjectJuice < ::Test::Unit::TestCase def test_nil dump_and_load(nil, false) end def test_true dump_and_load(true, false) end def test_false dump_and_load(false, false) end def test_fixnum dump_and_load(0, false) dump_and_load(12345, false) dump_and_load(-54321, false) dump_and_load(1, false) end def test_float dump_and_load(0.0, false) dump_and_load(12345.6789, false) dump_and_load(70.35, false) dump_and_load(-54321.012, false) dump_and_load(2.48e16, false) dump_and_load(2.48e100 * 1.0e10, false) dump_and_load(-2.48e100 * 1.0e10, false) end def test_string dump_and_load('', false) dump_and_load('abc', false) dump_and_load("abc\ndef", false) dump_and_load("a\u0041", false) end def test_symbol dump_and_load(:abc, false) dump_and_load(":abc", false) end def test_encode opts = Oj.default_options Oj.default_options = { :ascii_only => false } unless 'jruby' == $ruby dump_and_load("ぴーたー", false) end Oj.default_options = { :ascii_only => true } json = Oj.dump("ぴーたー") assert_equal(%{"\\u3074\\u30fc\\u305f\\u30fc"}, json) unless 'jruby' == $ruby dump_and_load("ぴーたー", false) end Oj.default_options = opts end def test_unicode # hits the 3 normal ranges and one extended surrogate pair json = %{"\\u019f\\u05e9\\u3074\\ud834\\udd1e"} obj = Oj.load(json) json2 = Oj.dump(obj, :ascii_only => true) assert_equal(json, json2) end def test_array dump_and_load([], false) dump_and_load([true, false], false) dump_and_load(['a', 1, nil], false) dump_and_load([[nil]], false) dump_and_load([[nil], 58], false) end def test_array_deep dump_and_load([1,[2,[3,[4,[5,[6,[7,[8,[9,[10,[11,[12,[13,[14,[15,[16,[17,[18,[19,[20]]]]]]]]]]]]]]]]]]]], false) end # Hash def test_hash dump_and_load({}, false) dump_and_load({ 'true' => true, 'false' => false}, false) dump_and_load({ 'true' => true, 'array' => [], 'hash' => { }}, false) end def test_hash_deep dump_and_load({'1' => { '2' => { '3' => { '4' => { '5' => { '6' => { '7' => { '8' => { '9' => { '10' => { '11' => { '12' => { '13' => { '14' => { '15' => { '16' => { '17' => { '18' => { '19' => { '20' => {}}}}}}}}}}}}}}}}}}}}}, false) end def test_hash_escaped_key json = %{{"a\nb":true,"c\td":false}} obj = Oj.object_load(json) assert_equal({"a\nb" => true, "c\td" => false}, obj) end def test_bignum_object dump_and_load(7 ** 55, false) end # BigDecimal def test_bigdecimal_object dump_and_load(BigDecimal.new('3.14159265358979323846'), false) end def test_bigdecimal_load orig = BigDecimal.new('80.51') json = Oj.dump(orig, :mode => :object, :bigdecimal_as_decimal => true) bg = Oj.load(json, :mode => :object, :bigdecimal_load => true) assert_equal(BigDecimal, bg.class) assert_equal(orig, bg) end # Stream IO def test_io_string json = %{{ "x":true, "y":58, "z": [1,2,3] } } input = StringIO.new(json) obj = Oj.object_load(input) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_io_file filename = 'open_file_test.json' File.open(filename, 'w') { |f| f.write(%{{ "x":true, "y":58, "z": [1,2,3] } }) } f = File.new(filename) obj = Oj.object_load(f) f.close() assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end # symbol_keys option def test_symbol_keys json = %{{ "x":true, "y":58, "z": [1,2,3] } } obj = Oj.object_load(json, :symbol_keys => true) assert_equal({ :x => true, :y => 58, :z => [1, 2, 3]}, obj) end # comments def test_comment_slash json = %{{ "x":true,//three "y":58, "z": [1,2, 3 // six ]} } obj = Oj.object_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment_c json = %{{ "x"/*one*/:/*two*/true, "y":58, "z": [1,2,3]} } obj = Oj.object_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment json = %{{ "x"/*one*/:/*two*/true,//three "y":58/*four*/, "z": [1,2/*five*/, 3 // six ] } } obj = Oj.object_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_json_module_object obj = One::Two::Three::Deep.new() dump_and_load(obj, false) end def test_time t = Time.now() dump_and_load(t, false) end def test_time_early t = Time.xmlschema("1954-01-05T00:00:00.123456") dump_and_load(t, false) end def test_json_object obj = Jeez.new(true, 58) dump_and_load(obj, false) end def test_json_object_create_deep obj = One::Two::Three::Deep.new() dump_and_load(obj, false) end def test_json_object_bad json = %{{"^o":"Junk","x":true}} begin Oj.object_load(json) rescue Exception => e assert_equal("Oj::ParseError", e.class().name) return end assert(false, "*** expected an exception") end def test_json_struct unless 'jruby' == RUBY_DESCRIPTION.split(' ')[0] obj = Stuck.new(false, 7) dump_and_load(obj, false) end end def test_json_non_str_hash obj = { 59 => "young", false => true } dump_and_load(obj, false) end def test_mixed_hash_object json = Oj.dump({ 1 => true, 'nil' => nil, :sim => 4 }) h = Oj.object_load(json) assert_equal({ 1 => true, 'nil' => nil, :sim => 4 }, h) end def test_circular_hash h = { 'a' => 7 } h['b'] = h json = Oj.dump(h, :mode => :object, :indent => 2, :circular => true) h2 = Oj.object_load(json, :circular => true) assert_equal(h2['b'].__id__, h2.__id__) end def test_circular_array a = [7] a << a json = Oj.dump(a, :mode => :object, :indent => 2, :circular => true) a2 = Oj.object_load(json, :circular => true) assert_equal(a2[1].__id__, a2.__id__) end def test_circular_object obj = Jeez.new(nil, 58) obj.x = obj json = Oj.dump(obj, :mode => :object, :indent => 2, :circular => true) obj2 = Oj.object_load(json, :circular => true) assert_equal(obj2.x.__id__, obj2.__id__) end def test_circular h = { 'a' => 7 } obj = Jeez.new(h, 58) obj.x['b'] = obj json = Oj.dump(obj, :mode => :object, :indent => 2, :circular => true) Oj.object_load(json, :circular => true) assert_equal(obj.x.__id__, h.__id__) assert_equal(h['b'].__id__, obj.__id__) end def test_odd_date dump_and_load(Date.new(2012, 6, 19), false) end def dump_and_load(obj, trace=false) json = Oj.dump(obj, :indent => 2, :mode => :object) puts json if trace loaded = Oj.object_load(json); assert_equal(obj, loaded) loaded end end oj-2.5.3/test/test_writer.rb0000755000004100000410000001130612263716750016055 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'test/unit' require 'stringio' require 'date' require 'bigdecimal' require 'oj' class OjWriter < ::Test::Unit::TestCase def test_string_writer_empty_array w = Oj::StringWriter.new(:indent => 0) w.push_array() w.pop() assert_equal('[]', w.to_s) end def test_string_writer_nested_array w = Oj::StringWriter.new(:indent => 0) w.push_array() w.push_array() w.pop() w.push_array() w.push_array() w.pop() w.pop() w.push_array() w.pop() w.pop() assert_equal('[[],[[]],[]]', w.to_s) end def test_string_writer_empty_object w = Oj::StringWriter.new(:indent => 0) w.push_object() w.pop() assert_equal('{}', w.to_s) end def test_string_writer_nested_object w = Oj::StringWriter.new(:indent => 0) w.push_object() w.push_object("a1") w.pop() w.push_object("a2") w.push_object("b") w.pop() w.pop() w.push_object("a3") w.pop() w.pop() assert_equal('{"a1":{},"a2":{"b":{}},"a3":{}}', w.to_s) end def test_string_writer_value_array w = Oj::StringWriter.new(:indent => 2) w.push_array() w.push_value(7) w.push_value(7.3) w.push_value(true) w.push_value(nil) w.push_value("a string") w.push_value({'a' => 65}) w.push_value([1,2]) w.pop() assert_equal(%|[ 7, 7.3, true, null, "a string", { "a":65 }, [ 1, 2 ] ] |, w.to_s) end def test_string_writer_block w = Oj::StringWriter.new(:indent => 0) w.push_object() { w.push_object("a1") { w.push_value(7, 'a7') } w.push_array("a2") { w.push_value('x') w.push_value(3) } } assert_equal('{"a1":{"a7":7},"a2":["x",3]}', w.to_s) end def test_string_writer_json w = Oj::StringWriter.new(:indent => 0) w.push_array() w.push_json('7') w.push_json('true') w.push_json(%|"a string"|) w.push_object() w.push_json('{"a":65}', 'x') w.pop() w.pop() assert_equal('[7,true,"a string",{"x":{"a":65}}]', w.to_s) end def test_string_writer_pop_excess w = Oj::StringWriter.new(:indent => 0) begin w.pop() rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_string_writer_obj_no_key w = Oj::StringWriter.new(:indent => 0) w.push_object() begin w.push_value(59) rescue Exception assert(true) return end assert(false, "*** expected an exception") end def test_string_writer_deep cnt = 1000 w = Oj::StringWriter.new(:indent => 0) cnt.times do w.push_array() end cnt.times do w.pop() end # if no exception then passed assert(true) end def test_string_writer_pop_all w = Oj::StringWriter.new(:indent => 0) w.push_object() w.push_object("a1") w.pop() w.push_array("a2") w.push_value(3) w.push_array() w.pop_all() assert_equal('{"a1":{},"a2":[3,[]]}', w.to_s) end def test_string_writer_reset w = Oj::StringWriter.new(:indent => 0) w.push_array() w.pop() w.reset() assert_equal('', w.to_s) end # Stream Writer def test_stream_writer_empty_array output = StringIO.open("", "w+") w = Oj::StreamWriter.new(output, :indent => 0) w.push_array() w.pop() assert_equal('[]', output.string()) end def test_stream_writer_mixed_stringio output = StringIO.open("", "w+") w = Oj::StreamWriter.new(output, :indent => 0) w.push_object() w.push_object("a1") w.pop() w.push_object("a2") w.push_array("b") w.push_value(7) w.push_value(true) w.push_value("string") w.pop() w.pop() w.push_object("a3") w.pop() w.pop() assert_equal('{"a1":{},"a2":{"b":[7,true,"string"]},"a3":{}}', output.string()) end def test_stream_writer_mixed_file filename = 'open_file_writer_test.json' File.open(filename, "w") do |f| w = Oj::StreamWriter.new(f, :indent => 0) w.push_object() w.push_object("a1") w.pop() w.push_object("a2") w.push_array("b") w.push_value(7) w.push_value(true) w.push_value("string") w.pop() w.pop() w.push_object("a3") w.pop() w.pop() end content = File.read(filename) assert_equal('{"a1":{},"a2":{"b":[7,true,"string"]},"a3":{}}', content) end end # OjWriter oj-2.5.3/test/test_gc.rb0000755000004100000410000000212612263716750015132 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get # warnings to show up the -w options is required. That can be set in the RUBYOPT # environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'test/unit' require 'bigdecimal' require 'oj' class Goo attr_accessor :x, :child def initialize(x, child) @x = x @child = child end def to_hash() { 'json_class' => "#{self.class}", 'x' => x, 'child' => child } end def self.json_create(h) GC.start self.new(h['x'], h['child']) end end # Goo class GCTest < ::Test::Unit::TestCase # if no crash then the GC marking is working def test_parse_compat_gc g = Goo.new(0, nil) 100.times { |i| g = Goo.new(i, g) } json = Oj.dump(g, :mode => :compat) Oj.compat_load(json) end def test_parse_object_gc g = Goo.new(0, nil) 100.times { |i| g = Goo.new(i, g) } json = Oj.dump(g, :mode => :object) Oj.object_load(json) end end oj-2.5.3/test/test_strict.rb0000755000004100000410000001432512263716750016055 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'test/unit' require 'stringio' require 'date' require 'bigdecimal' require 'oj' $ruby = RUBY_DESCRIPTION.split(' ')[0] $ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition') def hash_eql(h1, h2) return false if h1.size != h2.size h1.keys.each do |k| return false unless h1[k] == h2[k] end true end class StrictJuice < ::Test::Unit::TestCase def test_nil dump_and_load(nil, false) end def test_true dump_and_load(true, false) end def test_false dump_and_load(false, false) end def test_fixnum dump_and_load(0, false) dump_and_load(12345, false) dump_and_load(-54321, false) dump_and_load(1, false) end def test_float dump_and_load(0.0, false) dump_and_load(12345.6789, false) dump_and_load(70.35, false) dump_and_load(-54321.012, false) dump_and_load(2.48e16, false) dump_and_load(2.48e100 * 1.0e10, false) dump_and_load(-2.48e100 * 1.0e10, false) end def test_string dump_and_load('', false) dump_and_load('abc', false) dump_and_load("abc\ndef", false) dump_and_load("a\u0041", false) end def test_encode opts = Oj.default_options Oj.default_options = { :ascii_only => false } unless 'jruby' == $ruby dump_and_load("ぴーたー", false) end Oj.default_options = { :ascii_only => true } json = Oj.dump("ぴーたー") assert_equal(%{"\\u3074\\u30fc\\u305f\\u30fc"}, json) unless 'jruby' == $ruby dump_and_load("ぴーたー", false) end Oj.default_options = opts end def test_unicode # hits the 3 normal ranges and one extended surrogate pair json = %{"\\u019f\\u05e9\\u3074\\ud834\\udd1e"} obj = Oj.load(json) json2 = Oj.dump(obj, :ascii_only => true) assert_equal(json, json2) end def test_unicode_long # tests buffer overflow json = %{"\\u019f\\u05e9\\u3074\\ud834\\udd1e #{'x' * 2000}"} obj = Oj.load(json) json2 = Oj.dump(obj, :ascii_only => true) assert_equal(json, json2) end def test_array dump_and_load([], false) dump_and_load([true, false], false) dump_and_load(['a', 1, nil], false) dump_and_load([[nil]], false) dump_and_load([[nil], 58], false) end def test_array_deep dump_and_load([1,[2,[3,[4,[5,[6,[7,[8,[9,[10,[11,[12,[13,[14,[15,[16,[17,[18,[19,[20]]]]]]]]]]]]]]]]]]]], false) end # Hash def test_hash dump_and_load({}, false) dump_and_load({ 'true' => true, 'false' => false}, false) dump_and_load({ 'true' => true, 'array' => [], 'hash' => { }}, false) end def test_hash_deep dump_and_load({'1' => { '2' => { '3' => { '4' => { '5' => { '6' => { '7' => { '8' => { '9' => { '10' => { '11' => { '12' => { '13' => { '14' => { '15' => { '16' => { '17' => { '18' => { '19' => { '20' => {}}}}}}}}}}}}}}}}}}}}}, false) end def test_hash_escaped_key json = %{{"a\nb":true,"c\td":false}} obj = Oj.strict_load(json) assert_equal({"a\nb" => true, "c\td" => false}, obj) end def test_bignum_object dump_and_load(7 ** 55, false) end # BigDecimal def test_bigdecimal_strict dump_and_load(BigDecimal.new('3.14159265358979323846'), false) end def test_bigdecimal_load orig = BigDecimal.new('80.51') json = Oj.dump(orig, :mode => :compat, :bigdecimal_as_decimal => true) bg = Oj.load(json, :mode => :compat, :bigdecimal_load => true) assert_equal(BigDecimal, bg.class) assert_equal(orig, bg) end # Stream IO def test_io_string json = %{{ "x":true, "y":58, "z": [1,2,3] } } input = StringIO.new(json) obj = Oj.strict_load(input) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_io_file filename = 'open_file_test.json' File.open(filename, 'w') { |f| f.write(%{{ "x":true, "y":58, "z": [1,2,3] } }) } f = File.new(filename) obj = Oj.strict_load(f) f.close() assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end # symbol_keys option def test_symbol_keys json = %{{ "x":true, "y":58, "z": [1,2,3] } } obj = Oj.strict_load(json, :symbol_keys => true) assert_equal({ :x => true, :y => 58, :z => [1, 2, 3]}, obj) end def test_symbol_keys_safe json = %{{ "x":true, "y":58, "z": [1,2,3] } } obj = Oj.safe_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end # comments def test_comment_slash json = %{{ "x":true,//three "y":58, "z": [1,2, 3 // six ]} } obj = Oj.strict_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment_c json = %{{ "x"/*one*/:/*two*/true, "y":58, "z": [1,2,3]} } obj = Oj.strict_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment json = %{{ "x"/*one*/:/*two*/true,//three "y":58/*four*/, "z": [1,2/*five*/, 3 // six ] } } obj = Oj.strict_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def dump_and_load(obj, trace=false) json = Oj.dump(obj, :indent => 2) puts json if trace loaded = Oj.strict_load(json); assert_equal(obj, loaded) loaded end end oj-2.5.3/test/test_mimic.rb0000755000004100000410000001227112263716750015641 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'test/unit' require 'stringio' require 'bigdecimal' require 'oj' $ruby = RUBY_DESCRIPTION.split(' ')[0] $ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition') class Jam attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def eql?(o) self.class == o.class && @x == o.x && @y == o.y end alias == eql? def to_json() %{{"json_class":"#{self.class}","x":#{@x},"y":#{@y}}} end def self.json_create(h) self.new(h['x'], h['y']) end end # Jam class Mimic < ::Test::Unit::TestCase def test0_mimic_json assert(defined?(JSON).nil?) Oj.mimic_JSON assert(!defined?(JSON).nil?) end # dump def test_dump_string json = JSON.dump([1, true, nil]) assert_equal(%{[1,true,null]}, json) end def test_dump_io s = StringIO.new() json = JSON.dump([1, true, nil], s) assert_equal(s, json) assert_equal(%{[1,true,null]}, s.string) end # TBD options # load def test_load_string json = %{{"a":1,"b":[true,false]}} obj = JSON.load(json) assert_equal({ 'a' => 1, 'b' => [true, false]}, obj) end def test_load_io json = %{{"a":1,"b":[true,false]}} obj = JSON.load(StringIO.new(json)) assert_equal({ 'a' => 1, 'b' => [true, false]}, obj) end def test_load_proc Oj.mimic_JSON # TBD children = [] json = %{{"a":1,"b":[true,false]}} if 'rubinius' == $ruby || 'jruby' == $ruby || '1.8.7' == RUBY_VERSION obj = JSON.load(json) {|x| children << x } else p = Proc.new {|x| children << x } obj = JSON.load(json, p) end assert_equal({ 'a' => 1, 'b' => [true, false]}, obj) # JRuby 1.7.0 rb_yield() is broken and converts the [true, falser] array into true unless 'jruby' == $ruby && '1.9.3' == RUBY_VERSION assert([1, true, false, [true, false], { 'a' => 1, 'b' => [true, false]}] == children || [true, false, [true, false], 1, { 'a' => 1, 'b' => [true, false]}] == children, "children don't match") end end # [] def test_bracket_load json = %{{"a":1,"b":[true,false]}} obj = JSON[json] assert_equal({ 'a' => 1, 'b' => [true, false]}, obj) end def test_bracket_dump json = JSON[[1, true, nil]] assert_equal(%{[1,true,null]}, json) end # generate def test_generate json = JSON.generate({ 'a' => 1, 'b' => [true, false]}) assert(%{{"a":1,"b":[true,false]}} == json || %{{"b":[true,false],"a":1}} == json) end def test_generate_options json = JSON.generate({ 'a' => 1, 'b' => [true, false]}, :indent => "--", :array_nl => "\n", :object_nl => "#\n", :space => "*", :space_before => "~") assert(%{{# --"a"~:*1,# --"b"~:*[ ----true, ----false --]# }} == json || %{{# --"b"~:*[ ----true, ----false --],# --"a"~:*1# }} == json) end # fast_generate def test_fast_generate json = JSON.generate({ 'a' => 1, 'b' => [true, false]}) assert(%{{"a":1,"b":[true,false]}} == json || %{{"b":[true,false],"a":1}} == json) end # pretty_generate def test_pretty_generate json = JSON.pretty_generate({ 'a' => 1, 'b' => [true, false]}) assert(%{{ "a" : 1, "b" : [ true, false ] }} == json || %{{ "b" : [ true, false ], "a" : 1 }} == json) end # parse def test_parse json = %{{"a":1,"b":[true,false]}} obj = JSON.parse(json) assert_equal({ 'a' => 1, 'b' => [true, false]}, obj) end def test_parse_sym_names json = %{{"a":1,"b":[true,false]}} obj = JSON.parse(json, :symbolize_names => true) assert_equal({ :a => 1, :b => [true, false]}, obj) end def test_parse_additions jam = Jam.new(true, 58) json = Oj.dump(jam, :mode => :compat) obj = JSON.parse(json) assert_equal(jam, obj) obj = JSON.parse(json, :create_additions => true) assert_equal(jam, obj) obj = JSON.parse(json, :create_additions => false) assert_equal({'json_class' => 'Jam', 'x' => true, 'y' => 58}, obj) json.gsub!('json_class', 'kson_class') JSON.create_id = 'kson_class' obj = JSON.parse(json, :create_additions => true) JSON.create_id = 'json_class' assert_equal(jam, obj) end def test_parse_bang json = %{{"a":1,"b":[true,false]}} obj = JSON.parse!(json) assert_equal({ 'a' => 1, 'b' => [true, false]}, obj) end # recurse_proc def test_recurse_proc children = [] JSON.recurse_proc({ 'a' => 1, 'b' => [true, false]}) { |x| children << x } # JRuby 1.7.0 rb_yield() is broken and converts the [true, falser] array into true unless 'jruby' == $ruby && '1.9.3' == RUBY_VERSION assert([1, true, false, [true, false], { 'a' => 1, 'b' => [true, false]}] == children || [true, false, [true, false], 1, { 'b' => [true, false], 'a' => 1}] == children) end end end # Mimic oj-2.5.3/test/sample_json.rb0000755000004100000410000000133712263716750016017 0ustar www-datawww-data#!/usr/bin/env ruby -wW2 if $0 == __FILE__ $: << '.' $: << '..' $: << '../lib' $: << '../ext' end require 'pp' require 'oj' def sample_json(size=3) colors = [ :black, :gray, :white, :red, :blue, :yellow, :green, :purple, :orange ] container = [] size.times do |i| box = { 'color' => colors[i % colors.size], 'fragile' => (0 == (i % 2)), 'width' => i, 'height' => i, 'depth' => i, 'weight' => i * 1.3, 'address' => { 'street' => "#{i} Main Street", 'city' => 'Sity', 'state' => nil } } container << box end container end if $0 == __FILE__ File.open('sample.json', "w") { |f| f.write(Oj.dump(sample_json(3), :indent => 2)) } end oj-2.5.3/test/sample/0000755000004100000410000000000012263716750014432 5ustar www-datawww-dataoj-2.5.3/test/sample/oval.rb0000644000004100000410000000021112263716750015712 0ustar www-datawww-datamodule Sample class Oval < Shape def initialize(left, top, wide, high, color=nil) super end end # Oval end # Sample oj-2.5.3/test/sample/hasprops.rb0000644000004100000410000000045612263716750016623 0ustar www-datawww-data module Sample module HasProps def add_prop(key, value) @props = { } unless self.instance_variable_defined?(:@props) @props[key] = value end def props @props = { } unless self.instance_variable_defined?(:@props) @props end end # HasProps end # Sample oj-2.5.3/test/sample/layer.rb0000644000004100000410000000023412263716750016072 0ustar www-datawww-data module Sample class Layer < Group attr_accessor :name def initialize(name) super() @name = name end end # Layer end # Sample oj-2.5.3/test/sample/rect.rb0000644000004100000410000000021112263716750015706 0ustar www-datawww-data module Sample class Rect < Shape def initialize(left, top, wide, high, color=nil) super end end # Rect end # Sample oj-2.5.3/test/sample/text.rb0000644000004100000410000000064412263716750015747 0ustar www-datawww-data module Sample class Text < Shape attr_accessor :text attr_accessor :font attr_accessor :font_size attr_accessor :just attr_accessor :text_color def initialize(text, left, top, wide, high, color=nil) super(left, top, wide, high, color) @text = text @font = 'helvetica' @font_size = 14 @just = 'left' @text_color = 'black' end end # Text end # Sample oj-2.5.3/test/sample/doc.rb0000644000004100000410000000137012263716750015525 0ustar www-datawww-data require 'sample/hasprops' require 'sample/group' require 'sample/layer' require 'sample/line' require 'sample/shape' require 'sample/oval' require 'sample/rect' require 'sample/text' require 'sample/change' module Sample class Doc include HasProps attr_accessor :title attr_accessor :create_time attr_accessor :user # Hash of layers in the document indexed by layer name. attr_reader :layers attr_reader :change_history def initialize(title) @title = title @user = ENV['USER'] @create_time = Time.now @layers = { } @change_history = [] end def add_change(comment, time=nil, user=nil) @change_history << Change.new(comment, time, user) end end # Doc end # Sample oj-2.5.3/test/sample/change.rb0000644000004100000410000000044112263716750016203 0ustar www-datawww-data module Sample class Change attr_accessor :time attr_accessor :user attr_accessor :comment def initialize(comment=nil, time=nil, user=nil) @user = user || ENV['USER'] @time = time || Time.now @comment = comment end end # Change end # Sample oj-2.5.3/test/sample/line.rb0000644000004100000410000000050012263716750015701 0ustar www-datawww-datamodule Sample class Line include HasProps attr_accessor :x, :y, :dx, :dy attr_accessor :color attr_accessor :thick def initialize(x, y, dx, dy, thick, color) @x = x @y = y @dx = dx @dy = dy @thick = thick @color = color end end # Line end # Sample oj-2.5.3/test/sample/shape.rb0000644000004100000410000000110512263716750016054 0ustar www-datawww-data module Sample class Shape include HasProps attr_accessor :bounds attr_accessor :color attr_accessor :border, :border_color def initialize(left, top, wide, high, color=nil) @bounds = [[left, top], [left + wide, top + high]] @color = color @border = 1 @border_color = :black end def left @bounds[0][0] end def top @bounds[0][1] end def width @bounds[1][0] - @bounds[0][0] end def height @bounds[1][1] - @bounds[0][1] end end # Shape end # Sample oj-2.5.3/test/sample/file.rb0000644000004100000410000000320012263716750015671 0ustar www-datawww-data require 'etc' module Sample class File attr_accessor :name, :ctime, :mtime, :size, :owner, :group, :permissions def initialize(filename) @name = ::File.basename(filename) stat = ::File.stat(filename) @ctime = stat.ctime @mtime = stat.mtime @size = stat.size @owner = Etc.getpwuid(stat.uid).name @group = Etc.getgrgid(stat.gid).name if false @permissions = { 'user' => { 'read' => (0 != (stat.mode & 0x0100)), 'write' => (0 != (stat.mode & 0x0080)), 'execute' => (0 != (stat.mode & 0x0040))}, 'group' => { 'read' => (0 != (stat.mode & 0x0020)), 'write' => (0 != (stat.mode & 0x0010)), 'execute' => (0 != (stat.mode & 0x0008))}, 'other' => { 'read' => (0 != (stat.mode & 0x0004)), 'write' => (0 != (stat.mode & 0x0002)), 'execute' => (0 != (stat.mode & 0x0001))} } else @permissions = { 'user' => [(0 != (stat.mode & 0x0100)) ? 'r' : '-', (0 != (stat.mode & 0x0080)) ? 'w' : '-', (0 != (stat.mode & 0x0040)) ? 'x' : '-'].join(''), 'group' => [(0 != (stat.mode & 0x0020)) ? 'r' : '-', (0 != (stat.mode & 0x0010)) ? 'w' : '-', (0 != (stat.mode & 0x0008)) ? 'x' : '-'].join(''), 'other' => [(0 != (stat.mode & 0x0004)) ? 'r' : '-', (0 != (stat.mode & 0x0002)) ? 'w' : '-', (0 != (stat.mode & 0x0001)) ? 'x' : '-'].join('') } end end end # File end # Sample oj-2.5.3/test/sample/group.rb0000644000004100000410000000030612263716750016112 0ustar www-datawww-data module Sample class Group attr_reader :members def initialize() @members = [] end def <<(member) @members << member end end # Group end # Sample oj-2.5.3/test/sample/dir.rb0000644000004100000410000000032612263716750015536 0ustar www-datawww-data require 'etc' module Sample class Dir < File attr_accessor :files def initialize(filename) super @files = [] end def <<(f) @files << f end end # Dir end # Sample oj-2.5.3/test/mj.rb0000755000004100000410000000313612263716750014112 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") # $: << File.join(File.dirname(__FILE__), "../../multi_json/lib") require 'multi_json' require 'benchmark' require 'yajl' require 'json' require 'oj' iter = 1_000_000 iter = 100_000 json = %({"k1":"val1","k2":"val2","k3":"val3"}) obj = { k1: "val1", k2: "val2", k3: "val3" } puts "Benchmarks for different JSON handlers with MultiJson." puts " Ruby #{RUBY_VERSION}" puts " #{iter} iterations" MultiJson.engine = :oj dt = Benchmark.realtime { iter.times { MultiJson.decode(json) }} et = Benchmark.realtime { iter.times { MultiJson.encode(obj) }} puts " Oj decode: #{dt} encode: #{et}" MultiJson.engine = :yajl dt = Benchmark.realtime { iter.times { MultiJson.decode(json) }} et = Benchmark.realtime { iter.times { MultiJson.encode(obj) }} puts " Yajl decode: #{dt} encode: #{et}" MultiJson.engine = :json_gem dt = Benchmark.realtime { iter.times { MultiJson.decode(json) }} et = Benchmark.realtime { iter.times { MultiJson.encode(obj) }} puts " Json decode: #{dt} encode: #{et}" Oj.default_options = { :mode => :compat, :time_format => :ruby } dt = Benchmark.realtime { iter.times { Oj.load(json) }} et = Benchmark.realtime { iter.times { Oj.dump(obj) }} puts "Raw Oj decode: #{dt} encode: #{et}" ye = Yajl::Encoder.new dt = Benchmark.realtime { iter.times { Yajl::Parser.parse(json) }} et = Benchmark.realtime { iter.times { Yajl::Encoder.encode(obj) }} e2 = Benchmark.realtime { iter.times { ye.encode(obj) }} puts "Raw Yajl decode: #{dt} encode: #{et}, encoder: #{e2}" oj-2.5.3/test/debian_test.rb0000755000004100000410000000342412263716750015765 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'test/unit' require 'stringio' require 'date' require 'bigdecimal' require 'oj' $ruby = RUBY_DESCRIPTION.split(' ')[0] $ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition') def hash_eql(h1, h2) return false if h1.size != h2.size h1.keys.each do |k| return false unless h1[k] == h2[k] end true end class Jam attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def eql?(o) self.class == o.class && @x == o.x && @y == o.y end alias == eql? end# Jam # contributed by sauliusg to fix as_json class Orange < Jam def initialize(x, y) super end def as_json() puts "Orange.as_json called" { :json_class => self.class, :x => @x, :y => @y } end def self.json_create(h) puts "Orange.json_create" self.new(h['x'], h['y']) end end class DebJuice < ::Test::Unit::TestCase def test_class_hash Oj.hash_test() end def test_as_json_object_compat_hash_cached Oj.default_options = { :mode => :compat, :class_cache => true } obj = Orange.new(true, 58) puts "dumping compat with cache" json = Oj.dump(obj, :indent => 2) assert(!json.nil?) dump_and_load(obj, true) end def dump_and_load(obj, trace=false) puts "dumping" json = Oj.dump(obj, :indent => 2) puts json if trace puts "loading" loaded = Oj.load(json); puts "done" assert_equal(obj, loaded) loaded end end oj-2.5.3/test/perf_scp.rb0000755000004100000410000000725312263716750015311 0ustar www-datawww-data#!/usr/bin/env ruby -wW1 # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' require 'yajl' require 'perf' require 'json' require 'json/ext' require 'oj' $verbose = false $indent = 0 $iter = 50000 $with_bignum = false $size = 0 opts = OptionParser.new opts.on("-v", "verbose") { $verbose = true } opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i } opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i } opts.on("-b", "with bignum") { $with_bignum = true } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) $obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array 'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash 'f' => nil, # nil 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } $obj['g'] = 12345678901234567890123456789 if $with_bignum if 0 < $size o = $obj $obj = [] (4 * $size).times do $obj << o end end Oj.default_options = { :indent => $indent, :mode => :compat } $json = Oj.dump($obj) $failed = {} # key is same as String used in tests later class AllSaj < Oj::Saj def initialize() end def hash_start(key) end def hash_end(key) end def array_start(key) end def array_end(key) end def add_value(value, key) end end # AllSaj class NoSaj < Oj::Saj def initialize() end end # NoSaj class NoHandler < Oj::ScHandler def initialize() end end # NoHandler class AllHandler < Oj::ScHandler def initialize() end def hash_start() return nil end def hash_end() end def array_start() return nil end def array_end() end def add_value(value) end def hash_set(h, key, value) end def array_append(a, value) end end # AllHandler saj_handler = AllSaj.new() no_saj = NoSaj.new() sc_handler = AllHandler.new() no_handler = NoHandler.new() def capture_error(tag, orig, load_key, dump_key, &blk) begin obj = blk.call(orig) raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj rescue Exception => e $failed[tag] = "#{e.class}: #{e.message}" end end # Verify that all packages dump and load correctly and return the same Object as the original. capture_error('Yajl', $obj, 'encode', 'parse') { |o| Yajl::Parser.parse(Yajl::Encoder.encode(o)) } capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| JSON.generator = JSON::Ext::Generator; JSON::Ext::Parser.new(JSON.generate(o)).parse } if $verbose puts "json:\n#{$json}\n" end puts '-' * 80 puts "Parse Performance" perf = Perf.new() perf.add('Oj::Saj', 'all') { Oj.saj_parse(saj_handler, $json) } perf.add('Oj::Saj', 'none') { Oj.saj_parse(no_saj, $json) } perf.add('Oj::Scp', 'all') { Oj.sc_parse(sc_handler, $json) } perf.add('Oj::Scp', 'none') { Oj.sc_parse(no_handler, $json) } perf.add('Yajl', 'parse') { Yajl::Parser.parse($json) } unless $failed.has_key?('Yajl') perf.add('JSON::Ext', 'parse') { JSON::Ext::Parser.new($json).parse } unless $failed.has_key?('JSON::Ext') perf.run($iter) unless $failed.empty? puts "The following packages were not included for the reason listed" $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" } end oj-2.5.3/test/test_compat.rb0000755000004100000410000002003412263716750016022 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'test/unit' require 'stringio' require 'date' require 'bigdecimal' require 'oj' $ruby = RUBY_DESCRIPTION.split(' ')[0] $ruby = 'ree' if 'ruby' == $ruby && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition') class Jeez attr_accessor :x, :y def initialize(x, y) @x = x @y = y end def eql?(o) self.class == o.class && @x == o.x && @y == o.y end alias == eql? def to_json(*a) %{{"json_class":"#{self.class}","x":#{@x},"y":#{@y}}} end def self.json_create(h) self.new(h['x'], h['y']) end end # Jeez module One module Two module Three class Deep def initialize() end def eql?(o) self.class == o.class end alias == eql? def to_hash() {'json_class' => "#{self.class.name}"} end def to_json(*a) %{{"json_class":"#{self.class.name}"}} end def self.json_create(h) self.new() end end # Deep end # Three end # Two end # One def hash_eql(h1, h2) return false if h1.size != h2.size h1.keys.each do |k| return false unless h1[k] == h2[k] end true end class CompatJuice < ::Test::Unit::TestCase def test_nil dump_and_load(nil, false) end def test_true dump_and_load(true, false) end def test_false dump_and_load(false, false) end def test_fixnum dump_and_load(0, false) dump_and_load(12345, false) dump_and_load(-54321, false) dump_and_load(1, false) end def test_float dump_and_load(0.0, false) dump_and_load(12345.6789, false) dump_and_load(70.35, false) dump_and_load(-54321.012, false) dump_and_load(2.48e16, false) dump_and_load(2.48e100 * 1.0e10, false) dump_and_load(-2.48e100 * 1.0e10, false) end def test_string dump_and_load('', false) dump_and_load('abc', false) dump_and_load("abc\ndef", false) dump_and_load("a\u0041", false) end def test_encode opts = Oj.default_options Oj.default_options = { :ascii_only => false } unless 'jruby' == $ruby dump_and_load("ぴーたー", false) end Oj.default_options = { :ascii_only => true } json = Oj.dump("ぴーたー") assert_equal(%{"\\u3074\\u30fc\\u305f\\u30fc"}, json) unless 'jruby' == $ruby dump_and_load("ぴーたー", false) end Oj.default_options = opts end def test_unicode # hits the 3 normal ranges and one extended surrogate pair json = %{"\\u019f\\u05e9\\u3074\\ud834\\udd1e"} obj = Oj.load(json) json2 = Oj.dump(obj, :ascii_only => true) assert_equal(json, json2) end def test_array dump_and_load([], false) dump_and_load([true, false], false) dump_and_load(['a', 1, nil], false) dump_and_load([[nil]], false) dump_and_load([[nil], 58], false) end def test_array_deep dump_and_load([1,[2,[3,[4,[5,[6,[7,[8,[9,[10,[11,[12,[13,[14,[15,[16,[17,[18,[19,[20]]]]]]]]]]]]]]]]]]]], false) end # Hash def test_hash dump_and_load({}, false) dump_and_load({ 'true' => true, 'false' => false}, false) dump_and_load({ 'true' => true, 'array' => [], 'hash' => { }}, false) end def test_hash_deep dump_and_load({'1' => { '2' => { '3' => { '4' => { '5' => { '6' => { '7' => { '8' => { '9' => { '10' => { '11' => { '12' => { '13' => { '14' => { '15' => { '16' => { '17' => { '18' => { '19' => { '20' => {}}}}}}}}}}}}}}}}}}}}}, false) end def test_hash_escaped_key json = %{{"a\nb":true,"c\td":false}} obj = Oj.compat_load(json) assert_equal({"a\nb" => true, "c\td" => false}, obj) end def test_bignum_object dump_and_load(7 ** 55, false) end # BigDecimal def test_bigdecimal_compat dump_and_load(BigDecimal.new('3.14159265358979323846'), false) end def test_bigdecimal_load orig = BigDecimal.new('80.51') json = Oj.dump(orig, :mode => :compat, :bigdecimal_as_decimal => true) bg = Oj.load(json, :mode => :compat, :bigdecimal_load => true) assert_equal(BigDecimal, bg.class) assert_equal(orig, bg) end # Stream IO def test_io_string json = %{{ "x":true, "y":58, "z": [1,2,3] } } input = StringIO.new(json) obj = Oj.compat_load(input) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_io_file filename = 'open_file_test.json' File.open(filename, 'w') { |f| f.write(%{{ "x":true, "y":58, "z": [1,2,3] } }) } f = File.new(filename) obj = Oj.compat_load(f) f.close() assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end # symbol_keys option def test_symbol_keys json = %{{ "x":true, "y":58, "z": [1,2,3] } } obj = Oj.compat_load(json, :symbol_keys => true) assert_equal({ :x => true, :y => 58, :z => [1, 2, 3]}, obj) end # comments def test_comment_slash json = %{{ "x":true,//three "y":58, "z": [1,2, 3 // six ]} } obj = Oj.compat_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment_c json = %{{ "x"/*one*/:/*two*/true, "y":58, "z": [1,2,3]} } obj = Oj.compat_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_comment json = %{{ "x"/*one*/:/*two*/true,//three "y":58/*four*/, "z": [1,2/*five*/, 3 // six ] } } obj = Oj.compat_load(json) assert_equal({ 'x' => true, 'y' => 58, 'z' => [1, 2, 3]}, obj) end def test_json_object_compat obj = Jeez.new(true, 58) dump_and_load(obj, false) end def test_json_module_object obj = One::Two::Three::Deep.new() dump_and_load(obj, false) end def test_json_object_create_id expected = Jeez.new(true, 58) json = Oj.dump(expected, :indent => 2, :mode => :compat) obj = Oj.compat_load(json) assert_equal(expected, obj) end def test_json_object_bad json = %{{"json_class":"Junk","x":true}} begin Oj.compat_load(json) rescue Exception => e assert_equal("Oj::ParseError", e.class().name) return end assert(false, "*** expected an exception") end def test_json_object_create_cache expected = Jeez.new(true, 58) json = Oj.dump(expected, :indent => 2, :mode => :compat) obj = Oj.compat_load(json, :class_cache => true) assert_equal(expected, obj) obj = Oj.compat_load(json, :class_cache => false) assert_equal(expected, obj) end def test_json_object_create_id_other expected = Jeez.new(true, 58) json = Oj.dump(expected, :indent => 2, :mode => :compat) json.gsub!('json_class', '_class_') obj = Oj.compat_load(json, :create_id => "_class_") assert_equal(expected, obj) end def test_json_object_create_deep expected = One::Two::Three::Deep.new() json = Oj.dump(expected, :indent => 2, :mode => :compat) obj = Oj.compat_load(json) assert_equal(expected, obj) end def dump_and_load(obj, trace=false) json = Oj.dump(obj, :indent => 2, :mode => :compat) puts json if trace loaded = Oj.compat_load(json); assert_equal(obj, loaded) loaded end end oj-2.5.3/test/test_mimic_after.rb0000755000004100000410000000137312263716750017023 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'test/unit' require 'stringio' require 'oj' require 'json' class MimicAfter < ::Test::Unit::TestCase def test0_mimic_json assert(!defined?(JSON).nil?) Oj.mimic_JSON end # dump def test_dump_string Oj.default_options= {:indent => 2} # JSON this will not change anything json = JSON.dump([1, true, nil]) assert_equal(%{[ 1, true, null]}, json) end end # MimicAfter oj-2.5.3/test/perf_compat.rb0000755000004100000410000000664512263716750016013 0ustar www-datawww-data#!/usr/bin/env ruby -wW1 # encoding: UTF-8 $: << '.' $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'optparse' require 'perf' require 'oj' $verbose = false $indent = 0 $iter = 20000 $with_bignum = false $with_nums = true $size = 0 opts = OptionParser.new opts.on("-v", "verbose") { $verbose = true } opts.on("-c", "--count [Int]", Integer, "iterations") { |i| $iter = i } opts.on("-i", "--indent [Int]", Integer, "indentation") { |i| $indent = i } opts.on("-s", "--size [Int]", Integer, "size (~Kbytes)") { |i| $size = i } opts.on("-h", "--help", "Show this display") { puts opts; Process.exit!(0) } files = opts.parse(ARGV) module One module Two module Three class Empty def initialize() end def eql?(o) self.class == o.class end alias == eql? def to_hash() {'json_class' => "#{self.class.name}"} end def to_json(*a) %{{"json_class":"#{self.class.name}"}} end def self.json_create(h) self.new() end end # Empty end # Three end # Two end # One $obj = { 'a' => 'Alpha', # string 'b' => true, # boolean 'c' => 12345, # number 'd' => [ true, [false, [-123456789, nil], 3.9676, ['Something else.', false], nil]], # mix it up array 'e' => { 'zero' => nil, 'one' => 1, 'two' => 2, 'three' => [3], 'four' => [0, 1, 2, 3, 4] }, # hash 'f' => nil, # nil 'g' => One::Two::Three::Empty.new(), 'h' => { 'a' => { 'b' => { 'c' => { 'd' => {'e' => { 'f' => { 'g' => nil }}}}}}}, # deep hash, not that deep 'i' => [[[[[[[nil]]]]]]] # deep array, again, not that deep } Oj.default_options = { :indent => $indent, :mode => :compat } if 0 < $size s = Oj.dump($obj, :mode => :compat).size + 1 cnt = $size * 1024 / s o = $obj $obj = [] cnt.times do $obj << o end end $json = Oj.dump($obj, :mode => :compat) $failed = {} # key is same as String used in tests later def capture_error(tag, orig, load_key, dump_key, &blk) begin obj = blk.call(orig) raise "#{tag} #{dump_key} and #{load_key} did not return the same object as the original." unless orig == obj rescue Exception => e $failed[tag] = "#{e.class}: #{e.message}" end end # Verify that all packages dump and load correctly and return the same Object as the original. capture_error('Oj:compat', $obj, 'load', 'dump') { |o| Oj.compat_load(Oj.dump(o, :mode => :compat)) } capture_error('JSON::Ext', $obj, 'generate', 'parse') { |o| require 'json' require 'json/ext' JSON.generator = JSON::Ext::Generator JSON.parser = JSON::Ext::Parser JSON.load(JSON.generate(o)) } if $verbose puts "size: #{$json.size}" puts "json:\n#{$json}\n" puts "Oj:compat loaded object:\n#{Oj.compat_load($json)}\n" puts "JSON loaded object:\n#{JSON::Ext::Parser.new($json).parse}\n" end puts '-' * 80 puts "Compat Parse Performance" perf = Perf.new() unless $failed.has_key?('JSON::Ext') perf.add('JSON::Ext', 'parse') { JSON.load($json) } perf.before('JSON::Ext') { JSON.parser = JSON::Ext::Parser } end unless $failed.has_key?('Oj:compat') perf.add('Oj:compat', 'compat_load') { Oj.compat_load($json) } end perf.run($iter) puts puts '-' * 80 puts unless $failed.empty? puts "The following packages were not included for the reason listed" $failed.each { |tag,msg| puts "***** #{tag}: #{msg}" } end oj-2.5.3/test/sample.rb0000644000004100000410000000244112263716750014760 0ustar www-datawww-data#!/usr/bin/env ruby -wW2 if $0 == __FILE__ $: << '.' $: << '..' $: << '../lib' $: << '../ext' end require 'pp' require 'sample/doc' def sample_doc(size=3) colors = [ :black, :gray, :white, :red, :blue, :yellow, :green, :purple, :orange ] d = ::Sample::Doc.new('Sample') # add some history (0..size * 10).each do |i| d.add_change("Changed at t+#{i}.") end # add some layers (1..size).each do |i| layer = ::Sample::Layer.new("Layer-#{i}") (1..size).each do |j| g = ::Sample::Group.new (1..size).each do |k| g2 = ::Sample::Group.new r = ::Sample::Rect.new(j * 40 + 10.0, i * 10.0, 10.123456 / k, 10.0 / k, colors[(i + j + k) % colors.size]) r.add_prop(:part_of, layer.name) g2 << r g2 << ::Sample::Text.new("#{k} in #{j}", r.left, r.top, r.width, r.height) g << g2 end g2 = ::Sample::Group.new (1..size).each do |k| o = ::Sample::Oval.new(j * 40 + 12.0, i * 10.0 + 2.0, 6.0 / k, 6.0 / k, colors[(i + j + k) % colors.size]) o.add_prop(:inside, true) g << o end g << g2 layer << g end d.layers[layer.name] = layer end # some properties d.add_prop(:purpose, 'an example') d end oj-2.5.3/test/bug.rb0000755000004100000410000000072412263716750014261 0ustar www-datawww-data#!/usr/bin/env ruby # encoding: UTF-8 # Ubuntu does not accept arguments to ruby when called using env. To get warnings to show up the -w options is # required. That can be set in the RUBYOPT environment variable. # export RUBYOPT=-w $VERBOSE = true $: << File.join(File.dirname(__FILE__), "../lib") $: << File.join(File.dirname(__FILE__), "../ext") require 'oj' obj = Oj.load_file('bug.json', :mode => :object) puts Oj.dump(obj, :mode => :object, :indent => 0) oj-2.5.3/LICENSE0000644000004100000410000000272012263716750013200 0ustar www-datawww-dataCopyright (c) 2012, Peter Ohler All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Peter Ohler nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. oj-2.5.3/checksums.yaml.gz0000444000004100000410000000041512263716750015460 0ustar www-datawww-data_Re9R0 E"c-֒ȲDEäi?n+/g=b#^>dYY2a1HUIpO_ONV8P s)0b,¹:Ԛu5D+b"&uWHa]lUpօ0`)gXXMCN>vΡ;ӋB6gYbi`&YTEnɈ@+耮6ΩӳVū(iVf_~,oj-2.5.3/ext/0000755000004100000410000000000012263716750012772 5ustar www-datawww-dataoj-2.5.3/ext/oj/0000755000004100000410000000000012263716750013402 5ustar www-datawww-dataoj-2.5.3/ext/oj/resolve.c0000644000004100000410000000724412263716750015234 0ustar www-datawww-data/* resolve.c * Copyright (c) 2012, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #if USE_PTHREAD_MUTEX #include #endif #include "oj.h" #include "err.h" #include "parse.h" #include "hash.h" inline static VALUE resolve_classname(VALUE mod, const char *classname, int auto_define) { VALUE clas; ID ci = rb_intern(classname); if (rb_const_defined_at(mod, ci)) { clas = rb_const_get_at(mod, ci); } else if (auto_define) { clas = rb_define_class_under(mod, classname, oj_bag_class); } else { clas = Qundef; } return clas; } static VALUE resolve_classpath(ParseInfo pi, const char *name, size_t len, int auto_define) { char class_name[1024]; VALUE clas; char *end = class_name + sizeof(class_name) - 1; char *s; const char *n = name; clas = rb_cObject; for (s = class_name; 0 < len; n++, len--) { if (':' == *n) { *s = '\0'; n++; len--; if (':' != *n) { return Qundef; } if (Qundef == (clas = resolve_classname(clas, class_name, auto_define))) { return Qundef; } s = class_name; } else if (end <= s) { return Qundef; } else { *s++ = *n; } } *s = '\0'; if (Qundef == (clas = resolve_classname(clas, class_name, auto_define))) { if (sizeof(class_name) - 1 < len) { len = sizeof(class_name) - 1; } memcpy(class_name, name, len); class_name[len] = '\0'; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "class %s is not defined", class_name); } return clas; } VALUE oj_name2class(ParseInfo pi, const char *name, size_t len, int auto_define) { VALUE clas; VALUE *slot; if (No == pi->options.class_cache) { return resolve_classpath(pi, name, len, auto_define); } #if USE_PTHREAD_MUTEX pthread_mutex_lock(&oj_cache_mutex); #elif USE_RB_MUTEX rb_mutex_lock(oj_cache_mutex); #endif if (Qnil == (clas = oj_class_hash_get(name, len, &slot))) { if (Qundef != (clas = resolve_classpath(pi, name, len, auto_define))) { *slot = clas; } } #if USE_PTHREAD_MUTEX pthread_mutex_unlock(&oj_cache_mutex); #elif USE_RB_MUTEX rb_mutex_unlock(oj_cache_mutex); #endif return clas; } oj-2.5.3/ext/oj/object.c0000644000004100000410000003576612263716750015035 0ustar www-datawww-data/* object.c * Copyright (c) 2012, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "oj.h" #include "err.h" #include "parse.h" #include "resolve.h" #include "hash.h" #include "odd.h" #include "encode.h" inline static long read_long(const char *str, size_t len) { long n = 0; for (; 0 < len; str++, len--) { if ('0' <= *str && *str <= '9') { n = n * 10 + (*str - '0'); } else { return -1; } } return n; } static VALUE hash_key(ParseInfo pi, const char *key, size_t klen, char k1) { VALUE rkey; if (':' == k1) { rkey = rb_str_new(key + 1, klen - 1); rkey = oj_encode(rkey); rkey = rb_funcall(rkey, oj_to_sym_id, 0); } else { rkey = rb_str_new(key, klen); rkey = oj_encode(rkey); if (Yes == pi->options.sym_key) { rkey = rb_str_intern(rkey); } } return rkey; } static VALUE str_to_value(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = Qnil; if (':' == *orig && 0 < len) { rstr = rb_str_new(str + 1, len - 1); rstr = oj_encode(rstr); rstr = rb_funcall(rstr, oj_to_sym_id, 0); } else if (pi->circ_array && 3 <= len && '^' == *orig && 'r' == orig[1]) { long i = read_long(str + 2, len - 2); if (0 > i) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a valid ID number"); return Qnil; } rstr = oj_circ_array_get(pi->circ_array, i); } else { rstr = rb_str_new(str, len); rstr = oj_encode(rstr); } return rstr; } static int hat_cstr(ParseInfo pi, Val parent, const char *key, size_t klen, const char *str, size_t len) { if (2 == klen) { switch (key[1]) { case 'o': // object { // name2class sets and error if the class is not found or created VALUE clas = oj_name2class(pi, str, len, Yes == pi->options.auto_define); if (Qundef != clas) { parent->val = rb_obj_alloc(clas); } } break; case 'O': // odd object { Odd odd = oj_get_oddc(str, len); if (0 == odd) { return 0; } parent->val = odd->clas; parent->odd_args = oj_odd_alloc_args(odd); } break; case 'm': parent->val = rb_str_new(str + 1, len - 1); parent->val = oj_encode(parent->val); parent->val = rb_funcall(parent->val, oj_to_sym_id, 0); break; case 's': parent->val = rb_str_new(str, len); parent->val = oj_encode(parent->val); break; case 'c': // class parent->val = oj_name2class(pi, str, len, Yes == pi->options.auto_define); break; default: return 0; break; } return 1; // handled } return 0; } static int hat_num(ParseInfo pi, Val parent, const char *key, size_t klen, NumInfo ni) { if (2 == klen) { switch (key[1]) { case 't': // time as a float { int64_t nsec = ni->num * 1000000000LL / ni->div; if (ni->neg) { ni->i = -ni->i; if (0 < nsec) { ni->i--; nsec = 1000000000LL - nsec; } } #if HAS_NANO_TIME parent->val = rb_time_nano_new(ni->i, (long)nsec); #else parent->val = rb_time_new(ni->i, (long)(nsec / 1000)); #endif } break; case 'i': // circular index if (!ni->infinity && !ni->neg && 1 == ni->div && 0 == ni->exp && 0 != pi->circ_array) { // fixnum if (Qnil == parent->val) { parent->val = rb_hash_new(); } oj_circ_array_set(pi->circ_array, parent->val, ni->i); } else { return 0; } break; default: return 0; break; } return 1; // handled } return 0; } static int hat_value(ParseInfo pi, Val parent, const char *key, size_t klen, volatile VALUE value) { if (T_ARRAY == rb_type(value)) { int len = (int)RARRAY_LEN(value); if (2 == klen && 'u' == key[1]) { VALUE sc; if (0 == len) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data"); return 1; } // If struct is not defined then we let this fail and raise an exception. sc = rb_const_get(oj_struct_class, rb_to_id(*RARRAY_PTR(value))); // Create a properly initialized struct instance without calling the initialize method. parent->val = rb_obj_alloc(sc); // If the JSON array has more entries than the struct class allows, we record an error. #ifdef RSTRUCT_LEN // MRI >= 1.9 if (len - 1 > RSTRUCT_LEN(parent->val)) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data"); } else { MEMCPY(RSTRUCT_PTR(parent->val), RARRAY_PTR(value) + 1, VALUE, len - 1); } #else { // MRI < 1.9 or Rubinius int slen = FIX2INT(rb_funcall2(parent->val, oj_length_id, 0, 0)); int i; if (len - 1 > slen) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "Invalid struct data"); } else { for (i = 0; i < slen; i++) { rb_struct_aset(parent->val, INT2FIX(i), RARRAY_PTR(value)[i + 1]); } } } #endif return 1; } else if (3 <= klen && '#' == key[1]) { volatile VALUE *a; if (2 != len) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid hash pair"); return 1; } parent->val = rb_hash_new(); a = RARRAY_PTR(value); rb_hash_aset(parent->val, *a, a[1]); return 1; } } return 0; } static void copy_ivars(VALUE target, VALUE src) { volatile VALUE vars = rb_funcall(src, oj_instance_variables_id, 0); volatile VALUE *np = RARRAY_PTR(vars); ID vid; int i, cnt = (int)RARRAY_LEN(vars); const char *attr; for (i = cnt; 0 < i; i--, np++) { vid = rb_to_id(*np); attr = rb_id2name(vid); if ('@' == *attr) { rb_ivar_set(target, vid, rb_ivar_get(src, vid)); } } } static void set_obj_ivar(Val parent, const char *key, size_t klen, VALUE value) { ID var_id; ID *slot; if ('~' == *key && Qtrue == rb_obj_is_kind_of(parent->val, rb_eException)) { if (5 == klen && 0 == strncmp("~mesg", key, klen)) { VALUE args[1]; VALUE prev = parent->val; args[0] = value; parent->val = rb_class_new_instance(1, args, rb_class_of(parent->val)); copy_ivars(parent->val, prev); } else if (3 == klen && 0 == strncmp("~bt", key, klen)) { rb_funcall(parent->val, rb_intern("set_backtrace"), 1, value); } } #if USE_PTHREAD_MUTEX pthread_mutex_lock(&oj_cache_mutex); #elif USE_RB_MUTEX rb_mutex_lock(oj_cache_mutex); #endif if (0 == (var_id = oj_attr_hash_get(key, klen, &slot))) { char attr[256]; if (sizeof(attr) <= klen + 2) { char *buf = ALLOC_N(char, klen + 2); if ('~' == *key) { strncpy(buf, key + 1, klen - 1); buf[klen - 1] = '\0'; } else { *buf = '@'; strncpy(buf + 1, key, klen); buf[klen + 1] = '\0'; } var_id = rb_intern(buf); xfree(buf); } else { if ('~' == *key) { strncpy(attr, key + 1, klen - 1); attr[klen - 1] = '\0'; } else { *attr = '@'; strncpy(attr + 1, key, klen); attr[klen + 1] = '\0'; } var_id = rb_intern(attr); } *slot = var_id; } #if USE_PTHREAD_MUTEX pthread_mutex_unlock(&oj_cache_mutex); #elif USE_RB_MUTEX rb_mutex_unlock(oj_cache_mutex); #endif rb_ivar_set(parent->val, var_id, value); } static void hash_set_cstr(ParseInfo pi, const char *key, size_t klen, const char *str, size_t len, const char *orig) { Val parent = stack_peek(&pi->stack); WHICH_TYPE: switch (rb_type(parent->val)) { case T_NIL: parent->odd_args = 0; // make sure it is 0 in case not odd if ('^' != *key || !hat_cstr(pi, parent, key, klen, str, len)) { parent->val = rb_hash_new(); goto WHICH_TYPE; } break; case T_HASH: rb_hash_aset(parent->val, hash_key(pi, key, klen, parent->k1), str_to_value(pi, str, len, orig)); break; case T_OBJECT: set_obj_ivar(parent, key, klen, str_to_value(pi, str, len, orig)); break; case T_CLASS: if (0 == parent->odd_args) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an odd class", rb_class2name(rb_obj_class(parent->val))); return; } else if (0 != oj_odd_set_arg(parent->odd_args, key, klen, str_to_value(pi, str, len, orig))) { char buf[256]; if (sizeof(buf) - 1 <= klen) { klen = sizeof(buf) - 2; } memcpy(buf, key, klen); buf[klen] = '\0'; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an attribute of %s", buf, rb_class2name(rb_obj_class(parent->val))); } break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "can not add attributes to a %s", rb_class2name(rb_obj_class(parent->val))); return; } } static void hash_set_num(ParseInfo pi, const char *key, size_t klen, NumInfo ni) { Val parent = stack_peek(&pi->stack); WHICH_TYPE: switch (rb_type(parent->val)) { case T_NIL: parent->odd_args = 0; // make sure it is 0 in case not odd if ('^' != *key || !hat_num(pi, parent, key, klen, ni)) { parent->val = rb_hash_new(); goto WHICH_TYPE; } break; case T_HASH: rb_hash_aset(parent->val, hash_key(pi, key, klen, parent->k1), oj_num_as_value(ni)); break; case T_OBJECT: if (2 == klen && '^' == *key && 'i' == key[1] && !ni->infinity && !ni->neg && 1 == ni->div && 0 == ni->exp && 0 != pi->circ_array) { // fixnum oj_circ_array_set(pi->circ_array, parent->val, ni->i); } else { set_obj_ivar(parent, key, klen, oj_num_as_value(ni)); } break; case T_CLASS: if (0 == parent->odd_args) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an odd class", rb_class2name(rb_obj_class(parent->val))); return; } else if (0 != oj_odd_set_arg(parent->odd_args, key, klen, oj_num_as_value(ni))) { char buf[256]; if (sizeof(buf) - 1 <= klen) { klen = sizeof(buf) - 2; } memcpy(buf, key, klen); buf[klen] = '\0'; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an attribute of %s", buf, rb_class2name(rb_obj_class(parent->val))); } break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "can not add attributes to a %s", rb_class2name(rb_obj_class(parent->val))); return; } } static void hash_set_value(ParseInfo pi, const char *key, size_t klen, VALUE value) { Val parent = stack_peek(&pi->stack); WHICH_TYPE: switch (rb_type(parent->val)) { case T_NIL: parent->odd_args = 0; // make sure it is 0 in case not odd if ('^' != *key || !hat_value(pi, parent, key, klen, value)) { parent->val = rb_hash_new(); goto WHICH_TYPE; } break; case T_HASH: if (3 <= klen && '#' == key[1] && T_ARRAY == rb_type(value)) { long len = RARRAY_LEN(value); VALUE *a = RARRAY_PTR(value); if (2 != len) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid hash pair"); return; } rb_hash_aset(parent->val, *a, a[1]); } else { rb_hash_aset(parent->val, hash_key(pi, key, klen, parent->k1), value); } break; case T_OBJECT: set_obj_ivar(parent, key, klen, value); break; case T_CLASS: if (0 == parent->odd_args) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an odd class", rb_class2name(rb_obj_class(parent->val))); return; } else if (0 != oj_odd_set_arg(parent->odd_args, key, klen, value)) { char buf[256]; if (sizeof(buf) - 1 <= klen) { klen = sizeof(buf) - 2; } memcpy(buf, key, klen); buf[klen] = '\0'; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "%s is not an attribute of %s", buf, rb_class2name(rb_obj_class(parent->val))); } break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "can not add attributes to a %s", rb_class2name(rb_obj_class(parent->val))); return; } } static VALUE start_hash(ParseInfo pi) { return Qnil; } static void end_hash(struct _ParseInfo *pi) { Val parent = stack_peek(&pi->stack); if (Qnil == parent->val) { parent->val = rb_hash_new(); } else if (0 != parent->odd_args) { OddArgs oa = parent->odd_args; parent->val = rb_funcall2(oa->odd->create_obj, oa->odd->create_op, oa->odd->attr_cnt, oa->args); oj_odd_free(oa); parent->odd_args = 0; } } static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { if (3 <= len && 0 != pi->circ_array) { if ('i' == str[1]) { long i = read_long(str + 2, len - 2); if (0 < i) { oj_circ_array_set(pi->circ_array, stack_peek(&pi->stack)->val, i); return; } } else if ('r' == str[1]) { long i = read_long(str + 2, len - 2); if (0 < i) { rb_ary_push(stack_peek(&pi->stack)->val, oj_circ_array_get(pi->circ_array, i)); return; } } } rb_ary_push(stack_peek(&pi->stack)->val, str_to_value(pi, str, len, orig)); } static void add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { pi->stack.head->val = str_to_value(pi, str, len, orig); } VALUE oj_object_parse(int argc, VALUE *argv, VALUE self) { struct _ParseInfo pi; pi.options = oj_default_options; oj_set_strict_callbacks(&pi); pi.end_hash = end_hash; pi.start_hash = start_hash; pi.hash_set_cstr = hash_set_cstr; pi.hash_set_num = hash_set_num; pi.hash_set_value = hash_set_value; pi.add_cstr = add_cstr; pi.array_append_cstr = array_append_cstr; return oj_pi_parse(argc, argv, &pi, 0, 0); } VALUE oj_object_parse_cstr(int argc, VALUE *argv, char *json, size_t len) { struct _ParseInfo pi; pi.options = oj_default_options; oj_set_strict_callbacks(&pi); pi.end_hash = end_hash; pi.start_hash = start_hash; pi.hash_set_cstr = hash_set_cstr; pi.hash_set_num = hash_set_num; pi.hash_set_value = hash_set_value; pi.add_cstr = add_cstr; pi.array_append_cstr = array_append_cstr; return oj_pi_parse(argc, argv, &pi, json, len); } oj-2.5.3/ext/oj/buf.h0000644000004100000410000000612612263716750014334 0ustar www-datawww-data/* buf.h * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __OJ_BUF_H__ #define __OJ_BUF_H__ #include "ruby.h" typedef struct _Buf { char *head; char *end; char *tail; char base[1024]; } *Buf; inline static void buf_init(Buf buf) { buf->head = buf->base; buf->end = buf->base + sizeof(buf->base) - 1; buf->tail = buf->head; } inline static void buf_cleanup(Buf buf) { if (buf->base != buf->head) { xfree(buf->head); } } inline static size_t buf_len(Buf buf) { return buf->tail - buf->head; } inline static void buf_append_string(Buf buf, const char *s, size_t slen) { if (buf->end <= buf->tail + slen) { size_t len = buf->end - buf->head; size_t toff = buf->tail - buf->head; size_t new_len = len + slen + len / 2; if (buf->base == buf->head) { buf->head = ALLOC_N(char, new_len); memcpy(buf->head, buf->base, len); } else { REALLOC_N(buf->head, char, new_len); } buf->tail = buf->head + toff; buf->end = buf->head + new_len - 1; } memcpy(buf->tail, s, slen); buf->tail += slen; } inline static void buf_append(Buf buf, char c) { if (buf->end <= buf->tail) { size_t len = buf->end - buf->head; size_t toff = buf->tail - buf->head; size_t new_len = len + len / 2; if (buf->base == buf->head) { buf->head = ALLOC_N(char, new_len); memcpy(buf->head, buf->base, len); } else { REALLOC_N(buf->head, char, new_len); } buf->tail = buf->head + toff; buf->end = buf->head + new_len - 1; } *buf->tail = c; buf->tail++; *buf->tail = '\0'; // TBD temp for debugging } #endif /* __OJ_BUF_H__ */ oj-2.5.3/ext/oj/extconf.rb0000644000004100000410000000473112263716750015402 0ustar www-datawww-datarequire 'mkmf' require 'rbconfig' extension_name = 'oj' dir_config(extension_name) parts = RUBY_DESCRIPTION.split(' ') type = parts[0] type = type[4..-1] if type.start_with?('tcs-') type = 'ree' if 'ruby' == type && RUBY_DESCRIPTION.include?('Ruby Enterprise Edition') is_windows = RbConfig::CONFIG['host_os'] =~ /(mingw|mswin)/ platform = RUBY_PLATFORM version = RUBY_VERSION.split('.') puts ">>>>> Creating Makefile for #{type} version #{RUBY_VERSION} on #{platform} <<<<<" dflags = { 'RUBY_TYPE' => type, (type.upcase + '_RUBY') => nil, 'RUBY_VERSION' => RUBY_VERSION, 'RUBY_VERSION_MAJOR' => version[0], 'RUBY_VERSION_MINOR' => version[1], 'RUBY_VERSION_MICRO' => version[2], 'HAS_RB_TIME_TIMESPEC' => (!is_windows && 'ruby' == type && ('1.9.3' == RUBY_VERSION || '2' <= version[0])) ? 1 : 0, 'HAS_ENCODING_SUPPORT' => (('ruby' == type || 'rubinius' == type) && (('1' == version[0] && '9' == version[1]) || '2' <= version[0])) ? 1 : 0, 'HAS_NANO_TIME' => ('ruby' == type && ('1' == version[0] && '9' == version[1]) || '2' <= version[0]) ? 1 : 0, 'HAS_IVAR_HELPERS' => ('ruby' == type && !is_windows && (('1' == version[0] && '9' == version[1]) || '2' <= version[0])) ? 1 : 0, 'HAS_EXCEPTION_MAGIC' => ('ruby' == type && ('1' == version[0] && '9' == version[1])) ? 0 : 1, 'HAS_PROC_WITH_BLOCK' => ('ruby' == type && (('1' == version[0] && '9' == version[1]) || '2' <= version[0])) ? 1 : 0, 'HAS_TOP_LEVEL_ST_H' => ('ree' == type || ('ruby' == type && '1' == version[0] && '8' == version[1])) ? 1 : 0, 'NEEDS_RATIONAL' => ('1' == version[0] && '8' == version[1]) ? 1 : 0, 'IS_WINDOWS' => is_windows ? 1 : 0, 'USE_PTHREAD_MUTEX' => is_windows ? 0 : 1, 'USE_RB_MUTEX' => (is_windows && !('1' == version[0] && '8' == version[1])) ? 1 : 0, } # This is a monster hack to get around issues with 1.9.3-p0 on CentOS 5.4. SO # some reason math.h and string.h contents are not processed. Might be a # missing #define. This is the quick and easy way around it. if 'x86_64-linux' == RUBY_PLATFORM && '1.9.3' == RUBY_VERSION && '2011-10-30' == RUBY_RELEASE_DATE begin dflags['NEEDS_STPCPY'] = nil if File.read('/etc/redhat-release').include?('CentOS release 5.4') rescue Exception end else dflags['NEEDS_STPCPY'] = nil if is_windows end dflags.each do |k,v| if v.nil? $CPPFLAGS += " -D#{k}" else $CPPFLAGS += " -D#{k}=#{v}" end end $CPPFLAGS += ' -Wall' #puts "*** $CPPFLAGS: #{$CPPFLAGS}" create_makefile(extension_name) %x{make clean} oj-2.5.3/ext/oj/resolve.h0000644000004100000410000000336112263716750015235 0ustar www-datawww-data/* resolve.h * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __OJ_RESOLVE_H__ #define __OJ_RESOLVE_H__ #include "ruby.h" extern VALUE oj_name2class(ParseInfo pi, const char *name, size_t len, int auto_define); #endif /* __OJ_RESOLVE_H__ */ oj-2.5.3/ext/oj/circarray.c0000644000004100000410000000534412263716750015533 0ustar www-datawww-data/* circarray.c * Copyright (c) 2012, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "circarray.h" CircArray oj_circ_array_new() { CircArray ca; if (0 == (ca = ALLOC(struct _CircArray))) { rb_raise(rb_eNoMemError, "not enough memory\n"); } ca->objs = ca->obj_array; ca->size = sizeof(ca->obj_array) / sizeof(VALUE); ca->cnt = 0; return ca; } void oj_circ_array_free(CircArray ca) { if (ca->objs != ca->obj_array) { xfree(ca->objs); } xfree(ca); } void oj_circ_array_set(CircArray ca, VALUE obj, unsigned long id) { if (0 < id && 0 != ca) { unsigned long i; if (ca->size < id) { unsigned long cnt = id + 512; if (ca->objs == ca->obj_array) { if (0 == (ca->objs = ALLOC_N(VALUE, cnt))) { rb_raise(rb_eNoMemError, "not enough memory\n"); } memcpy(ca->objs, ca->obj_array, sizeof(VALUE) * ca->cnt); } else { REALLOC_N(ca->objs, VALUE, cnt); } ca->size = cnt; } id--; for (i = ca->cnt; i < id; i++) { ca->objs[i] = Qnil; } ca->objs[id] = obj; if (ca->cnt <= id) { ca->cnt = id + 1; } } } VALUE oj_circ_array_get(CircArray ca, unsigned long id) { VALUE obj = Qnil; if (id <= ca->cnt && 0 != ca) { obj = ca->objs[id - 1]; } return obj; } oj-2.5.3/ext/oj/strict.c0000644000004100000410000001120012263716750015050 0ustar www-datawww-data/* strict.c * Copyright (c) 2012, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include "oj.h" #include "err.h" #include "parse.h" #include "encode.h" // Workaround in case INFINITY is not defined in math.h or if the OS is CentOS #define OJ_INFINITY (1.0/0.0) static void noop_end(struct _ParseInfo *pi) { } static void add_value(ParseInfo pi, VALUE val) { pi->stack.head->val = val; } static void add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = rb_str_new(str, len); rstr = oj_encode(rstr); pi->stack.head->val = rstr; } static void add_num(ParseInfo pi, NumInfo ni) { pi->stack.head->val = oj_num_as_value(ni); } static VALUE start_hash(ParseInfo pi) { return rb_hash_new(); } static VALUE hash_key(ParseInfo pi, const char *key, size_t klen) { volatile VALUE rkey = rb_str_new(key, klen); rkey = oj_encode(rkey); if (Yes == pi->options.sym_key) { rkey = rb_str_intern(rkey); } return rkey; } static void hash_set_cstr(ParseInfo pi, const char *key, size_t klen, const char *str, size_t len, const char *orig) { volatile VALUE rstr = rb_str_new(str, len); rstr = oj_encode(rstr); rb_hash_aset(stack_peek(&pi->stack)->val, hash_key(pi, key, klen), rstr); } static void hash_set_num(struct _ParseInfo *pi, const char *key, size_t klen, NumInfo ni) { rb_hash_aset(stack_peek(&pi->stack)->val, hash_key(pi, key, klen), oj_num_as_value(ni)); } static void hash_set_value(ParseInfo pi, const char *key, size_t klen, VALUE value) { rb_hash_aset(stack_peek(&pi->stack)->val, hash_key(pi, key, klen), value); } static VALUE start_array(ParseInfo pi) { return rb_ary_new(); } static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = rb_str_new(str, len); rstr = oj_encode(rstr); rb_ary_push(stack_peek(&pi->stack)->val, rstr); } static void array_append_num(ParseInfo pi, NumInfo ni) { rb_ary_push(stack_peek(&pi->stack)->val, oj_num_as_value(ni)); } static void array_append_value(ParseInfo pi, VALUE value) { rb_ary_push(stack_peek(&pi->stack)->val, value); } void oj_set_strict_callbacks(ParseInfo pi) { pi->start_hash = start_hash; pi->end_hash = noop_end; pi->hash_set_cstr = hash_set_cstr; pi->hash_set_num = hash_set_num; pi->hash_set_value = hash_set_value; pi->start_array = start_array; pi->end_array = noop_end; pi->array_append_cstr = array_append_cstr; pi->array_append_num = array_append_num; pi->array_append_value = array_append_value; pi->add_cstr = add_cstr; pi->add_num = add_num; pi->add_value = add_value; pi->expect_value = 1; } VALUE oj_strict_parse(int argc, VALUE *argv, VALUE self) { struct _ParseInfo pi; pi.options = oj_default_options; oj_set_strict_callbacks(&pi); return oj_pi_parse(argc, argv, &pi, 0, 0); } VALUE oj_strict_parse_cstr(int argc, VALUE *argv, char *json, size_t len) { struct _ParseInfo pi; pi.options = oj_default_options; oj_set_strict_callbacks(&pi); return oj_pi_parse(argc, argv, &pi, json, len); } oj-2.5.3/ext/oj/hash.h0000644000004100000410000000365112263716750014503 0ustar www-datawww-data/* hash.h * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __OJ_HASH_H__ #define __OJ_HASH_H__ #include "ruby.h" typedef struct _Hash *Hash; extern void oj_hash_init(); extern VALUE oj_class_hash_get(const char *key, size_t len, VALUE **slotp); extern ID oj_attr_hash_get(const char *key, size_t len, ID **slotp); extern void oj_hash_print(); extern char* oj_strndup(const char *s, size_t len); #endif /* __OJ_HASH_H__ */ oj-2.5.3/ext/oj/saj.c0000644000004100000410000004572012263716750014333 0ustar www-datawww-data/* sajkey.c * Copyright (c) 2012, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #if !IS_WINDOWS #include /* for getrlimit() on linux */ #endif #include #include #include #include #include #include // Workaround in case INFINITY is not defined in math.h or if the OS is CentOS #define OJ_INFINITY (1.0/0.0) #include "oj.h" #include "encode.h" typedef struct _ParseInfo { char *str; /* buffer being read from */ char *s; /* current position in buffer */ void *stack_min; VALUE handler; int has_hash_start; int has_hash_end; int has_array_start; int has_array_end; int has_add_value; int has_error; } *ParseInfo; static void read_next(ParseInfo pi, const char *key); static void read_hash(ParseInfo pi, const char *key); static void read_array(ParseInfo pi, const char *key); static void read_str(ParseInfo pi, const char *key); static void read_num(ParseInfo pi, const char *key); static void read_true(ParseInfo pi, const char *key); static void read_false(ParseInfo pi, const char *key); static void read_nil(ParseInfo pi, const char *key); static void next_non_white(ParseInfo pi); static char* read_quoted_value(ParseInfo pi); static void skip_comment(ParseInfo pi); /* This XML parser is a single pass, destructive, callback parser. It is a * single pass parse since it only make one pass over the characters in the * XML document string. It is destructive because it re-uses the content of * the string for values in the callback and places \0 characters at various * places to mark the end of tokens and strings. It is a callback parser like * a SAX parser because it uses callback when document elements are * encountered. * * Parsing is very tolerant. Lack of headers and even mispelled element * endings are passed over without raising an error. A best attempt is made in * all cases to parse the string. */ inline static void call_error(const char *msg, ParseInfo pi, const char* file, int line) { char buf[128]; const char *s = pi->s; int jline = 1; int col = 1; for (; pi->str < s && '\n' != *s; s--) { col++; } for (; pi->str < s; s--) { if ('\n' == *s) { jline++; } } sprintf(buf, "%s at line %d, column %d [%s:%d]", msg, jline, col, file, line); rb_funcall(pi->handler, oj_error_id, 3, rb_str_new2(buf), LONG2NUM(jline), LONG2NUM(col)); } inline static void next_non_white(ParseInfo pi) { for (; 1; pi->s++) { switch(*pi->s) { case ' ': case '\t': case '\f': case '\n': case '\r': break; case '/': skip_comment(pi); break; default: return; } } } inline static void next_white(ParseInfo pi) { for (; 1; pi->s++) { switch(*pi->s) { case ' ': case '\t': case '\f': case '\n': case '\r': case '\0': return; default: break; } } } inline static void call_add_value(VALUE handler, VALUE value, const char *key) { volatile VALUE k; if (0 == key) { k = Qnil; } else { k = rb_str_new2(key); k = oj_encode(k); } rb_funcall(handler, oj_add_value_id, 2, value, k); } inline static void call_no_value(VALUE handler, ID method, const char *key) { volatile VALUE k; if (0 == key) { k = Qnil; } else { k = rb_str_new2(key); k = oj_encode(k); } rb_funcall(handler, method, 1, k); } static void skip_comment(ParseInfo pi) { pi->s++; /* skip first / */ if ('*' == *pi->s) { pi->s++; for (; '\0' != *pi->s; pi->s++) { if ('*' == *pi->s && '/' == *(pi->s + 1)) { pi->s++; return; } else if ('\0' == *pi->s) { if (pi->has_error) { call_error("comment not terminated", pi, __FILE__, __LINE__); } else { raise_error("comment not terminated", pi->str, pi->s); } } } } else if ('/' == *pi->s) { for (; 1; pi->s++) { switch (*pi->s) { case '\n': case '\r': case '\f': case '\0': return; default: break; } } } else { if (pi->has_error) { call_error("invalid comment", pi, __FILE__, __LINE__); } else { raise_error("invalid comment", pi->str, pi->s); } } } static void read_next(ParseInfo pi, const char *key) { VALUE obj; if ((void*)&obj < pi->stack_min) { rb_raise(rb_eSysStackError, "JSON is too deeply nested"); } next_non_white(pi); /* skip white space */ switch (*pi->s) { case '{': read_hash(pi, key); break; case '[': read_array(pi, key); break; case '"': read_str(pi, key); break; case '+': case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': read_num(pi, key); break; case 'I': read_num(pi, key); break; case 't': read_true(pi, key); break; case 'f': read_false(pi, key); break; case 'n': read_nil(pi, key); break; case '\0': return; default: return; } } static void read_hash(ParseInfo pi, const char *key) { const char *ks; if (pi->has_hash_start) { call_no_value(pi->handler, oj_hash_start_id, key); } pi->s++; next_non_white(pi); if ('}' == *pi->s) { pi->s++; } else { while (1) { next_non_white(pi); ks = read_quoted_value(pi); next_non_white(pi); if (':' == *pi->s) { pi->s++; } else { if (pi->has_error) { call_error("invalid format, expected :", pi, __FILE__, __LINE__); } raise_error("invalid format, expected :", pi->str, pi->s); } read_next(pi, ks); next_non_white(pi); if ('}' == *pi->s) { pi->s++; break; } else if (',' == *pi->s) { pi->s++; } else { if (pi->has_error) { call_error("invalid format, expected , or } while in an object", pi, __FILE__, __LINE__); } raise_error("invalid format, expected , or } while in an object", pi->str, pi->s); } } } if (pi->has_hash_end) { call_no_value(pi->handler, oj_hash_end_id, key); } } static void read_array(ParseInfo pi, const char *key) { if (pi->has_array_start) { call_no_value(pi->handler, oj_array_start_id, key); } pi->s++; next_non_white(pi); if (']' == *pi->s) { pi->s++; } else { while (1) { read_next(pi, 0); next_non_white(pi); if (',' == *pi->s) { pi->s++; } else if (']' == *pi->s) { pi->s++; break; } else { if (pi->has_error) { call_error("invalid format, expected , or ] while in an array", pi, __FILE__, __LINE__); } raise_error("invalid format, expected , or ] while in an array", pi->str, pi->s); } } } if (pi->has_array_end) { call_no_value(pi->handler, oj_array_end_id, key); } } static void read_str(ParseInfo pi, const char *key) { char *text; text = read_quoted_value(pi); if (pi->has_add_value) { VALUE s = rb_str_new2(text); s = oj_encode(s); call_add_value(pi->handler, s, key); } } #ifdef RUBINIUS_RUBY #define NUM_MAX 0x07FFFFFF #else #define NUM_MAX (FIXNUM_MAX >> 8) #endif static void read_num(ParseInfo pi, const char *key) { char *start = pi->s; int64_t n = 0; long a = 0; long div = 1; long e = 0; int neg = 0; int eneg = 0; int big = 0; if ('-' == *pi->s) { pi->s++; neg = 1; } else if ('+' == *pi->s) { pi->s++; } if ('I' == *pi->s) { if (0 != strncmp("Infinity", pi->s, 8)) { if (pi->has_error) { call_error("number or other value", pi, __FILE__, __LINE__); } raise_error("number or other value", pi->str, pi->s); } pi->s += 8; if (neg) { if (pi->has_add_value) { call_add_value(pi->handler, rb_float_new(-OJ_INFINITY), key); } } else { if (pi->has_add_value) { call_add_value(pi->handler, rb_float_new(OJ_INFINITY), key); } } return; } for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) { if (big) { big++; } else { n = n * 10 + (*pi->s - '0'); if (NUM_MAX <= n) { big = 1; } } } if ('.' == *pi->s) { pi->s++; for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) { a = a * 10 + (*pi->s - '0'); div *= 10; if (NUM_MAX <= div) { big = 1; } } } if ('e' == *pi->s || 'E' == *pi->s) { pi->s++; if ('-' == *pi->s) { pi->s++; eneg = 1; } else if ('+' == *pi->s) { pi->s++; } for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) { e = e * 10 + (*pi->s - '0'); if (NUM_MAX <= e) { big = 1; } } } if (0 == e && 0 == a && 1 == div) { if (big) { char c = *pi->s; *pi->s = '\0'; if (pi->has_add_value) { call_add_value(pi->handler, rb_funcall(oj_bigdecimal_class, oj_new_id, 1, rb_str_new2(start)), key); } *pi->s = c; } else { if (neg) { n = -n; } if (pi->has_add_value) { call_add_value(pi->handler, LONG2NUM(n), key); } } return; } else { /* decimal */ if (big) { char c = *pi->s; *pi->s = '\0'; if (pi->has_add_value) { call_add_value(pi->handler, rb_funcall(oj_bigdecimal_class, oj_new_id, 1, rb_str_new2(start)), key); } *pi->s = c; } else { double d = (double)n + (double)a / (double)div; if (neg) { d = -d; } if (1 < big) { e += big - 1; } if (0 != e) { if (eneg) { e = -e; } d *= pow(10.0, e); } if (pi->has_add_value) { call_add_value(pi->handler, rb_float_new(d), key); } } } } static void read_true(ParseInfo pi, const char *key) { pi->s++; if ('r' != *pi->s || 'u' != *(pi->s + 1) || 'e' != *(pi->s + 2)) { if (pi->has_error) { call_error("invalid format, expected 'true'", pi, __FILE__, __LINE__); } raise_error("invalid format, expected 'true'", pi->str, pi->s); } pi->s += 3; if (pi->has_add_value) { call_add_value(pi->handler, Qtrue, key); } } static void read_false(ParseInfo pi, const char *key) { pi->s++; if ('a' != *pi->s || 'l' != *(pi->s + 1) || 's' != *(pi->s + 2) || 'e' != *(pi->s + 3)) { if (pi->has_error) { call_error("invalid format, expected 'false'", pi, __FILE__, __LINE__); } raise_error("invalid format, expected 'false'", pi->str, pi->s); } pi->s += 4; if (pi->has_add_value) { call_add_value(pi->handler, Qfalse, key); } } static void read_nil(ParseInfo pi, const char *key) { pi->s++; if ('u' != *pi->s || 'l' != *(pi->s + 1) || 'l' != *(pi->s + 2)) { if (pi->has_error) { call_error("invalid format, expected 'null'", pi, __FILE__, __LINE__); } raise_error("invalid format, expected 'null'", pi->str, pi->s); } pi->s += 3; if (pi->has_add_value) { call_add_value(pi->handler, Qnil, key); } } static uint32_t read_hex(ParseInfo pi, char *h) { uint32_t b = 0; int i; /* TBD this can be made faster with a table */ for (i = 0; i < 4; i++, h++) { b = b << 4; if ('0' <= *h && *h <= '9') { b += *h - '0'; } else if ('A' <= *h && *h <= 'F') { b += *h - 'A' + 10; } else if ('a' <= *h && *h <= 'f') { b += *h - 'a' + 10; } else { pi->s = h; if (pi->has_error) { call_error("invalid hex character", pi, __FILE__, __LINE__); } raise_error("invalid hex character", pi->str, pi->s); } } return b; } static char* unicode_to_chars(ParseInfo pi, char *t, uint32_t code) { if (0x0000007F >= code) { *t = (char)code; } else if (0x000007FF >= code) { *t++ = 0xC0 | (code >> 6); *t = 0x80 | (0x3F & code); } else if (0x0000FFFF >= code) { *t++ = 0xE0 | (code >> 12); *t++ = 0x80 | ((code >> 6) & 0x3F); *t = 0x80 | (0x3F & code); } else if (0x001FFFFF >= code) { *t++ = 0xF0 | (code >> 18); *t++ = 0x80 | ((code >> 12) & 0x3F); *t++ = 0x80 | ((code >> 6) & 0x3F); *t = 0x80 | (0x3F & code); } else if (0x03FFFFFF >= code) { *t++ = 0xF8 | (code >> 24); *t++ = 0x80 | ((code >> 18) & 0x3F); *t++ = 0x80 | ((code >> 12) & 0x3F); *t++ = 0x80 | ((code >> 6) & 0x3F); *t = 0x80 | (0x3F & code); } else if (0x7FFFFFFF >= code) { *t++ = 0xFC | (code >> 30); *t++ = 0x80 | ((code >> 24) & 0x3F); *t++ = 0x80 | ((code >> 18) & 0x3F); *t++ = 0x80 | ((code >> 12) & 0x3F); *t++ = 0x80 | ((code >> 6) & 0x3F); *t = 0x80 | (0x3F & code); } else { if (pi->has_error) { call_error("invalid Unicode", pi, __FILE__, __LINE__); } raise_error("invalid Unicode", pi->str, pi->s); } return t; } /* Assume the value starts immediately and goes until the quote character is * reached again. Do not read the character after the terminating quote. */ static char* read_quoted_value(ParseInfo pi) { char *value = 0; char *h = pi->s; /* head */ char *t = h; /* tail */ uint32_t code; h++; /* skip quote character */ t++; value = h; for (; '"' != *h; h++, t++) { if ('\0' == *h) { pi->s = h; raise_error("quoted string not terminated", pi->str, pi->s); } else if ('\\' == *h) { h++; switch (*h) { case 'n': *t = '\n'; break; case 'r': *t = '\r'; break; case 't': *t = '\t'; break; case 'f': *t = '\f'; break; case 'b': *t = '\b'; break; case '"': *t = '"'; break; case '/': *t = '/'; break; case '\\': *t = '\\'; break; case 'u': h++; code = read_hex(pi, h); h += 3; if (0x0000D800 <= code && code <= 0x0000DFFF) { uint32_t c1 = (code - 0x0000D800) & 0x000003FF; uint32_t c2; h++; if ('\\' != *h || 'u' != *(h + 1)) { pi->s = h; if (pi->has_error) { call_error("invalid escaped character", pi, __FILE__, __LINE__); } raise_error("invalid escaped character", pi->str, pi->s); } h += 2; c2 = read_hex(pi, h); h += 3; c2 = (c2 - 0x0000DC00) & 0x000003FF; code = ((c1 << 10) | c2) + 0x00010000; } t = unicode_to_chars(pi, t, code); break; default: pi->s = h; if (pi->has_error) { call_error("invalid escaped character", pi, __FILE__, __LINE__); } raise_error("invalid escaped character", pi->str, pi->s); break; } } else if (t != h) { *t = *h; } } *t = '\0'; /* terminate value */ pi->s = h + 1; return value; } inline static int respond_to(VALUE obj, ID method) { #ifdef JRUBY_RUBY /* There is a bug in JRuby where rb_respond_to() returns true (1) even if * a method is private. */ { VALUE args[1]; *args = ID2SYM(method); return (Qtrue == rb_funcall2(obj, rb_intern("respond_to?"), 1, args)); } #else return rb_respond_to(obj, method); #endif } static void sajkey_parse(VALUE handler, char *json) { volatile VALUE obj = Qnil; struct _ParseInfo pi; if (0 == json) { if (pi.has_error) { call_error("Invalid arg, xml string can not be null", &pi, __FILE__, __LINE__); } raise_error("Invalid arg, xml string can not be null", json, 0); } /* skip UTF-8 BOM if present */ if (0xEF == (uint8_t)*json && 0xBB == (uint8_t)json[1] && 0xBF == (uint8_t)json[2]) { json += 3; } /* initialize parse info */ pi.str = json; pi.s = json; #if IS_WINDOWS pi.stack_min = (void*)((char*)&obj - (512 * 1024)); /* assume a 1M stack and give half to ruby */ #else { struct rlimit lim; if (0 == getrlimit(RLIMIT_STACK, &lim)) { pi.stack_min = (void*)((char*)&obj - (lim.rlim_cur / 4 * 3)); /* let 3/4ths of the stack be used only */ } else { pi.stack_min = 0; /* indicates not to check stack limit */ } } #endif pi.handler = handler; pi.has_hash_start = respond_to(handler, oj_hash_start_id); pi.has_hash_end = respond_to(handler, oj_hash_end_id); pi.has_array_start = respond_to(handler, oj_array_start_id); pi.has_array_end = respond_to(handler, oj_array_end_id); pi.has_add_value = respond_to(handler, oj_add_value_id); pi.has_error = respond_to(handler, oj_error_id); read_next(&pi, 0); next_non_white(&pi); if ('\0' != *pi.s) { if (pi.has_error) { call_error("invalid format, extra characters", &pi, __FILE__, __LINE__); } else { raise_error("invalid format, extra characters", pi.str, pi.s); } } } /* call-seq: sajkey_parse(handler, io) * * Parses an IO stream or file containing an JSON document. Raises an exception * if the JSON is malformed. * @param [Oj::SajKey] handler SajKey (responds to Oj::SajKey methods) like handler * @param [IO|String] io IO Object to read from */ VALUE oj_saj_parse(int argc, VALUE *argv, VALUE self) { char *json = 0; size_t len = 0; VALUE input = argv[1]; if (argc < 2) { rb_raise(rb_eArgError, "Wrong number of arguments to saj_parse.\n"); } if (rb_type(input) == T_STRING) { // the json string gets modified so make a copy of it len = RSTRING_LEN(input) + 1; json = ALLOC_N(char, len); strcpy(json, StringValuePtr(input)); } else { VALUE clas = rb_obj_class(input); volatile VALUE s; if (oj_stringio_class == clas) { s = rb_funcall2(input, oj_string_id, 0, 0); len = RSTRING_LEN(s) + 1; json = ALLOC_N(char, len); strcpy(json, rb_string_value_cstr((VALUE*)&s)); #ifndef JRUBY_RUBY #if !IS_WINDOWS // JRuby gets confused with what is the real fileno. } else if (rb_respond_to(input, oj_fileno_id) && Qnil != (s = rb_funcall(input, oj_fileno_id, 0))) { int fd = FIX2INT(s); ssize_t cnt; len = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); json = ALLOC_N(char, len + 1); if (0 >= (cnt = read(fd, json, len)) || cnt != (ssize_t)len) { rb_raise(rb_eIOError, "failed to read from IO Object."); } json[len] = '\0'; #endif #endif } else if (rb_respond_to(input, oj_read_id)) { s = rb_funcall2(input, oj_read_id, 0, 0); len = RSTRING_LEN(s) + 1; json = ALLOC_N(char, len); strcpy(json, rb_string_value_cstr((VALUE*)&s)); } else { rb_raise(rb_eArgError, "saj_parse() expected a String or IO Object."); } } sajkey_parse(*argv, json); xfree(json); return Qnil; } oj-2.5.3/ext/oj/fast.c0000644000004100000410000012030212263716750014501 0ustar www-datawww-data/* fast.c * Copyright (c) 2012, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #if !IS_WINDOWS #include // for getrlimit() on linux #endif #include #include #include #include #include #include "oj.h" #include "encode.h" // maximum to allocate on the stack, arbitrary limit #define SMALL_XML 65536 #define MAX_STACK 100 //#define BATCH_SIZE (4096 / sizeof(struct _Leaf) - 1) #define BATCH_SIZE 100 typedef struct _Batch { struct _Batch *next; int next_avail; struct _Leaf leaves[BATCH_SIZE]; } *Batch; typedef struct _Doc { Leaf data; Leaf *where; // points to current location Leaf where_path[MAX_STACK]; // points to head of path char *json; unsigned long size; // number of leaves/branches in the doc VALUE self; Batch batches; struct _Batch batch0; } *Doc; typedef struct _ParseInfo { char *str; /* buffer being read from */ char *s; /* current position in buffer */ Doc doc; void *stack_min; } *ParseInfo; static void leaf_init(Leaf leaf, int type); static Leaf leaf_new(Doc doc, int type); static void leaf_append_element(Leaf parent, Leaf element); static VALUE leaf_value(Doc doc, Leaf leaf); static void leaf_fixnum_value(Leaf leaf); static void leaf_float_value(Leaf leaf); static VALUE leaf_array_value(Doc doc, Leaf leaf); static VALUE leaf_hash_value(Doc doc, Leaf leaf); static Leaf read_next(ParseInfo pi); static Leaf read_obj(ParseInfo pi); static Leaf read_array(ParseInfo pi); static Leaf read_str(ParseInfo pi); static Leaf read_num(ParseInfo pi); static Leaf read_true(ParseInfo pi); static Leaf read_false(ParseInfo pi); static Leaf read_nil(ParseInfo pi); static void next_non_white(ParseInfo pi); static char* read_quoted_value(ParseInfo pi); static void skip_comment(ParseInfo pi); static VALUE protect_open_proc(VALUE x); static VALUE parse_json(VALUE clas, char *json, int given, int allocated); static void each_leaf(Doc doc, VALUE self); static int move_step(Doc doc, const char *path, int loc); static Leaf get_doc_leaf(Doc doc, const char *path); static Leaf get_leaf(Leaf *stack, Leaf *lp, const char *path); static void each_value(Doc doc, Leaf leaf); static void doc_init(Doc doc); static void doc_free(Doc doc); static VALUE doc_open(VALUE clas, VALUE str); static VALUE doc_open_file(VALUE clas, VALUE filename); static VALUE doc_where(VALUE self); static VALUE doc_local_key(VALUE self); static VALUE doc_home(VALUE self); static VALUE doc_type(int argc, VALUE *argv, VALUE self); static VALUE doc_fetch(int argc, VALUE *argv, VALUE self); static VALUE doc_each_leaf(int argc, VALUE *argv, VALUE self); static VALUE doc_move(VALUE self, VALUE str); static VALUE doc_each_child(int argc, VALUE *argv, VALUE self); static VALUE doc_each_value(int argc, VALUE *argv, VALUE self); static VALUE doc_dump(int argc, VALUE *argv, VALUE self); static VALUE doc_size(VALUE self); VALUE oj_doc_class = 0; // This is only for CentOS 5.4 with Ruby 1.9.3-p0. #ifdef NEEDS_STPCPY char *stpcpy(char *dest, const char *src) { size_t cnt = strlen(src); strcpy(dest, src); return dest + cnt; } #endif inline static void next_non_white(ParseInfo pi) { for (; 1; pi->s++) { switch(*pi->s) { case ' ': case '\t': case '\f': case '\n': case '\r': break; case '/': skip_comment(pi); break; default: return; } } } inline static void next_white(ParseInfo pi) { for (; 1; pi->s++) { switch(*pi->s) { case ' ': case '\t': case '\f': case '\n': case '\r': case '\0': return; default: break; } } } inline static char* ulong_fill(char *s, size_t num) { char buf[32]; char *b = buf + sizeof(buf) - 1; *b-- = '\0'; for (; 0 < num; num /= 10, b--) { *b = (num % 10) + '0'; } b++; if ('\0' == *b) { b--; *b = '0'; } for (; '\0' != *b; b++, s++) { *s = *b; } return s; } inline static void leaf_init(Leaf leaf, int type) { leaf->next = 0; leaf->type = type; leaf->parent_type = T_NONE; switch (type) { case T_ARRAY: case T_HASH: leaf->elements = 0; leaf->value_type = COL_VAL; break; case T_NIL: leaf->value = Qnil; leaf->value_type = RUBY_VAL; break; case T_TRUE: leaf->value = Qtrue; leaf->value_type = RUBY_VAL; break; case T_FALSE: leaf->value = Qfalse; leaf->value_type = RUBY_VAL; break; case T_FIXNUM: case T_FLOAT: case T_STRING: default: leaf->value_type = STR_VAL; break; } } inline static Leaf leaf_new(Doc doc, int type) { Leaf leaf; if (0 == doc->batches || BATCH_SIZE == doc->batches->next_avail) { Batch b = ALLOC(struct _Batch); b->next = doc->batches; doc->batches = b; b->next_avail = 0; } leaf = &doc->batches->leaves[doc->batches->next_avail]; doc->batches->next_avail++; leaf_init(leaf, type); return leaf; } inline static void leaf_append_element(Leaf parent, Leaf element) { if (0 == parent->elements) { parent->elements = element; element->next = element; } else { element->next = parent->elements->next; parent->elements->next = element; parent->elements = element; } } static VALUE leaf_value(Doc doc, Leaf leaf) { if (RUBY_VAL != leaf->value_type) { switch (leaf->type) { case T_NIL: leaf->value = Qnil; break; case T_TRUE: leaf->value = Qtrue; break; case T_FALSE: leaf->value = Qfalse; break; case T_FIXNUM: leaf_fixnum_value(leaf); break; case T_FLOAT: leaf_float_value(leaf); break; case T_STRING: leaf->value = rb_str_new2(leaf->str); leaf->value = oj_encode(leaf->value); leaf->value_type = RUBY_VAL; break; case T_ARRAY: return leaf_array_value(doc, leaf); break; case T_HASH: return leaf_hash_value(doc, leaf); break; default: rb_raise(rb_const_get_at(Oj, rb_intern("Error")), "Unexpected type %02x.", leaf->type); break; } } return leaf->value; } inline static Doc self_doc(VALUE self) { Doc doc = DATA_PTR(self); if (0 == doc) { rb_raise(rb_eIOError, "Document already closed or not open."); } return doc; } static void skip_comment(ParseInfo pi) { pi->s++; // skip first / if ('*' == *pi->s) { pi->s++; for (; '\0' != *pi->s; pi->s++) { if ('*' == *pi->s && '/' == *(pi->s + 1)) { pi->s++; return; } else if ('\0' == *pi->s) { raise_error("comment not terminated", pi->str, pi->s); } } } else if ('/' == *pi->s) { for (; 1; pi->s++) { switch (*pi->s) { case '\n': case '\r': case '\f': case '\0': return; default: break; } } } else { raise_error("invalid comment", pi->str, pi->s); } } #ifdef RUBINIUS_RUBY #define NUM_MAX 0x07FFFFFF #else #define NUM_MAX (FIXNUM_MAX >> 8) #endif static void leaf_fixnum_value(Leaf leaf) { char *s = leaf->str; int64_t n = 0; int neg = 0; int big = 0; if ('-' == *s) { s++; neg = 1; } else if ('+' == *s) { s++; } for (; '0' <= *s && *s <= '9'; s++) { n = n * 10 + (*s - '0'); if (NUM_MAX <= n) { big = 1; } } if (big) { char c = *s; *s = '\0'; leaf->value = rb_cstr_to_inum(leaf->str, 10, 0); *s = c; } else { if (neg) { n = -n; } leaf->value = LONG2NUM(n); } leaf->value_type = RUBY_VAL; } #ifdef JRUBY_RUBY static void leaf_float_value(Leaf leaf) { char *s = leaf->str; int64_t n = 0; long a = 0; long div = 1; long e = 0; int neg = 0; int eneg = 0; int big = 0; if ('-' == *s) { s++; neg = 1; } else if ('+' == *s) { s++; } for (; '0' <= *s && *s <= '9'; s++) { n = n * 10 + (*s - '0'); if (NUM_MAX <= n) { big = 1; } } if (big) { char c = *s; *s = '\0'; leaf->value = rb_cstr_to_inum(leaf->str, 10, 0); *s = c; } else { double d; if ('.' == *s) { s++; for (; '0' <= *s && *s <= '9'; s++) { a = a * 10 + (*s - '0'); div *= 10; } } if ('e' == *s || 'E' == *s) { s++; if ('-' == *s) { s++; eneg = 1; } else if ('+' == *s) { s++; } for (; '0' <= *s && *s <= '9'; s++) { e = e * 10 + (*s - '0'); } } d = (double)n + (double)a / (double)div; if (neg) { d = -d; } if (0 != e) { if (eneg) { e = -e; } d *= pow(10.0, e); } leaf->value = rb_float_new(d); } leaf->value_type = RUBY_VAL; } #else static void leaf_float_value(Leaf leaf) { leaf->value = rb_float_new(rb_cstr_to_dbl(leaf->str, 1)); leaf->value_type = RUBY_VAL; } #endif static VALUE leaf_array_value(Doc doc, Leaf leaf) { VALUE a = rb_ary_new(); if (0 != leaf->elements) { Leaf first = leaf->elements->next; Leaf e = first; do { rb_ary_push(a, leaf_value(doc, e)); e = e->next; } while (e != first); } return a; } static VALUE leaf_hash_value(Doc doc, Leaf leaf) { VALUE h = rb_hash_new(); if (0 != leaf->elements) { Leaf first = leaf->elements->next; Leaf e = first; VALUE key; do { key = rb_str_new2(e->key); key = oj_encode(key); rb_hash_aset(h, key, leaf_value(doc, e)); e = e->next; } while (e != first); } return h; } static Leaf read_next(ParseInfo pi) { Leaf leaf = 0; if ((void*)&leaf < pi->stack_min) { rb_raise(rb_eSysStackError, "JSON is too deeply nested"); } next_non_white(pi); // skip white space switch (*pi->s) { case '{': leaf = read_obj(pi); break; case '[': leaf = read_array(pi); break; case '"': leaf = read_str(pi); break; case '+': case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': leaf = read_num(pi); break; case 't': leaf = read_true(pi); break; case 'f': leaf = read_false(pi); break; case 'n': leaf = read_nil(pi); break; case '\0': default: break; // returns 0 } pi->doc->size++; return leaf; } static Leaf read_obj(ParseInfo pi) { Leaf h = leaf_new(pi->doc, T_HASH); char *end; const char *key = 0; Leaf val = 0; pi->s++; next_non_white(pi); if ('}' == *pi->s) { pi->s++; return h; } while (1) { next_non_white(pi); key = 0; val = 0; if ('"' != *pi->s || 0 == (key = read_quoted_value(pi))) { raise_error("unexpected character", pi->str, pi->s); } next_non_white(pi); if (':' == *pi->s) { pi->s++; } else { raise_error("invalid format, expected :", pi->str, pi->s); } if (0 == (val = read_next(pi))) { //printf("*** '%s'\n", pi->s); raise_error("unexpected character", pi->str, pi->s); } end = pi->s; val->key = key; val->parent_type = T_HASH; leaf_append_element(h, val); next_non_white(pi); if ('}' == *pi->s) { pi->s++; *end = '\0'; break; } else if (',' == *pi->s) { pi->s++; } else { //printf("*** '%s'\n", pi->s); raise_error("invalid format, expected , or } while in an object", pi->str, pi->s); } *end = '\0'; } return h; } static Leaf read_array(ParseInfo pi) { Leaf a = leaf_new(pi->doc, T_ARRAY); Leaf e; char *end; int cnt = 0; pi->s++; next_non_white(pi); if (']' == *pi->s) { pi->s++; return a; } while (1) { next_non_white(pi); if (0 == (e = read_next(pi))) { raise_error("unexpected character", pi->str, pi->s); } cnt++; e->index = cnt; e->parent_type = T_ARRAY; leaf_append_element(a, e); end = pi->s; next_non_white(pi); if (',' == *pi->s) { pi->s++; } else if (']' == *pi->s) { pi->s++; *end = '\0'; break; } else { raise_error("invalid format, expected , or ] while in an array", pi->str, pi->s); } *end = '\0'; } return a; } static Leaf read_str(ParseInfo pi) { Leaf leaf = leaf_new(pi->doc, T_STRING); leaf->str = read_quoted_value(pi); return leaf; } static Leaf read_num(ParseInfo pi) { char *start = pi->s; int type = T_FIXNUM; Leaf leaf; if ('-' == *pi->s) { pi->s++; } // digits for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) { } if ('.' == *pi->s) { type = T_FLOAT; pi->s++; for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) { } } if ('e' == *pi->s || 'E' == *pi->s) { pi->s++; if ('-' == *pi->s || '+' == *pi->s) { pi->s++; } for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) { } } leaf = leaf_new(pi->doc, type); leaf->str = start; return leaf; } static Leaf read_true(ParseInfo pi) { Leaf leaf = leaf_new(pi->doc, T_TRUE); pi->s++; if ('r' != *pi->s || 'u' != *(pi->s + 1) || 'e' != *(pi->s + 2)) { raise_error("invalid format, expected 'true'", pi->str, pi->s); } pi->s += 3; return leaf; } static Leaf read_false(ParseInfo pi) { Leaf leaf = leaf_new(pi->doc, T_FALSE); pi->s++; if ('a' != *pi->s || 'l' != *(pi->s + 1) || 's' != *(pi->s + 2) || 'e' != *(pi->s + 3)) { raise_error("invalid format, expected 'false'", pi->str, pi->s); } pi->s += 4; return leaf; } static Leaf read_nil(ParseInfo pi) { Leaf leaf = leaf_new(pi->doc, T_NIL); pi->s++; if ('u' != *pi->s || 'l' != *(pi->s + 1) || 'l' != *(pi->s + 2)) { raise_error("invalid format, expected 'nil'", pi->str, pi->s); } pi->s += 3; return leaf; } static char read_hex(ParseInfo pi, char *h) { uint8_t b = 0; if ('0' <= *h && *h <= '9') { b = *h - '0'; } else if ('A' <= *h && *h <= 'F') { b = *h - 'A' + 10; } else if ('a' <= *h && *h <= 'f') { b = *h - 'a' + 10; } else { pi->s = h; raise_error("invalid hex character", pi->str, pi->s); } h++; b = b << 4; if ('0' <= *h && *h <= '9') { b += *h - '0'; } else if ('A' <= *h && *h <= 'F') { b += *h - 'A' + 10; } else if ('a' <= *h && *h <= 'f') { b += *h - 'a' + 10; } else { pi->s = h; raise_error("invalid hex character", pi->str, pi->s); } return (char)b; } /* Assume the value starts immediately and goes until the quote character is * reached again. Do not read the character after the terminating quote. */ static char* read_quoted_value(ParseInfo pi) { char *value = 0; char *h = pi->s; // head char *t = h; // tail h++; // skip quote character t++; value = h; for (; '"' != *h; h++, t++) { if ('\0' == *h) { pi->s = h; raise_error("quoted string not terminated", pi->str, pi->s); } else if ('\\' == *h) { h++; switch (*h) { case 'n': *t = '\n'; break; case 'r': *t = '\r'; break; case 't': *t = '\t'; break; case 'f': *t = '\f'; break; case 'b': *t = '\b'; break; case '"': *t = '"'; break; case '/': *t = '/'; break; case '\\': *t = '\\'; break; case 'u': h++; *t = read_hex(pi, h); h += 2; if ('\0' != *t) { t++; } *t = read_hex(pi, h); h++; break; default: pi->s = h; raise_error("invalid escaped character", pi->str, pi->s); break; } } else if (t != h) { *t = *h; } } *t = '\0'; // terminate value pi->s = h + 1; return value; } // doc support functions inline static void doc_init(Doc doc) { doc->where = doc->where_path; *doc->where = 0; doc->data = 0; doc->self = Qundef; doc->size = 0; doc->json = 0; doc->batches = &doc->batch0; doc->batch0.next = 0; doc->batch0.next_avail = 0; } static void doc_free(Doc doc) { if (0 != doc) { Batch b; while (0 != (b = doc->batches)) { doc->batches = doc->batches->next; if (&doc->batch0 != b) { xfree(b); } } //xfree(f); } } static VALUE protect_open_proc(VALUE x) { ParseInfo pi = (ParseInfo)x; pi->doc->data = read_next(pi); // parse *pi->doc->where = pi->doc->data; pi->doc->where = pi->doc->where_path; if (rb_block_given_p()) { return rb_yield(pi->doc->self); // caller processing } return Qnil; } static void free_doc_cb(void *x) { Doc doc = (Doc)x; if (0 != doc) { xfree(doc->json); doc_free(doc); } } static VALUE parse_json(VALUE clas, char *json, int given, int allocated) { struct _ParseInfo pi; VALUE result = Qnil; Doc doc; int ex = 0; if (given) { doc = ALLOCA_N(struct _Doc, 1); } else { doc = ALLOC_N(struct _Doc, 1); } /* skip UTF-8 BOM if present */ if (0xEF == (uint8_t)*json && 0xBB == (uint8_t)json[1] && 0xBF == (uint8_t)json[2]) { pi.str = json + 3; } else { pi.str = json; } pi.s = pi.str; doc_init(doc); pi.doc = doc; #if IS_WINDOWS pi.stack_min = (void*)((char*)&pi - (512 * 1024)); // assume a 1M stack and give half to ruby #else { struct rlimit lim; if (0 == getrlimit(RLIMIT_STACK, &lim)) { pi.stack_min = (void*)((char*)&lim - (lim.rlim_cur / 4 * 3)); // let 3/4ths of the stack be used only } else { pi.stack_min = 0; // indicates not to check stack limit } } #endif // last arg is free func void* func(void*) doc->self = rb_data_object_alloc(clas, doc, 0, free_doc_cb); rb_gc_register_address(&doc->self); doc->json = json; DATA_PTR(doc->self) = doc; result = rb_protect(protect_open_proc, (VALUE)&pi, &ex); if (given || 0 != ex) { rb_gc_unregister_address(&doc->self); DATA_PTR(doc->self) = 0; doc_free(pi.doc); if (allocated && 0 != ex) { // will jump so caller will not free xfree(json); } } else { result = doc->self; } if (0 != ex) { rb_jump_tag(ex); } return result; } static Leaf get_doc_leaf(Doc doc, const char *path) { Leaf leaf = *doc->where; if (0 != doc->data && 0 != path) { Leaf stack[MAX_STACK]; Leaf *lp; if ('/' == *path) { path++; *stack = doc->data; lp = stack; } else if (doc->where == doc->where_path) { *stack = doc->data; lp = stack; } else { size_t cnt = doc->where - doc->where_path; if (MAX_STACK <= cnt) { rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK); } memcpy(stack, doc->where_path, sizeof(Leaf) * cnt); lp = stack + cnt; } return get_leaf(stack, lp, path); } return leaf; } static Leaf get_leaf(Leaf *stack, Leaf *lp, const char *path) { Leaf leaf = *lp; if (MAX_STACK <= lp - stack) { rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK); } if ('\0' != *path) { if ('.' == *path && '.' == *(path + 1)) { path += 2; if ('/' == *path) { path++; } if (stack < lp) { leaf = get_leaf(stack, lp - 1, path); } else { return 0; } } else if (COL_VAL == leaf->value_type && 0 != leaf->elements) { Leaf first = leaf->elements->next; Leaf e = first; int type = leaf->type; leaf = 0; if (T_ARRAY == type) { int cnt = 0; for (; '0' <= *path && *path <= '9'; path++) { cnt = cnt * 10 + (*path - '0'); } if ('/' == *path) { path++; } do { if (1 >= cnt) { lp++; *lp = e; leaf = get_leaf(stack, lp, path); break; } cnt--; e = e->next; } while (e != first); } else if (T_HASH == type) { const char *key = path; const char *slash = strchr(path, '/'); int klen; if (0 == slash) { klen = (int)strlen(key); path += klen; } else { klen = (int)(slash - key); path += klen + 1; } do { if (0 == strncmp(key, e->key, klen) && '\0' == e->key[klen]) { lp++; *lp = e; leaf = get_leaf(stack, lp, path); break; } e = e->next; } while (e != first); } } } return leaf; } static void each_leaf(Doc doc, VALUE self) { if (COL_VAL == (*doc->where)->value_type) { if (0 != (*doc->where)->elements) { Leaf first = (*doc->where)->elements->next; Leaf e = first; doc->where++; if (MAX_STACK <= doc->where - doc->where_path) { rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK); } do { *doc->where = e; each_leaf(doc, self); e = e->next; } while (e != first); doc->where--; } } else { rb_yield(self); } } static int move_step(Doc doc, const char *path, int loc) { if (MAX_STACK <= doc->where - doc->where_path) { rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK); } if ('\0' == *path) { loc = 0; } else { Leaf leaf; if (0 == doc->where || 0 == (leaf = *doc->where)) { printf("*** Internal error at %s\n", path); return loc; } if ('.' == *path && '.' == *(path + 1)) { Leaf init = *doc->where; path += 2; if (doc->where == doc->where_path) { return loc; } if ('/' == *path) { path++; } *doc->where = 0; doc->where--; loc = move_step(doc, path, loc + 1); if (0 != loc) { *doc->where = init; doc->where++; } } else if (COL_VAL == leaf->value_type && 0 != leaf->elements) { Leaf first = leaf->elements->next; Leaf e = first; if (T_ARRAY == leaf->type) { int cnt = 0; for (; '0' <= *path && *path <= '9'; path++) { cnt = cnt * 10 + (*path - '0'); } if ('/' == *path) { path++; } else if ('\0' != *path) { return loc; } do { if (1 >= cnt) { doc->where++; *doc->where = e; loc = move_step(doc, path, loc + 1); if (0 != loc) { *doc->where = 0; doc->where--; } break; } cnt--; e = e->next; } while (e != first); } else if (T_HASH == leaf->type) { const char *key = path; const char *slash = strchr(path, '/'); int klen; if (0 == slash) { klen = (int)strlen(key); path += klen; } else { klen = (int)(slash - key); path += klen + 1; } do { if (0 == strncmp(key, e->key, klen) && '\0' == e->key[klen]) { doc->where++; *doc->where = e; loc = move_step(doc, path, loc + 1); if (0 != loc) { *doc->where = 0; doc->where--; } break; } e = e->next; } while (e != first); } } } return loc; } static void each_value(Doc doc, Leaf leaf) { if (COL_VAL == leaf->value_type) { if (0 != leaf->elements) { Leaf first = leaf->elements->next; Leaf e = first; do { each_value(doc, e); e = e->next; } while (e != first); } } else { rb_yield(leaf_value(doc, leaf)); } } // doc functions /* call-seq: open(json) { |doc| ... } => Object * * Parses a JSON document String and then yields to the provided block if one * is given with an instance of the Oj::Doc as the single yield parameter. If * a block is not given then an Oj::Doc instance is returned and must be * closed with a call to the #close() method when no longer needed. * * @param [String] json JSON document string * @yieldparam [Oj::Doc] doc parsed JSON document * @yieldreturn [Object] returns the result of the yield as the result of the method call * @example * Oj::Doc.open('[1,2,3]') { |doc| doc.size() } #=> 4 * # or as an alternative * doc = Oj::Doc.open('[1,2,3]') * doc.size() #=> 4 * doc.close() */ static VALUE doc_open(VALUE clas, VALUE str) { char *json; size_t len; VALUE obj; int given = rb_block_given_p(); int allocate; Check_Type(str, T_STRING); len = RSTRING_LEN(str) + 1; allocate = (SMALL_XML < len || !given); if (allocate) { json = ALLOC_N(char, len); } else { json = ALLOCA_N(char, len); } memcpy(json, StringValuePtr(str), len); obj = parse_json(clas, json, given, allocate); if (given && allocate) { xfree(json); } return obj; } /* call-seq: open_file(filename) { |doc| ... } => Object * * Parses a JSON document from a file and then yields to the provided block if * one is given with an instance of the Oj::Doc as the single yield * parameter. If a block is not given then an Oj::Doc instance is returned and * must be closed with a call to the #close() method when no longer needed. * * @param [String] filename name of file that contains a JSON document * @yieldparam [Oj::Doc] doc parsed JSON document * @yieldreturn [Object] returns the result of the yield as the result of the method call * @example * File.open('array.json', 'w') { |f| f.write('[1,2,3]') } * Oj::Doc.open_file(filename) { |doc| doc.size() } #=> 4 * # or as an alternative * doc = Oj::Doc.open_file(filename) * doc.size() #=> 4 * doc.close() */ static VALUE doc_open_file(VALUE clas, VALUE filename) { char *path; char *json; FILE *f; size_t len; VALUE obj; int given = rb_block_given_p(); int allocate; Check_Type(filename, T_STRING); path = StringValuePtr(filename); if (0 == (f = fopen(path, "r"))) { rb_raise(rb_eIOError, "%s", strerror(errno)); } fseek(f, 0, SEEK_END); len = ftell(f); allocate = (SMALL_XML < len || !given); if (allocate) { json = ALLOC_N(char, len + 1); } else { json = ALLOCA_N(char, len + 1); } fseek(f, 0, SEEK_SET); if (len != fread(json, 1, len, f)) { fclose(f); rb_raise(rb_const_get_at(Oj, rb_intern("LoadError")), "Failed to read %lu bytes from %s.", (unsigned long)len, path); } fclose(f); json[len] = '\0'; obj = parse_json(clas, json, given, allocate); if (given && allocate) { xfree(json); } return obj; } /* Document-method: parse * @see Oj::Doc.open */ /* call-seq: where?() => String * * Returns a String that describes the absolute path to the current location * in the JSON document. */ static VALUE doc_where(VALUE self) { Doc doc = self_doc(self); if (0 == *doc->where_path || doc->where == doc->where_path) { return oj_slash_string; } else { Leaf *lp; Leaf leaf; size_t size = 3; // leading / and terminating \0 char *path; char *p; for (lp = doc->where_path; lp <= doc->where; lp++) { leaf = *lp; if (T_HASH == leaf->parent_type) { size += strlen((*lp)->key) + 1; } else if (T_ARRAY == leaf->parent_type) { size += ((*lp)->index < 100) ? 3 : 11; } } path = ALLOCA_N(char, size); p = path; for (lp = doc->where_path; lp <= doc->where; lp++) { leaf = *lp; if (T_HASH == leaf->parent_type) { p = stpcpy(p, (*lp)->key); } else if (T_ARRAY == leaf->parent_type) { p = ulong_fill(p, (*lp)->index); } *p++ = '/'; } *--p = '\0'; return rb_str_new(path, p - path); } } /* call-seq: local_key() => String, Fixnum, nil * * Returns the final key to the current location. * @example * Oj::Doc.open('[1,2,3]') { |doc| doc.move('/2'); doc.local_key() } #=> 2 * Oj::Doc.open('{"one":3}') { |doc| doc.move('/one'); doc.local_key() } #=> "one" * Oj::Doc.open('[1,2,3]') { |doc| doc.local_key() } #=> nil */ static VALUE doc_local_key(VALUE self) { Doc doc = self_doc(self); Leaf leaf = *doc->where; VALUE key = Qnil; if (T_HASH == leaf->parent_type) { key = rb_str_new2(leaf->key); key = oj_encode(key); } else if (T_ARRAY == leaf->parent_type) { key = LONG2NUM(leaf->index); } return key; } /* call-seq: home() => nil * * Moves the document marker or location to the hoot or home position. The * same operation can be performed with a Oj::Doc.move('/'). * @example * Oj::Doc.open('[1,2,3]') { |doc| doc.move('/2'); doc.home(); doc.where? } #=> '/' */ static VALUE doc_home(VALUE self) { Doc doc = self_doc(self); *doc->where_path = doc->data; doc->where = doc->where_path; return oj_slash_string; } /* call-seq: type(path=nil) => Class * * Returns the Class of the data value at the location identified by the path * or the current location if the path is nil or not provided. This method * does not create the Ruby Object at the location specified so the overhead * is low. * @param [String] path path to the location to get the type of if provided * @example * Oj::Doc.open('[1,2]') { |doc| doc.type() } #=> Array * Oj::Doc.open('[1,2]') { |doc| doc.type('/1') } #=> Fixnum */ static VALUE doc_type(int argc, VALUE *argv, VALUE self) { Doc doc = self_doc(self); Leaf leaf; const char *path = 0; VALUE type = Qnil; if (1 <= argc) { Check_Type(*argv, T_STRING); path = StringValuePtr(*argv); } if (0 != (leaf = get_doc_leaf(doc, path))) { switch (leaf->type) { case T_NIL: type = rb_cNilClass; break; case T_TRUE: type = rb_cTrueClass; break; case T_FALSE: type = rb_cFalseClass; break; case T_STRING: type = rb_cString; break; case T_FIXNUM: type = rb_cFixnum; break; case T_FLOAT: type = rb_cFloat; break; case T_ARRAY: type = rb_cArray; break; case T_HASH: type = rb_cHash; break; default: break; } } return type; } /* call-seq: fetch(path=nil) => nil, true, false, Fixnum, Float, String, Array, Hash * * Returns the value at the location identified by the path or the current * location if the path is nil or not provided. This method will create and * return an Array or Hash if that is the type of Object at the location * specified. This is more expensive than navigating to the leaves of the JSON * document. * @param [String] path path to the location to get the type of if provided * @example * Oj::Doc.open('[1,2]') { |doc| doc.fetch() } #=> [1, 2] * Oj::Doc.open('[1,2]') { |doc| doc.fetch('/1') } #=> 1 */ static VALUE doc_fetch(int argc, VALUE *argv, VALUE self) { Doc doc; Leaf leaf; VALUE val = Qnil; const char *path = 0; doc = self_doc(self); if (1 <= argc) { Check_Type(*argv, T_STRING); path = StringValuePtr(*argv); if (2 == argc) { val = argv[1]; } } if (0 != (leaf = get_doc_leaf(doc, path))) { val = leaf_value(doc, leaf); } return val; } /* call-seq: each_leaf(path=nil) => nil * * Yields to the provided block for each leaf node with the identified * location of the JSON document as the root. The parameter passed to the * block on yield is the Doc instance after moving to the child location. * @param [String] path if provided it identified the top of the branch to process the leaves of * @yieldparam [Doc] Doc at the child location * @example * Oj::Doc.open('[3,[2,1]]') { |doc| * result = {} * doc.each_leaf() { |d| result[d.where?] = d.fetch() } * result * } * #=> ["/1" => 3, "/2/1" => 2, "/2/2" => 1] */ static VALUE doc_each_leaf(int argc, VALUE *argv, VALUE self) { if (rb_block_given_p()) { Leaf save_path[MAX_STACK]; Doc doc = self_doc(self); const char *path = 0; size_t wlen; wlen = doc->where - doc->where_path; if (0 < wlen) { memcpy(save_path, doc->where_path, sizeof(Leaf) * wlen); } if (1 <= argc) { Check_Type(*argv, T_STRING); path = StringValuePtr(*argv); if ('/' == *path) { doc->where = doc->where_path; path++; } if (0 != move_step(doc, path, 1)) { if (0 < wlen) { memcpy(doc->where_path, save_path, sizeof(Leaf) * wlen); } return Qnil; } } each_leaf(doc, self); if (0 < wlen) { memcpy(doc->where_path, save_path, sizeof(Leaf) * wlen); } } return Qnil; } /* call-seq: move(path) => nil * * Moves the document marker to the path specified. The path can an absolute * path or a relative path. * @param [String] path path to the location to move to * @example * Oj::Doc.open('{"one":[1,2]') { |doc| doc.move('/one/2'); doc.where? } #=> "/one/2" */ static VALUE doc_move(VALUE self, VALUE str) { Doc doc = self_doc(self); const char *path; int loc; Check_Type(str, T_STRING); path = StringValuePtr(str); if ('/' == *path) { doc->where = doc->where_path; path++; } if (0 != (loc = move_step(doc, path, 1))) { rb_raise(rb_eArgError, "Failed to locate element %d of the path %s.", loc, path); } return Qnil; } /* call-seq: each_child(path=nil) { |doc| ... } => nil * * Yields to the provided block for each immediate child node with the * identified location of the JSON document as the root. The parameter passed * to the block on yield is the Doc instance after moving to the child * location. * @param [String] path if provided it identified the top of the branch to process the chilren of * @yieldparam [Doc] Doc at the child location * @example * Oj::Doc.open('[3,[2,1]]') { |doc| * result = [] * doc.each_value('/2') { |doc| result << doc.where? } * result * } * #=> ["/2/1", "/2/2"] */ static VALUE doc_each_child(int argc, VALUE *argv, VALUE self) { if (rb_block_given_p()) { Leaf save_path[MAX_STACK]; Doc doc = self_doc(self); const char *path = 0; size_t wlen; wlen = doc->where - doc->where_path; if (0 < wlen) { memcpy(save_path, doc->where_path, sizeof(Leaf) * wlen); } if (1 <= argc) { Check_Type(*argv, T_STRING); path = StringValuePtr(*argv); if ('/' == *path) { doc->where = doc->where_path; path++; } if (0 != move_step(doc, path, 1)) { if (0 < wlen) { memcpy(doc->where_path, save_path, sizeof(Leaf) * wlen); } return Qnil; } } if (COL_VAL == (*doc->where)->value_type && 0 != (*doc->where)->elements) { Leaf first = (*doc->where)->elements->next; Leaf e = first; doc->where++; do { *doc->where = e; rb_yield(self); e = e->next; } while (e != first); } if (0 < wlen) { memcpy(doc->where_path, save_path, sizeof(Leaf) * wlen); } } return Qnil; } /* call-seq: each_value(path=nil) { |val| ... } => nil * * Yields to the provided block for each leaf value in the identified location * of the JSON document. The parameter passed to the block on yield is the * value of the leaf. Only those leaves below the element specified by the * path parameter are processed. * @param [String] path if provided it identified the top of the branch to process the leaf values of * @yieldparam [Object] val each leaf value * @example * Oj::Doc.open('[3,[2,1]]') { |doc| * result = [] * doc.each_value() { |v| result << v } * result * } * #=> [3, 2, 1] * * Oj::Doc.open('[3,[2,1]]') { |doc| * result = [] * doc.each_value('/2') { |v| result << v } * result * } * #=> [2, 1] */ static VALUE doc_each_value(int argc, VALUE *argv, VALUE self) { if (rb_block_given_p()) { Doc doc = self_doc(self); const char *path = 0; Leaf leaf; if (1 <= argc) { Check_Type(*argv, T_STRING); path = StringValuePtr(*argv); } if (0 != (leaf = get_doc_leaf(doc, path))) { each_value(doc, leaf); } } return Qnil; } /* call-seq: dump(path=nil) => String * * Dumps the document or nodes to a new JSON document. It uses the default * options for generating the JSON. * @param [String] path if provided it identified the top of the branch to dump to JSON * @param [String] filename if provided it is the filename to write the output to * @example * Oj::Doc.open('[3,[2,1]]') { |doc| * doc.dump('/2') * } * #=> "[2,1]" */ static VALUE doc_dump(int argc, VALUE *argv, VALUE self) { Doc doc = self_doc(self); Leaf leaf; const char *path = 0; const char *filename = 0; if (1 <= argc) { if (Qnil != *argv) { Check_Type(*argv, T_STRING); path = StringValuePtr(*argv); } if (2 <= argc) { Check_Type(argv[1], T_STRING); filename = StringValuePtr(argv[1]); } } if (0 != (leaf = get_doc_leaf(doc, path))) { VALUE rjson; if (0 == filename) { char buf[4096]; struct _Out out; out.buf = buf; out.end = buf + sizeof(buf) - 10; out.allocated = 0; oj_dump_leaf_to_json(leaf, &oj_default_options, &out); rjson = rb_str_new2(out.buf); if (out.allocated) { xfree(out.buf); } } else { oj_write_leaf_to_file(leaf, filename, &oj_default_options); rjson = Qnil; } return rjson; } return Qnil; } /* call-seq: size() => Fixnum * * Returns the number of nodes in the JSON document where a node is any one of * the basic JSON components. * @return Returns the size of the JSON document. * @example * Oj::Doc.open('[1,2,3]') { |doc| doc.size() } #=> 4 */ static VALUE doc_size(VALUE self) { return ULONG2NUM(((Doc)DATA_PTR(self))->size); } /* call-seq: close() => nil * * Closes an open document. No further calls to the document will be valid * after closing. * @example * doc = Oj::Doc.open('[1,2,3]') * doc.size() #=> 4 * doc.close() */ static VALUE doc_close(VALUE self) { Doc doc = self_doc(self); rb_gc_unregister_address(&doc->self); DATA_PTR(doc->self) = 0; if (0 != doc) { xfree(doc->json); doc_free(doc); } return Qnil; } #if 0 // hack to keep the doc generator happy Oj = rb_define_module("Oj"); #endif /* Document-class: Oj::Doc * * The Doc class is used to parse and navigate a JSON document. The model it * employs is that of a document that while open can be navigated and values * extracted. Once the document is closed the document can not longer be * accessed. This allows the parsing and data extraction to be extremely fast * compared to other JSON parses. * * An Oj::Doc class is not created directly but the _open()_ class method is * used to open a document and the yield parameter to the block of the #open() * call is the Doc instance. The Doc instance can be moved across, up, and * down the JSON document. At each element the data associated with the * element can be extracted. It is also possible to just provide a path to the * data to be extracted and retrieve the data in that manner. * * For many of the methods a path is used to describe the location of an * element. Paths follow a subset of the XPath syntax. The slash ('/') * character is the separator. Each step in the path identifies the next * branch to take through the document. A JSON object will expect a key string * while an array will expect a positive index. A .. step indicates a move up * the JSON document. * * @example * json = %{[ * { * "one" : 1, * "two" : 2 * }, * { * "three" : 3, * "four" : 4 * } * ]} * # move and get value * Oj::Doc.open(json) do |doc| * doc.move('/1/two') * # doc location is now at the 'two' element of the hash that is the first element of the array. * doc.fetch() * end * #=> 2 * * # Now try again using a path to Oj::Doc.fetch() directly and not using a block. * doc = Oj::Doc.open(json) * doc.fetch('/2/three') #=> 3 * doc.close() */ void oj_init_doc() { oj_doc_class = rb_define_class_under(Oj, "Doc", rb_cObject); rb_define_singleton_method(oj_doc_class, "open", doc_open, 1); rb_define_singleton_method(oj_doc_class, "open_file", doc_open_file, 1); rb_define_singleton_method(oj_doc_class, "parse", doc_open, 1); rb_define_method(oj_doc_class, "where?", doc_where, 0); rb_define_method(oj_doc_class, "local_key", doc_local_key, 0); rb_define_method(oj_doc_class, "home", doc_home, 0); rb_define_method(oj_doc_class, "type", doc_type, -1); rb_define_method(oj_doc_class, "fetch", doc_fetch, -1); rb_define_method(oj_doc_class, "each_leaf", doc_each_leaf, -1); rb_define_method(oj_doc_class, "move", doc_move, 1); rb_define_method(oj_doc_class, "each_child", doc_each_child, -1); rb_define_method(oj_doc_class, "each_value", doc_each_value, -1); rb_define_method(oj_doc_class, "dump", doc_dump, -1); rb_define_method(oj_doc_class, "size", doc_size, 0); rb_define_method(oj_doc_class, "close", doc_close, 0); } oj-2.5.3/ext/oj/err.h0000644000004100000410000000473112263716750014350 0ustar www-datawww-data/* err.h * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __OJ_ERR_H__ #define __OJ_ERR_H__ #include "ruby.h" #define set_error(err, eclas, msg, json, current) _oj_err_set_with_location(err, eclas, msg, json, current, __FILE__, __LINE__) typedef struct _Err { VALUE clas; char msg[128]; } *Err; extern VALUE oj_parse_error_class; extern void oj_err_set(Err e, VALUE clas, const char *format, ...); extern void _oj_err_set_with_location(Err err, VALUE eclas, const char *msg, const char *json, const char *current, const char* file, int line); extern void oj_err_raise(Err e); // TBD remove #define raise_error(msg, json, current) _oj_raise_error(msg, json, current, __FILE__, __LINE__) extern void _oj_raise_error(const char *msg, const char *json, const char *current, const char* file, int line); inline static void err_init(Err e) { e->clas = Qnil; *e->msg = '\0'; } inline static int err_has(Err e) { return (Qnil != e->clas); } #endif /* __OJ_ERR_H__ */ oj-2.5.3/ext/oj/hash.c0000644000004100000410000001016512263716750014474 0ustar www-datawww-data/* hash.c * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "hash.h" #include #define HASH_MASK 0x000003FF #define HASH_SLOT_CNT 1024 typedef struct _KeyVal { struct _KeyVal *next; const char *key; size_t len; VALUE val; } *KeyVal; struct _Hash { struct _KeyVal slots[HASH_SLOT_CNT]; }; struct _Hash class_hash; struct _Hash intern_hash; // almost the Murmur hash algorithm #define M 0x5bd1e995 #define C1 0xCC9E2D51 #define C2 0x1B873593 #define N 0xE6546B64 static uint32_t hash_calc(const uint8_t *key, size_t len) { const uint8_t *end = key + len; const uint8_t *endless = key + (len / 4 * 4); uint32_t h = (uint32_t)len; uint32_t k; while (key < endless) { k = (uint32_t)*key++; k |= (uint32_t)*key++ << 8; k |= (uint32_t)*key++ << 16; k |= (uint32_t)*key++ << 24; k *= M; k ^= k >> 24; h *= M; h ^= k * M; } if (1 < end - key) { uint16_t k16 = (uint16_t)*key++; k16 |= (uint16_t)*key++ << 8; h ^= k16 << 8; } if (key < end) { h ^= *key; } h *= M; h ^= h >> 13; h *= M; h ^= h >> 15; return h; } void oj_hash_init() { memset(class_hash.slots, 0, sizeof(class_hash.slots)); memset(intern_hash.slots, 0, sizeof(intern_hash.slots)); } // if slotp is 0 then just lookup static VALUE hash_get(Hash hash, const char *key, size_t len, VALUE **slotp, VALUE def_value) { uint32_t h = hash_calc((const uint8_t*)key, len) & HASH_MASK; KeyVal bucket = hash->slots + h; if (0 != bucket->key) { KeyVal b; for (b = bucket; 0 != b; b = b->next) { if (len == b->len && 0 == strncmp(b->key, key, len)) { *slotp = &b->val; return b->val; } bucket = b; } } if (0 != slotp) { if (0 != bucket->key) { KeyVal b = ALLOC(struct _KeyVal); b->next = 0; bucket->next = b; bucket = b; } bucket->key = oj_strndup(key, len); bucket->len = len; bucket->val = def_value; *slotp = &bucket->val; } return def_value; } void oj_hash_print() { int i; KeyVal b; for (i = 0; i < HASH_SLOT_CNT; i++) { printf("%4d:", i); for (b = class_hash.slots + i; 0 != b && 0 != b->key; b = b->next) { printf(" %s", b->key); } printf("\n"); } } VALUE oj_class_hash_get(const char *key, size_t len, VALUE **slotp) { return hash_get(&class_hash, key, len, slotp, Qnil); } ID oj_attr_hash_get(const char *key, size_t len, ID **slotp) { return (ID)hash_get(&intern_hash, key, len, (VALUE**)slotp, 0); } char* oj_strndup(const char *s, size_t len) { char *d = ALLOC_N(char, len + 1); memcpy(d, s, len); d[len] = '\0'; return d; } oj-2.5.3/ext/oj/err.c0000644000004100000410000000533112263716750014340 0ustar www-datawww-data/* err.c * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "err.h" void oj_err_set(Err e, VALUE clas, const char *format, ...) { va_list ap; va_start(ap, format); e->clas = clas; vsnprintf(e->msg, sizeof(e->msg) - 1, format, ap); va_end(ap); } void oj_err_raise(Err e) { rb_raise(e->clas, "%s", e->msg); } void _oj_err_set_with_location(Err err, VALUE eclas, const char *msg, const char *json, const char *current, const char* file, int line) { int n = 1; int col = 1; for (; json < current && '\n' != *current; current--) { col++; } for (; json < current; current--) { if ('\n' == *current) { n++; } } oj_err_set(err, eclas, "%s at line %d, column %d [%s:%d]", msg, n, col, file, line); } void _oj_raise_error(const char *msg, const char *json, const char *current, const char* file, int line) { struct _Err err; int n = 1; int col = 1; for (; json < current && '\n' != *current; current--) { col++; } for (; json < current; current--) { if ('\n' == *current) { n++; } } oj_err_set(&err, oj_parse_error_class, "%s at line %d, column %d [%s:%d]", msg, n, col, file, line); rb_raise(err.clas, "%s", err.msg); } oj-2.5.3/ext/oj/cache8.c0000644000004100000410000000416512263716750014707 0ustar www-datawww-data #include #include #include #include #include #include "ruby.h" #include "cache8.h" #define BITS 4 #define MASK 0x000000000000000FULL #define SLOT_CNT 16 #define DEPTH 16 typedef union { struct _Cache8 *child; slot_t value; } Bucket; struct _Cache8 { Bucket buckets[SLOT_CNT]; }; static void cache8_delete(Cache8 cache, int depth); static void slot_print(Cache8 cache, sid_t key, unsigned int depth); void oj_cache8_new(Cache8 *cache) { Bucket *b; int i; *cache = ALLOC(struct _Cache8); for (i = SLOT_CNT, b = (*cache)->buckets; 0 < i; i--, b++) { b->value = 0; } } void oj_cache8_delete(Cache8 cache) { cache8_delete(cache, 0); } static void cache8_delete(Cache8 cache, int depth) { Bucket *b; unsigned int i; for (i = 0, b = cache->buckets; i < SLOT_CNT; i++, b++) { if (0 != b->child) { if (DEPTH - 1 != depth) { cache8_delete(b->child, depth + 1); } } } xfree(cache); } slot_t oj_cache8_get(Cache8 cache, sid_t key, slot_t **slot) { Bucket *b; int i; sid_t k8 = (sid_t)key; sid_t k; for (i = 64 - BITS; 0 < i; i -= BITS) { k = (k8 >> i) & MASK; b = cache->buckets + k; if (0 == b->child) { oj_cache8_new(&b->child); } cache = b->child; } *slot = &(cache->buckets + (k8 & MASK))->value; return **slot; } void oj_cache8_print(Cache8 cache) { /*printf("-------------------------------------------\n"); */ slot_print(cache, 0, 0); } static void slot_print(Cache8 c, sid_t key, unsigned int depth) { Bucket *b; unsigned int i; sid_t k8 = (sid_t)key; sid_t k; for (i = 0, b = c->buckets; i < SLOT_CNT; i++, b++) { if (0 != b->child) { k = (k8 << BITS) | i; /*printf("*** key: 0x%016llx depth: %u i: %u\n", k, depth, i); */ if (DEPTH - 1 == depth) { #if IS_WINDOWS printf("0x%016lx: %4lu\n", (long unsigned int)k, (long unsigned int)b->value); #else printf("0x%016llx: %4llu\n", (long long unsigned int)k, (long long unsigned int)b->value); #endif } else { slot_print(b->child, k, depth + 1); } } } } oj-2.5.3/ext/oj/val_stack.h0000644000004100000410000001116412263716750015525 0ustar www-datawww-data/* val_stack.h * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __OJ_VAL_STACK_H__ #define __OJ_VAL_STACK_H__ #include "ruby.h" #include "odd.h" #include #if USE_PTHREAD_MUTEX #include #endif #define STACK_INC 64 typedef enum { NEXT_NONE = 0, NEXT_ARRAY_NEW = 'a', NEXT_ARRAY_ELEMENT = 'e', NEXT_ARRAY_COMMA = ',', NEXT_HASH_NEW = 'h', NEXT_HASH_KEY = 'k', NEXT_HASH_COLON = ':', NEXT_HASH_VALUE = 'v', NEXT_HASH_COMMA = 'n', } ValNext; typedef struct _Val { VALUE val; const char *key; union { const char *classname; OddArgs odd_args; }; uint16_t klen; uint16_t clen; char next; // ValNext char k1; // first original character in the key } *Val; typedef struct _ValStack { struct _Val base[STACK_INC]; Val head; // current stack Val end; // stack end Val tail; // pointer to one past last element name on stack #if USE_PTHREAD_MUTEX pthread_mutex_t mutex; #elif USE_RB_MUTEX VALUE mutex; #endif } *ValStack; extern VALUE oj_stack_init(ValStack stack); inline static int stack_empty(ValStack stack) { return (stack->head == stack->tail); } inline static void stack_cleanup(ValStack stack) { if (stack->base != stack->head) { xfree(stack->head); } } inline static void stack_push(ValStack stack, VALUE val, ValNext next) { if (stack->end <= stack->tail) { size_t len = stack->end - stack->head; size_t toff = stack->tail - stack->head; Val head = stack->head; // A realloc can trigger a GC so make sure it happens outside the lock // but lock before changing pointers. if (stack->base == stack->head) { head = ALLOC_N(struct _Val, len + STACK_INC); memcpy(head, stack->base, sizeof(struct _Val) * len); } else { REALLOC_N(head, struct _Val, len + STACK_INC); } #if USE_PTHREAD_MUTEX pthread_mutex_lock(&stack->mutex); #elif USE_RB_MUTEX rb_mutex_lock(stack->mutex); #endif stack->head = head; stack->tail = stack->head + toff; stack->end = stack->head + len + STACK_INC; #if USE_PTHREAD_MUTEX pthread_mutex_unlock(&stack->mutex); #elif USE_RB_MUTEX rb_mutex_unlock(stack->mutex); #endif } stack->tail->val = val; stack->tail->next = next; stack->tail->classname = 0; stack->tail->key = 0; stack->tail->clen = 0; stack->tail->klen = 0; stack->tail++; } inline static size_t stack_size(ValStack stack) { return stack->tail - stack->head; } inline static Val stack_peek(ValStack stack) { if (stack->head < stack->tail) { return stack->tail - 1; } return 0; } inline static Val stack_peek_up(ValStack stack) { if (stack->head < stack->tail - 1) { return stack->tail - 2; } return 0; } inline static Val stack_prev(ValStack stack) { return stack->tail; } inline static VALUE stack_head_val(ValStack stack) { if (Qundef != stack->head->val) { return stack->head->val; } return Qnil; } inline static Val stack_pop(ValStack stack) { if (stack->head < stack->tail) { stack->tail--; return stack->tail; } return 0; } extern const char* oj_stack_next_string(ValNext n); #endif /* __OJ_VAL_STACK_H__ */ oj-2.5.3/ext/oj/hash_test.c0000644000004100000410000004005412263716750015533 0ustar www-datawww-data/* hash_test.c * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // if windows, comment out the whole file. It's only a performance test. #ifndef _WIN32 #include #include #include "hash.h" #include /* Define printf formats for standard types, like PRIu64 for uint64_t. */ #define __STDC_FORMAT_MACROS #include typedef struct _StrLen { const char *str; size_t len; } *StrLen; static struct _StrLen data[] = { { "Gem::Version", 12 }, { "TracePoint", 10 }, { "Complex::compatible", 19 }, { "Complex", 7 }, { "Rational::compatible", 20 }, { "Rational", 8 }, { "FiberError", 10 }, { "Fiber", 5 }, { "ThreadError", 11 }, { "Mutex", 5 }, { "ThreadGroup", 11 }, { "RubyVM::InstructionSequence", 27 }, { "Thread::Backtrace::Location", 27 }, { "Thread::Backtrace", 17 }, { "Thread", 6 }, { "RubyVM::Env", 11 }, { "RubyVM", 6 }, { "Enumerator::Yielder", 19 }, { "Enumerator::Generator", 21 }, { "StopIteration", 13 }, { "Enumerator::Lazy", 16 }, { "Enumerator", 10 }, { "ObjectSpace::WeakMap", 20 }, { "Math::DomainError", 17 }, { "Binding", 7 }, { "UnboundMethod", 13 }, { "Method", 6 }, { "SystemStackError", 16 }, { "LocalJumpError", 14 }, { "Proc", 4 }, { "Struct::Tms", 11 }, { "Process::Status", 15 }, { "Random", 6 }, { "Time", 4 }, { "Dir", 3 }, { "File::Stat", 10 }, { "File", 4 }, { "ARGF.class", 10 }, { "IO", 2 }, { "EOFError", 8 }, { "IOError", 7 }, { "Range", 5 }, { "Encoding::Converter", 19 }, { "Encoding::ConverterNotFoundError", 32 }, { "Encoding::InvalidByteSequenceError", 34 }, { "Encoding::UndefinedConversionError", 34 }, { "MatchData", 9 }, { "Regexp", 6 }, { "RegexpError", 11 }, { "Struct", 6 }, { "Hash", 4 }, { "Array", 5 }, { "Errno::ERPCMISMATCH", 19 }, { "Errno::EPROGUNAVAIL", 19 }, { "Errno::EPROGMISMATCH", 20 }, { "Errno::EPROCUNAVAIL", 19 }, { "Errno::EPROCLIM", 15 }, { "Errno::ENOTSUP", 14 }, { "Errno::ENOATTR", 14 }, { "Errno::ENEEDAUTH", 16 }, { "Errno::EFTYPE", 13 }, { "Errno::EBADRPC", 14 }, { "Errno::EAUTH", 12 }, { "Errno::EOWNERDEAD", 17 }, { "Errno::ENOTRECOVERABLE", 22 }, { "Errno::ECANCELED", 16 }, { "Errno::EDQUOT", 13 }, { "Errno::ESTALE", 13 }, { "Errno::EINPROGRESS", 18 }, { "Errno::EALREADY", 15 }, { "Errno::EHOSTUNREACH", 19 }, { "Errno::EHOSTDOWN", 16 }, { "Errno::ECONNREFUSED", 19 }, { "Errno::ETIMEDOUT", 16 }, { "Errno::ETOOMANYREFS", 19 }, { "Errno::ESHUTDOWN", 16 }, { "Errno::ENOTCONN", 15 }, { "Errno::EISCONN", 14 }, { "Errno::ENOBUFS", 14 }, { "Errno::ECONNRESET", 17 }, { "Errno::ECONNABORTED", 19 }, { "Errno::ENETRESET", 16 }, { "Errno::ENETUNREACH", 18 }, { "Errno::ENETDOWN", 15 }, { "Errno::EADDRNOTAVAIL", 20 }, { "Errno::EADDRINUSE", 17 }, { "Errno::EAFNOSUPPORT", 19 }, { "Errno::EPFNOSUPPORT", 19 }, { "Errno::EOPNOTSUPP", 17 }, { "Errno::ESOCKTNOSUPPORT", 22 }, { "Errno::EPROTONOSUPPORT", 22 }, { "Errno::ENOPROTOOPT", 18 }, { "Errno::EPROTOTYPE", 17 }, { "Errno::EMSGSIZE", 15 }, { "Errno::EDESTADDRREQ", 19 }, { "Errno::ENOTSOCK", 15 }, { "Errno::EUSERS", 13 }, { "Errno::EILSEQ", 13 }, { "Errno::EOVERFLOW", 16 }, { "Errno::EBADMSG", 14 }, { "Errno::EMULTIHOP", 16 }, { "Errno::EPROTO", 13 }, { "Errno::ENOLINK", 14 }, { "Errno::EREMOTE", 14 }, { "Errno::ENOSR", 12 }, { "Errno::ETIME", 12 }, { "Errno::ENODATA", 14 }, { "Errno::ENOSTR", 13 }, { "Errno::EIDRM", 12 }, { "Errno::ENOMSG", 13 }, { "Errno::ELOOP", 12 }, { "Errno::ENOTEMPTY", 16 }, { "Errno::ENOSYS", 13 }, { "Errno::ENOLCK", 13 }, { "Errno::ENAMETOOLONG", 19 }, { "Errno::EDEADLK", 14 }, { "Errno::ERANGE", 13 }, { "Errno::EDOM", 11 }, { "Errno::EPIPE", 12 }, { "Errno::EMLINK", 13 }, { "Errno::EROFS", 12 }, { "Errno::ESPIPE", 13 }, { "Errno::ENOSPC", 13 }, { "Errno::EFBIG", 12 }, { "Errno::ETXTBSY", 14 }, { "Errno::ENOTTY", 13 }, { "Errno::EMFILE", 13 }, { "Errno::ENFILE", 13 }, { "Errno::EINVAL", 13 }, { "Errno::EISDIR", 13 }, { "Errno::ENOTDIR", 14 }, { "Errno::ENODEV", 13 }, { "Errno::EXDEV", 12 }, { "Errno::EEXIST", 13 }, { "Errno::EBUSY", 12 }, { "Errno::ENOTBLK", 14 }, { "Errno::EFAULT", 13 }, { "Errno::EACCES", 13 }, { "Errno::ENOMEM", 13 }, { "Errno::EAGAIN", 13 }, { "Errno::ECHILD", 13 }, { "Errno::EBADF", 12 }, { "Errno::ENOEXEC", 14 }, { "Errno::E2BIG", 12 }, { "Errno::ENXIO", 12 }, { "Errno::EIO", 10 }, { "Errno::EINTR", 12 }, { "Errno::ESRCH", 12 }, { "Errno::ENOENT", 13 }, { "Errno::EPERM", 12 }, { "Errno::NOERROR", 14 }, { "Bignum", 6 }, { "Float", 5 }, { "Fixnum", 6 }, { "Integer", 7 }, { "Numeric", 7 }, { "FloatDomainError", 16 }, { "ZeroDivisionError", 17 }, { "SystemCallError", 15 }, { "Encoding::CompatibilityError", 28 }, { "EncodingError", 13 }, { "NoMemoryError", 13 }, { "SecurityError", 13 }, { "RuntimeError", 12 }, { "NoMethodError", 13 }, { "NameError::message", 18 }, { "NameError", 9 }, { "NotImplementedError", 19 }, { "LoadError", 9 }, { "SyntaxError", 11 }, { "ScriptError", 11 }, { "RangeError", 10 }, { "KeyError", 8 }, { "IndexError", 10 }, { "ArgumentError", 13 }, { "TypeError", 9 }, { "StandardError", 13 }, { "Interrupt", 9 }, { "SignalException", 15 }, { "SystemExit", 10 }, { "Exception", 9 }, { "Symbol", 6 }, { "String", 6 }, { "Encoding", 8 }, { "FalseClass", 10 }, { "TrueClass", 9 }, { "Data", 4 }, { "NilClass", 8 }, { "Class", 5 }, { "Module", 6 }, { "Object", 6 }, { "BasicObject", 11 }, { "Gem::Requirement::BadRequirementError", 37 }, { "Gem::Requirement", 16 }, { "Gem::SourceFetchProblem", 23 }, { "Gem::PlatformMismatch", 21 }, { "Gem::ErrorReason", 16 }, { "Gem::LoadError", 14 }, { "Gem::RemoteSourceException", 26 }, { "Gem::RemoteInstallationSkipped", 30 }, { "Gem::RemoteInstallationCancelled", 32 }, { "Gem::RemoteError", 16 }, { "Gem::OperationNotSupportedError", 31 }, { "Gem::InvalidSpecificationException", 34 }, { "Gem::InstallError", 17 }, { "Gem::Specification", 18 }, { "Date", 4 }, { "Gem::Platform", 13 }, { "Gem::SpecificGemNotFoundException", 33 }, { "Gem::GemNotFoundException", 25 }, { "Gem::FormatException", 20 }, { "Gem::FilePermissionError", 24 }, { "Gem::EndOfYAMLException", 23 }, { "Gem::DocumentError", 18 }, { "Gem::GemNotInHomeException", 26 }, { "Gem::DependencyRemovalException", 31 }, { "Gem::DependencyError", 20 }, { "Gem::CommandLineError", 21 }, { "Gem::Exception", 14 }, { "IRB::SLex", 9 }, { "IRB::Notifier::NoMsgNotifier", 28 }, { "IRB::Notifier::LeveledNotifier", 30 }, { "IRB::Notifier::CompositeNotifier", 32 }, { "IRB::Notifier::AbstractNotifier", 31 }, { "IRB::Notifier::ErrUnrecognizedLevel", 35 }, { "IRB::Notifier::ErrUndefinedNotifier", 35 }, { "IRB::StdioOutputMethod", 22 }, { "IRB::OutputMethod::NotImplementedError", 38 }, { "IRB::OutputMethod", 17 }, { "IRB::IllegalRCGenerator", 23 }, { "IRB::UndefinedPromptMode", 24 }, { "IRB::CantChangeBinding", 22 }, { "IRB::CantShiftToMultiIrbMode", 28 }, { "IRB::NoSuchJob", 14 }, { "IRB::IrbSwitchedToCurrentThread", 31 }, { "IRB::IrbAlreadyDead", 19 }, { "IRB::IllegalParameter", 21 }, { "IRB::CantReturnToNormalMode", 27 }, { "IRB::NotImplementedError", 24 }, { "IRB::UnrecognizedSwitch", 23 }, { "IRB::Irb", 8 }, { "IRB::Abort", 10 }, { "IRB::Locale", 11 }, { "IRB::SLex::ErrNodeNothing", 25 }, { "IRB::SLex::ErrNodeAlreadyExists", 31 }, { "RubyLex", 7 }, { "IRB::SLex::Node", 15 }, { "Gem::SystemExitException", 24 }, { "Gem::VerificationError", 22 }, { "RubyToken::TkError", 18 }, { "RubyToken::TkUnknownChar", 24 }, { "RubyToken::TkOPASGN", 19 }, { "RubyToken::TkOp", 15 }, { "RubyToken::TkVal", 16 }, { "RubyToken::TkId", 15 }, { "RubyToken::TkNode", 17 }, { "RubyToken::Token", 16 }, { "RubyToken::TkUNDEF", 18 }, { "RubyToken::TkDEF", 16 }, { "RubyToken::TkMODULE", 19 }, { "RubyToken::TkCLASS", 18 }, { "RubyToken::TkWHILE", 18 }, { "RubyToken::TkWHEN", 17 }, { "RubyToken::TkCASE", 17 }, { "RubyToken::TkELSE", 17 }, { "RubyToken::TkELSIF", 18 }, { "RubyToken::TkTHEN", 17 }, { "RubyToken::TkUNLESS", 19 }, { "RubyToken::TkIF", 15 }, { "RubyToken::TkEND", 16 }, { "RubyToken::TkENSURE", 19 }, { "RubyToken::TkRESCUE", 19 }, { "RubyToken::TkBEGIN", 18 }, { "RubyToken::TkDO", 15 }, { "RubyToken::TkIN", 15 }, { "RubyToken::TkRETRY", 18 }, { "RubyToken::TkREDO", 17 }, { "RubyToken::TkNEXT", 17 }, { "RubyToken::TkBREAK", 18 }, { "RubyToken::TkFOR", 16 }, { "RubyToken::TkUNTIL", 18 }, { "RubyToken::TkTRUE", 17 }, { "RubyToken::TkNIL", 16 }, { "RubyToken::TkSELF", 17 }, { "RubyToken::TkSUPER", 18 }, { "RubyToken::TkYIELD", 18 }, { "RubyToken::TkRETURN", 19 }, { "RubyToken::TkAND", 16 }, { "RubyToken::TkFALSE", 18 }, { "RubyToken::TkUNLESS_MOD", 23 }, { "RubyToken::TkIF_MOD", 19 }, { "RubyToken::TkNOT", 16 }, { "RubyToken::TkOR", 15 }, { "RubyToken::TkALIAS", 18 }, { "RubyToken::TkUNTIL_MOD", 22 }, { "RubyToken::TkWHILE_MOD", 22 }, { "RubyToken::TkGVAR", 17 }, { "RubyToken::TkFID", 16 }, { "RubyToken::TkIDENTIFIER", 23 }, { "RubyToken::Tk__FILE__", 21 }, { "RubyToken::Tk__LINE__", 21 }, { "RubyToken::TklEND", 17 }, { "RubyToken::TklBEGIN", 19 }, { "RubyToken::TkDEFINED", 20 }, { "RubyToken::TkDREGEXP", 20 }, { "RubyToken::TkDXSTRING", 21 }, { "RubyToken::TkDSTRING", 20 }, { "RubyToken::TkSYMBOL", 19 }, { "RubyToken::TkREGEXP", 19 }, { "RubyToken::TkXSTRING", 20 }, { "RubyToken::TkSTRING", 19 }, { "RubyToken::TkFLOAT", 18 }, { "RubyToken::TkINTEGER", 20 }, { "RubyToken::TkCONSTANT", 21 }, { "RubyToken::TkIVAR", 17 }, { "RubyToken::TkCVAR", 17 }, { "RubyToken::TkNEQ", 16 }, { "RubyToken::TkEQQ", 16 }, { "RubyToken::TkEQ", 15 }, { "RubyToken::TkCMP", 16 }, { "RubyToken::TkPOW", 16 }, { "RubyToken::TkUMINUS", 19 }, { "RubyToken::TkUPLUS", 18 }, { "Exception2MessageMapper::ErrNotRegisteredException", 50 }, { "RubyToken::TkBACK_REF", 21 }, { "RubyToken::TkNTH_REF", 20 }, { "RubyToken::TkLSHFT", 18 }, { "RubyToken::TkASET", 17 }, { "RubyToken::TkAREF", 17 }, { "RubyToken::TkDOT3", 17 }, { "RubyToken::TkDOT2", 17 }, { "RubyToken::TkNMATCH", 19 }, { "RubyToken::TkMATCH", 18 }, { "RubyToken::TkOROP", 17 }, { "RubyToken::TkANDOP", 18 }, { "RubyToken::TkLEQ", 16 }, { "RubyToken::TkGEQ", 16 }, { "RubyToken::TkAMPER", 18 }, { "RubyToken::TkSTAR", 17 }, { "RubyToken::TkfLBRACE", 20 }, { "RubyToken::TkfLBRACK", 20 }, { "RubyToken::TkfLPAREN", 20 }, { "RubyToken::TkCOLON", 18 }, { "RubyToken::TkQUESTION", 21 }, { "RubyToken::TkASSOC", 18 }, { "RubyToken::TkCOLON3", 19 }, { "RubyToken::TkCOLON2", 19 }, { "RubyToken::TkRSHFT", 18 }, { "RubyToken::TkBITAND", 19 }, { "RubyToken::TkBITXOR", 19 }, { "RubyToken::TkBITOR", 18 }, { "RubyToken::TkMOD", 16 }, { "RubyToken::TkDIV", 16 }, { "RubyToken::TkMULT", 17 }, { "RubyToken::TkMINUS", 18 }, { "RubyToken::TkPLUS", 17 }, { "RubyToken::TkLT", 15 }, { "RubyToken::TkGT", 15 }, { "RubyToken::TkSYMBEG", 19 }, { "IRB::DefaultEncodings", 21 }, { "RubyToken::TkRPAREN", 19 }, { "RubyToken::TkLBRACE", 19 }, { "RubyToken::TkLBRACK", 19 }, { "RubyToken::TkLPAREN", 19 }, { "RubyToken::TkDOT", 16 }, { "RubyToken::TkASSIGN", 19 }, { "RubyToken::TkBACKQUOTE", 22 }, { "RubyToken::TkNOTOP", 18 }, { "RubyToken::TkBITNOT", 19 }, { "RubyToken::TkDOLLAR", 19 }, { "RubyToken::TkAT", 15 }, { "RubyToken::TkBACKSLASH", 22 }, { "RubyToken::TkEND_OF_SCRIPT", 26 }, { "RubyToken::TkNL", 15 }, { "RubyToken::TkSPACE", 18 }, { "RubyToken::TkRD_COMMENT", 23 }, { "RubyToken::TkCOMMENT", 20 }, { "RubyToken::TkSEMICOLON", 22 }, { "RubyToken::TkCOMMA", 18 }, { "RubyToken::TkRBRACE", 19 }, { "RubyToken::TkRBRACK", 19 }, { "RubyLex::TerminateLineInput", 27 }, { "RubyLex::SyntaxError", 20 }, { "RubyLex::TkReading2TokenDuplicateError", 38 }, { "RubyLex::TkSymbol2TokenNoKey", 28 }, { "RubyLex::TkReading2TokenNoKey", 29 }, { "RubyLex::AlreadyDefinedToken", 28 }, { "IRB::FileInputMethod", 20 }, { "IRB::StdioInputMethod", 21 }, { "IRB::InputMethod", 16 }, { "IRB::ReadlineInputMethod", 24 }, { "IRB::Context", 12 }, { "IRB::Inspector", 14 }, { "IRB::WorkSpace", 14 }, { 0, 0 } }; static uint64_t micro_time() { struct timeval tv; struct timezone tz; gettimeofday(&tv, &tz); return (uint64_t)tv.tv_sec * 1000000ULL + (uint64_t)tv.tv_usec; } static void perf() { StrLen d; VALUE v; VALUE *slot = 0; uint64_t dt, start; int i, iter = 1000000; int dataCnt = sizeof(data) / sizeof(*data); oj_hash_init(); start = micro_time(); for (i = iter; 0 < i; i--) { for (d = data; 0 != d->str; d++) { v = oj_class_hash_get(d->str, d->len, &slot); if (Qundef == v) { if (0 != slot) { v = ID2SYM(rb_intern(d->str)); *slot = v; } } } } dt = micro_time() - start; #if IS_WINDOWS printf("%d iterations took %ld msecs, %ld gets/msec\n", iter, (long)(dt / 1000), (long)(iter * dataCnt / (dt / 1000))); #else printf("%d iterations took %"PRIu64" msecs, %ld gets/msec\n", iter, dt / 1000, (long)(iter * dataCnt / (dt / 1000))); #endif } void oj_hash_test() { StrLen d; VALUE v; VALUE *slot = 0;; oj_hash_init(); for (d = data; 0 != d->str; d++) { char *s = oj_strndup(d->str, d->len); v = oj_class_hash_get(d->str, d->len, &slot); if (Qnil == v) { if (0 == slot) { printf("*** failed to get a slot for %s\n", s); } else { v = ID2SYM(rb_intern(d->str)); *slot = v; } } else { VALUE rs = rb_funcall2(v, rb_intern("to_s"), 0, 0); printf("*** get on '%s' returned '%s' (%s)\n", s, StringValuePtr(rs), rb_class2name(rb_obj_class(v))); } /*oj_hash_print(c);*/ } printf("*** ---------- hash table ------------\n"); oj_hash_print(); perf(); } #endif oj-2.5.3/ext/oj/odd.c0000644000004100000410000001013212263716750014311 0ustar www-datawww-data/* odd.c * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "odd.h" struct _Odd odds[5]; // bump up if new Odd classes are added static void set_class(Odd odd, const char *classname) { const char **np; ID *idp; odd->classname = classname; odd->clen = strlen(classname); odd->clas = rb_const_get(rb_cObject, rb_intern(classname)); odd->create_obj = odd->clas; odd->create_op = rb_intern("new"); for (np = odd->attr_names, idp = odd->attrs; 0 != *np; np++, idp++) { *idp = rb_intern(*np); } *idp = 0; } void oj_odd_init() { Odd odd; const char **np; odd = odds; // Rational np = odd->attr_names; *np++ = "numerator"; *np++ = "denominator"; *np = 0; set_class(odd, "Rational"); odd->create_obj = rb_cObject; odd->create_op = rb_intern("Rational"); odd->attr_cnt = 2; // Date odd++; np = odd->attr_names; *np++ = "year"; *np++ = "month"; *np++ = "day"; *np++ = "start"; *np++ = 0; set_class(odd, "Date"); odd->attr_cnt = 4; // DateTime odd++; np = odd->attr_names; *np++ = "year"; *np++ = "month"; *np++ = "day"; *np++ = "hour"; *np++ = "min"; *np++ = "sec"; *np++ = "offset"; *np++ = "start"; *np++ = 0; set_class(odd, "DateTime"); odd->attr_cnt = 8; // Range odd++; np = odd->attr_names; *np++ = "begin"; *np++ = "end"; *np++ = "exclude_end?"; *np++ = 0; set_class(odd, "Range"); odd->attr_cnt = 3; // The end. bump up the size of odds if a new class is added. odd++; odd->clas = Qundef; } Odd oj_get_odd(VALUE clas) { Odd odd = odds; for (; Qundef != odd->clas; odd++) { if (clas == odd->clas) { return odd; } } return 0; } Odd oj_get_oddc(const char *classname, size_t len) { Odd odd = odds; for (; Qundef != odd->clas; odd++) { if (len == odd->clen && 0 == strncmp(classname, odd->classname, len)) { return odd; } } return 0; } OddArgs oj_odd_alloc_args(Odd odd) { OddArgs oa = ALLOC_N(struct _OddArgs, 1); VALUE *a; int i; oa->odd = odd; for (i = odd->attr_cnt, a = oa->args; 0 < i; i--, a++) { *a = Qnil; } return oa; } void oj_odd_free(OddArgs args) { xfree(args); } int oj_odd_set_arg(OddArgs args, const char *key, size_t klen, VALUE value) { const char **np; VALUE *vp; int i; for (i = args->odd->attr_cnt, np = args->odd->attr_names, vp = args->args; 0 < i; i--, np++, vp++) { if (0 == strncmp(key, *np, klen) && '\0' == *((*np) + klen)) { *vp = value; return 0; } } return -1; } oj-2.5.3/ext/oj/scp.c0000644000004100000410000002076512263716750014345 0ustar www-datawww-data/* scp.c * Copyright (c) 2012, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include "oj.h" #include "parse.h" #include "encode.h" inline static int respond_to(VALUE obj, ID method) { #ifdef JRUBY_RUBY /* There is a bug in JRuby where rb_respond_to() returns true (1) even if * a method is private. */ { VALUE args[1]; *args = ID2SYM(method); return (Qtrue == rb_funcall2(obj, rb_intern("respond_to?"), 1, args)); } #else return rb_respond_to(obj, method); #endif } static VALUE noop_start(ParseInfo pi) { return Qnil; } static void noop_end(ParseInfo pi) { } static void noop_add_value(ParseInfo pi, VALUE val) { } static void noop_add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { } static void noop_add_num(ParseInfo pi, NumInfo ni) { } static void noop_hash_set_cstr(ParseInfo pi, const char *key, size_t klen, const char *str, size_t len, const char *orig) { } static void noop_hash_set_num(ParseInfo pi, const char *key, size_t klen, NumInfo ni) { } static void noop_hash_set_value(ParseInfo pi, const char *key, size_t klen, VALUE value) { } static void noop_array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { } static void noop_array_append_num(ParseInfo pi, NumInfo ni) { } static void noop_array_append_value(ParseInfo pi, VALUE value) { } static void add_value(ParseInfo pi, VALUE val) { rb_funcall((VALUE)pi->cbc, oj_add_value_id, 1, val); } static void add_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = rb_str_new(str, len); rstr = oj_encode(rstr); rb_funcall((VALUE)pi->cbc, oj_add_value_id, 1, rstr); } static void add_num(ParseInfo pi, NumInfo ni) { rb_funcall((VALUE)pi->cbc, oj_add_value_id, 1, oj_num_as_value(ni)); } static VALUE start_hash(ParseInfo pi) { return rb_funcall((VALUE)pi->cbc, oj_hash_start_id, 0); } static void end_hash(ParseInfo pi) { rb_funcall((VALUE)pi->cbc, oj_hash_end_id, 0); } static VALUE start_array(ParseInfo pi) { return rb_funcall((VALUE)pi->cbc, oj_array_start_id, 0); } static void end_array(ParseInfo pi) { rb_funcall((VALUE)pi->cbc, oj_array_end_id, 0); } static VALUE hash_key(ParseInfo pi, const char *key, size_t klen) { volatile VALUE rkey = rb_str_new(key, klen); rkey = oj_encode(rkey); if (Yes == pi->options.sym_key) { rkey = rb_str_intern(rkey); } return rkey; } static void hash_set_cstr(ParseInfo pi, const char *key, size_t klen, const char *str, size_t len, const char *orig) { volatile VALUE rstr = rb_str_new(str, len); rstr = oj_encode(rstr); rb_funcall((VALUE)pi->cbc, oj_hash_set_id, 3, stack_peek(&pi->stack)->val, hash_key(pi, key, klen), rstr); } static void hash_set_num(ParseInfo pi, const char *key, size_t klen, NumInfo ni) { rb_funcall((VALUE)pi->cbc, oj_hash_set_id, 3, stack_peek(&pi->stack)->val, hash_key(pi, key, klen), oj_num_as_value(ni)); } static void hash_set_value(ParseInfo pi, const char *key, size_t klen, VALUE value) { rb_funcall((VALUE)pi->cbc, oj_hash_set_id, 3, stack_peek(&pi->stack)->val, hash_key(pi, key, klen), value); } static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const char *orig) { volatile VALUE rstr = rb_str_new(str, len); rstr = oj_encode(rstr); rb_funcall((VALUE)pi->cbc, oj_array_append_id, 2, stack_peek(&pi->stack)->val, rstr); } static void array_append_num(ParseInfo pi, NumInfo ni) { rb_funcall((VALUE)pi->cbc, oj_array_append_id, 2, stack_peek(&pi->stack)->val, oj_num_as_value(ni)); } static void array_append_value(ParseInfo pi, VALUE value) { rb_funcall((VALUE)pi->cbc, oj_array_append_id, 2, stack_peek(&pi->stack)->val, value); } static VALUE protect_parse(VALUE pip) { oj_parse2((ParseInfo)pip); return Qnil; } VALUE oj_sc_parse(int argc, VALUE *argv, VALUE self) { struct _ParseInfo pi; char *buf = 0; VALUE input; VALUE handler; volatile VALUE wrapped_stack; int line = 0; if (argc < 2) { rb_raise(rb_eArgError, "Wrong number of arguments to saj_parse."); } handler = *argv;; input = argv[1]; pi.json = 0; pi.options = oj_default_options; if (3 == argc) { oj_parse_options(argv[2], &pi.options); } pi.cbc = (void*)handler; pi.start_hash = respond_to(handler, oj_hash_start_id) ? start_hash : noop_start; pi.end_hash = respond_to(handler, oj_hash_end_id) ? end_hash : noop_end; pi.start_array = respond_to(handler, oj_array_start_id) ? start_array : noop_start; pi.end_array = respond_to(handler, oj_array_end_id) ? end_array : noop_end; if (respond_to(handler, oj_hash_set_id)) { pi.hash_set_value = hash_set_value; pi.hash_set_cstr = hash_set_cstr; pi.hash_set_num = hash_set_num; pi.expect_value = 1; } else { pi.hash_set_value = noop_hash_set_value; pi.hash_set_cstr = noop_hash_set_cstr; pi.hash_set_num = noop_hash_set_num; pi.expect_value = 0; } if (respond_to(handler, oj_array_append_id)) { pi.array_append_value = array_append_value; pi.array_append_cstr = array_append_cstr; pi.array_append_num = array_append_num; pi.expect_value = 1; } else { pi.array_append_value = noop_array_append_value; pi.array_append_cstr = noop_array_append_cstr; pi.array_append_num = noop_array_append_num; pi.expect_value = 0; } if (respond_to(handler, oj_add_value_id)) { pi.add_cstr = add_cstr; pi.add_num = add_num; pi.add_value = add_value; pi.expect_value = 1; } else { pi.add_cstr = noop_add_cstr; pi.add_num = noop_add_num; pi.add_value = noop_add_value; pi.expect_value = 0; } if (rb_type(input) == T_STRING) { pi.json = StringValuePtr(input); } else { VALUE clas = rb_obj_class(input); volatile VALUE s; if (oj_stringio_class == clas) { s = rb_funcall2(input, oj_string_id, 0, 0); pi.json = StringValuePtr(input); #ifndef JRUBY_RUBY #if !IS_WINDOWS // JRuby gets confused with what is the real fileno. } else if (rb_respond_to(input, oj_fileno_id) && Qnil != (s = rb_funcall(input, oj_fileno_id, 0))) { int fd = FIX2INT(s); ssize_t cnt; size_t len = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); buf = ALLOC_N(char, len + 1); pi.json = buf; if (0 >= (cnt = read(fd, (char*)pi.json, len)) || cnt != (ssize_t)len) { if (0 != buf) { xfree(buf); } rb_raise(rb_eIOError, "failed to read from IO Object."); } ((char*)pi.json)[len] = '\0'; #endif #endif } else if (rb_respond_to(input, oj_read_id)) { s = rb_funcall2(input, oj_read_id, 0, 0); pi.json = rb_string_value_cstr((VALUE*)&s); } else { rb_raise(rb_eArgError, "saj_parse() expected a String or IO Object."); } } wrapped_stack = oj_stack_init(&pi.stack); rb_protect(protect_parse, (VALUE)&pi, &line); DATA_PTR(wrapped_stack) = NULL; if (0 != buf) { xfree(buf); } stack_cleanup(&pi.stack); if (0 != line) { rb_jump_tag(line); } if (err_has(&pi.err)) { oj_err_raise(&pi.err); } return Qnil; } oj-2.5.3/ext/oj/val_stack.c0000644000004100000410000000621712263716750015523 0ustar www-datawww-data/* val_stack.c * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "oj.h" #include "val_stack.h" static void mark(void *ptr) { ValStack stack = (ValStack)ptr; Val v; if (0 == ptr) { return; } #if USE_PTHREAD_MUTEX pthread_mutex_lock(&stack->mutex); #elif USE_RB_MUTEX rb_mutex_lock(stack->mutex); rb_gc_mark(stack->mutex); #endif for (v = stack->head; v < stack->tail; v++) { if (Qnil != v->val && Qundef != v->val) { rb_gc_mark(v->val); } } #if USE_PTHREAD_MUTEX pthread_mutex_unlock(&stack->mutex); #elif USE_RB_MUTEX rb_mutex_unlock(stack->mutex); #endif } VALUE oj_stack_init(ValStack stack) { #if USE_PTHREAD_MUTEX pthread_mutex_init(&stack->mutex, 0); #elif USE_RB_MUTEX stack->mutex = rb_mutex_new(); #endif stack->head = stack->base; stack->end = stack->base + sizeof(stack->base) / sizeof(struct _Val); stack->tail = stack->head; stack->head->val = Qundef; stack->head->key = 0; stack->head->classname = 0; stack->head->klen = 0; stack->head->clen = 0; stack->head->next = NEXT_NONE; return Data_Wrap_Struct(oj_cstack_class, mark, 0, stack); } const char* oj_stack_next_string(ValNext n) { switch (n) { case NEXT_ARRAY_NEW: return "array element or close"; case NEXT_ARRAY_ELEMENT: return "array element"; case NEXT_ARRAY_COMMA: return "comma"; case NEXT_HASH_NEW: return "hash pair or close"; case NEXT_HASH_KEY: return "hash key"; case NEXT_HASH_COLON: return "colon"; case NEXT_HASH_VALUE: return "hash value"; case NEXT_HASH_COMMA: return "comma"; case NEXT_NONE: break; default: break; } return "nothing"; } oj-2.5.3/ext/oj/compat.c0000644000004100000410000000677312263716750015046 0ustar www-datawww-data/* compat.c * Copyright (c) 2012, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include "oj.h" #include "err.h" #include "parse.h" #include "resolve.h" #include "hash.h" #include "encode.h" static void hash_set_cstr(ParseInfo pi, const char *key, size_t klen, const char *str, size_t len, const char *orig) { Val parent = stack_peek(&pi->stack); if (0 != pi->options.create_id && *pi->options.create_id == *key && pi->options.create_id_len == klen && 0 == strncmp(pi->options.create_id, key, klen)) { if (str < pi->json || pi->cur < str) { parent->classname = oj_strndup(str, len); } else { parent->classname = str; } parent->clen = len; } else { volatile VALUE rstr = rb_str_new(str, len); volatile VALUE rkey = rb_str_new(key, klen); rstr = oj_encode(rstr); rkey = oj_encode(rkey); if (Yes == pi->options.sym_key) { rkey = rb_str_intern(rkey); } rb_hash_aset(parent->val, rkey, rstr); } } static void end_hash(struct _ParseInfo *pi) { Val parent = stack_peek(&pi->stack); if (0 != parent->classname) { VALUE clas; clas = oj_name2class(pi, parent->classname, parent->clen, 0); if (Qundef != clas) { // else an error parent->val = rb_funcall(clas, oj_json_create_id, 1, parent->val); } if (parent->classname < pi->json || pi->end < parent->classname) { xfree((char*)parent->classname); parent->classname = 0; } } } void oj_set_compat_callbacks(ParseInfo pi) { oj_set_strict_callbacks(pi); pi->end_hash = end_hash; pi->hash_set_cstr = hash_set_cstr; } VALUE oj_compat_parse(int argc, VALUE *argv, VALUE self) { struct _ParseInfo pi; pi.options = oj_default_options; oj_set_compat_callbacks(&pi); return oj_pi_parse(argc, argv, &pi, 0, 0); } VALUE oj_compat_parse_cstr(int argc, VALUE *argv, char *json, size_t len) { struct _ParseInfo pi; pi.options = oj_default_options; oj_set_strict_callbacks(&pi); pi.end_hash = end_hash; pi.hash_set_cstr = hash_set_cstr; return oj_pi_parse(argc, argv, &pi, json, len); } oj-2.5.3/ext/oj/circarray.h0000644000004100000410000000406512263716750015537 0ustar www-datawww-data/* circarray.h * Copyright (c) 2012, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __OJ_CIRCARRAY_H__ #define __OJ_CIRCARRAY_H__ #include "ruby.h" typedef struct _CircArray { VALUE obj_array[1024]; VALUE *objs; unsigned long size; // allocated size or initial array size unsigned long cnt; } *CircArray; extern CircArray oj_circ_array_new(void); extern void oj_circ_array_free(CircArray ca); extern void oj_circ_array_set(CircArray ca, VALUE obj, unsigned long id); extern VALUE oj_circ_array_get(CircArray ca, unsigned long id); #endif /* __OJ_CIRCARRAY_H__ */ oj-2.5.3/ext/oj/cache8.h0000644000004100000410000000370212263716750014710 0ustar www-datawww-data/* cache8.h * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __OJ_CACHE8_H__ #define __OJ_CACHE8_H__ #include "ruby.h" #include "stdint.h" typedef struct _Cache8 *Cache8; typedef uint64_t slot_t; typedef uint64_t sid_t; extern void oj_cache8_new(Cache8 *cache); extern void oj_cache8_delete(Cache8 cache); extern slot_t oj_cache8_get(Cache8 cache, sid_t key, slot_t **slot); extern void oj_cache8_print(Cache8 cache); #endif /* __OJ_CACHE8_H__ */ oj-2.5.3/ext/oj/dump.c0000644000004100000410000015172412263716750014525 0ustar www-datawww-data/* dump.c * Copyright (c) 2012, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #if !IS_WINDOWS #include #endif #include #include #include #include #include #include #include "oj.h" #include "cache8.h" #include "odd.h" #if !HAS_ENCODING_SUPPORT || defined(RUBINIUS_RUBY) #define rb_eEncodingError rb_eException #endif // Workaround in case INFINITY is not defined in math.h or if the OS is CentOS #define OJ_INFINITY (1.0/0.0) // Extra padding at end of buffer. #define BUFFER_EXTRA 10 typedef unsigned long ulong; static void raise_strict(VALUE obj); static void dump_val(VALUE obj, int depth, Out out); static void dump_nil(Out out); static void dump_true(Out out); static void dump_false(Out out); static void dump_fixnum(VALUE obj, Out out); static void dump_bignum(VALUE obj, Out out); static void dump_float(VALUE obj, Out out); static void dump_raw(const char *str, size_t cnt, Out out); static void dump_cstr(const char *str, size_t cnt, int is_sym, int escape1, Out out); static void dump_hex(uint8_t c, Out out); static void dump_str_comp(VALUE obj, Out out); static void dump_str_obj(VALUE obj, Out out); static void dump_sym_comp(VALUE obj, Out out); static void dump_sym_obj(VALUE obj, Out out); static void dump_class_comp(VALUE obj, Out out); static void dump_class_obj(VALUE obj, Out out); static void dump_array(VALUE obj, int depth, Out out); static int hash_cb_strict(VALUE key, VALUE value, Out out); static int hash_cb_compat(VALUE key, VALUE value, Out out); static int hash_cb_object(VALUE key, VALUE value, Out out); static void dump_hash(VALUE obj, int depth, int mode, Out out); static void dump_time(VALUE obj, Out out); static void dump_ruby_time(VALUE obj, Out out); static void dump_xml_time(VALUE obj, Out out); static void dump_data_strict(VALUE obj, Out out); static void dump_data_null(VALUE obj, Out out); static void dump_data_comp(VALUE obj, int depth, Out out); static void dump_data_obj(VALUE obj, int depth, Out out); static void dump_obj_comp(VALUE obj, int depth, Out out); static void dump_obj_obj(VALUE obj, int depth, Out out); static void dump_struct_comp(VALUE obj, int depth, Out out); static void dump_struct_obj(VALUE obj, int depth, Out out); #if HAS_IVAR_HELPERS static int dump_attr_cb(ID key, VALUE value, Out out); #endif static void dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out); static void dump_odd(VALUE obj, Odd odd, VALUE clas, int depth, Out out); static void grow(Out out, size_t len); static size_t hibit_friendly_size(const uint8_t *str, size_t len); static size_t xss_friendly_size(const uint8_t *str, size_t len); static size_t ascii_friendly_size(const uint8_t *str, size_t len); static void dump_leaf(Leaf leaf, int depth, Out out); static void dump_leaf_str(Leaf leaf, Out out); static void dump_leaf_fixnum(Leaf leaf, Out out); static void dump_leaf_float(Leaf leaf, Out out); static void dump_leaf_array(Leaf leaf, int depth, Out out); static void dump_leaf_hash(Leaf leaf, int depth, Out out); // These are used to detect rails re-call of Oj.dump() inside the to_json() // method. It is not thread safe. static VALUE last_obj = Qundef; static int oj_rails_hack = -1; static const char hex_chars[17] = "0123456789abcdef"; static char hibit_friendly_chars[256] = "\ 66666666222622666666666666666666\ 11211111111111111111111111111111\ 11111111111111111111111111112111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111\ 11111111111111111111111111111111"; // High bit set characters are always encoded as unicode. Worse case is 3 // bytes per character in the output. That makes this conservative. static char ascii_friendly_chars[256] = "\ 66666666222622666666666666666666\ 11211111111111121111111111111111\ 11111111111111111111111111112111\ 11111111111111111111111111111116\ 33333333333333333333333333333333\ 33333333333333333333333333333333\ 33333333333333333333333333333333\ 33333333333333333333333333333333"; static char xss_friendly_chars[256] = "\ 66666666222622666666666666666666\ 11211161111111121111111111116161\ 11111111111111111111111111112111\ 11111111111111111111111111111116\ 33333333333333333333333333333333\ 33333333333333333333333333333333\ 33333333333333333333333333333333\ 33333333333333333333333333333333"; inline static size_t hibit_friendly_size(const uint8_t *str, size_t len) { size_t size = 0; for (; 0 < len; str++, len--) { size += hibit_friendly_chars[*str]; } return size - len * (size_t)'0'; } inline static size_t ascii_friendly_size(const uint8_t *str, size_t len) { size_t size = 0; for (; 0 < len; str++, len--) { size += ascii_friendly_chars[*str]; } return size - len * (size_t)'0'; } inline static size_t xss_friendly_size(const uint8_t *str, size_t len) { size_t size = 0; for (; 0 < len; str++, len--) { size += xss_friendly_chars[*str]; } return size - len * (size_t)'0'; } inline static void fill_indent(Out out, int cnt) { if (0 < out->indent) { cnt *= out->indent; *out->cur++ = '\n'; for (; 0 < cnt; cnt--) { *out->cur++ = ' '; } } } inline static const char* ulong2str(uint32_t num, char *end) { char *b; *end-- = '\0'; for (b = end; 0 < num || b == end; num /= 10, b--) { *b = (num % 10) + '0'; } b++; return b; } inline static void dump_ulong(unsigned long num, Out out) { char buf[32]; char *b = buf + sizeof(buf) - 1; *b-- = '\0'; if (0 < num) { for (; 0 < num; num /= 10, b--) { *b = (num % 10) + '0'; } b++; } else { *b = '0'; } for (; '\0' != *b; b++) { *out->cur++ = *b; } *out->cur = '\0'; } static void grow(Out out, size_t len) { size_t size = out->end - out->buf; long pos = out->cur - out->buf; char *buf; size *= 2; if (size <= len * 2 + pos) { size += len; } if (out->allocated) { buf = REALLOC_N(out->buf, char, (size + BUFFER_EXTRA)); } else { buf = ALLOC_N(char, (size + BUFFER_EXTRA)); out->allocated = 1; memcpy(buf, out->buf, out->end - out->buf + BUFFER_EXTRA); } if (0 == buf) { rb_raise(rb_eNoMemError, "Failed to create string. [%d:%s]\n", ENOSPC, strerror(ENOSPC)); } out->buf = buf; out->end = buf + size; out->cur = out->buf + pos; } inline static void dump_hex(uint8_t c, Out out) { uint8_t d = (c >> 4) & 0x0F; *out->cur++ = hex_chars[d]; d = c & 0x0F; *out->cur++ = hex_chars[d]; } static void dump_raw(const char *str, size_t cnt, Out out) { if (out->end - out->cur <= (long)cnt + 10) { grow(out, cnt + 10); } memcpy(out->cur, str, cnt); out->cur += cnt; *out->cur = '\0'; } const char* dump_unicode(const char *str, const char *end, Out out) { uint32_t code = 0; uint8_t b = *(uint8_t*)str; int i, cnt; if (0xC0 == (0xE0 & b)) { cnt = 1; code = b & 0x0000001F; } else if (0xE0 == (0xF0 & b)) { cnt = 2; code = b & 0x0000000F; } else if (0xF0 == (0xF8 & b)) { cnt = 3; code = b & 0x00000007; } else if (0xF8 == (0xFC & b)) { cnt = 4; code = b & 0x00000003; } else if (0xFC == (0xFE & b)) { cnt = 5; code = b & 0x00000001; } else { cnt = 0; rb_raise(rb_eEncodingError, "Invalid Unicode\n"); } str++; for (; 0 < cnt; cnt--, str++) { b = *(uint8_t*)str; if (end <= str || 0x80 != (0xC0 & b)) { rb_raise(rb_eEncodingError, "Invalid Unicode\n"); } code = (code << 6) | (b & 0x0000003F); } if (0x0000FFFF < code) { uint32_t c1; code -= 0x00010000; c1 = ((code >> 10) & 0x000003FF) + 0x0000D800; code = (code & 0x000003FF) + 0x0000DC00; *out->cur++ = '\\'; *out->cur++ = 'u'; for (i = 3; 0 <= i; i--) { *out->cur++ = hex_chars[(uint8_t)(c1 >> (i * 4)) & 0x0F]; } } *out->cur++ = '\\'; *out->cur++ = 'u'; for (i = 3; 0 <= i; i--) { *out->cur++ = hex_chars[(uint8_t)(code >> (i * 4)) & 0x0F]; } return str - 1; } // returns 0 if not using circular references, -1 if not further writing is // needed (duplicate), and a positive value if the object was added to the cache. static long check_circular(VALUE obj, Out out) { slot_t id = 0; slot_t *slot; if (ObjectMode == out->opts->mode && Yes == out->opts->circular) { if (0 == (id = oj_cache8_get(out->circ_cache, obj, &slot))) { out->circ_cnt++; id = out->circ_cnt; *slot = id; } else { if (out->end - out->cur <= 18) { grow(out, 18); } *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'r'; dump_ulong(id, out); *out->cur++ = '"'; return -1; } } return (long)id; } static void dump_nil(Out out) { size_t size = 4; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = 'n'; *out->cur++ = 'u'; *out->cur++ = 'l'; *out->cur++ = 'l'; *out->cur = '\0'; } static void dump_true(Out out) { size_t size = 4; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = 't'; *out->cur++ = 'r'; *out->cur++ = 'u'; *out->cur++ = 'e'; *out->cur = '\0'; } static void dump_false(Out out) { size_t size = 5; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = 'f'; *out->cur++ = 'a'; *out->cur++ = 'l'; *out->cur++ = 's'; *out->cur++ = 'e'; *out->cur = '\0'; } static void dump_fixnum(VALUE obj, Out out) { char buf[32]; char *b = buf + sizeof(buf) - 1; long num = NUM2LONG(obj); int neg = 0; if (0 > num) { neg = 1; num = -num; } *b-- = '\0'; if (0 < num) { for (; 0 < num; num /= 10, b--) { *b = (num % 10) + '0'; } if (neg) { *b = '-'; } else { b++; } } else { *b = '0'; } if (out->end - out->cur <= (long)(sizeof(buf) - (b - buf))) { grow(out, sizeof(buf) - (b - buf)); } for (; '\0' != *b; b++) { *out->cur++ = *b; } *out->cur = '\0'; } static void dump_bignum(VALUE obj, Out out) { volatile VALUE rs = rb_big2str(obj, 10); int cnt = (int)RSTRING_LEN(rs); if (out->end - out->cur <= (long)cnt) { grow(out, cnt); } memcpy(out->cur, rb_string_value_ptr((VALUE*)&rs), cnt); out->cur += cnt; *out->cur = '\0'; } // Removed dependencies on math due to problems with CentOS 5.4. static void dump_float(VALUE obj, Out out) { char buf[64]; char *b; double d = rb_num2dbl(obj); int cnt; if (0.0 == d) { b = buf; *b++ = '0'; *b++ = '.'; *b++ = '0'; *b++ = '\0'; cnt = 3; } else if (OJ_INFINITY == d) { strcpy(buf, "Infinity"); cnt = 8; } else if (-OJ_INFINITY == d) { strcpy(buf, "-Infinity"); cnt = 9; } else if (isnan(d)) { strcpy(buf, "NaN"); cnt = 3; } else if (d == (double)(long long int)d) { cnt = sprintf(buf, "%.1f", d); // used sprintf due to bug in snprintf } else { cnt = sprintf(buf, "%0.15g", d); // used sprintf due to bug in snprintf } if (out->end - out->cur <= (long)cnt) { grow(out, cnt); } for (b = buf; '\0' != *b; b++) { *out->cur++ = *b; } *out->cur = '\0'; } static void dump_cstr(const char *str, size_t cnt, int is_sym, int escape1, Out out) { size_t size; char *cmap; switch (out->opts->escape_mode) { case ASCIIEsc: cmap = ascii_friendly_chars; size = ascii_friendly_size((uint8_t*)str, cnt); break; case XSSEsc: cmap = xss_friendly_chars; size = xss_friendly_size((uint8_t*)str, cnt); break; case JSONEsc: default: cmap = hibit_friendly_chars; size = hibit_friendly_size((uint8_t*)str, cnt); } if (out->end - out->cur <= (long)size + BUFFER_EXTRA) { // extra 10 for escaped first char, quotes, and sym grow(out, size + BUFFER_EXTRA); } *out->cur++ = '"'; if (escape1) { *out->cur++ = '\\'; *out->cur++ = 'u'; *out->cur++ = '0'; *out->cur++ = '0'; dump_hex((uint8_t)*str, out); cnt--; size--; str++; is_sym = 0; // just to make sure } if (cnt == size) { if (is_sym) { *out->cur++ = ':'; } for (; '\0' != *str; str++) { *out->cur++ = *str; } *out->cur++ = '"'; } else { const char *end = str + cnt; if (is_sym) { *out->cur++ = ':'; } for (; str < end; str++) { switch (cmap[(uint8_t)*str]) { case '1': *out->cur++ = *str; break; case '2': *out->cur++ = '\\'; switch (*str) { case '\b': *out->cur++ = 'b'; break; case '\t': *out->cur++ = 't'; break; case '\n': *out->cur++ = 'n'; break; case '\f': *out->cur++ = 'f'; break; case '\r': *out->cur++ = 'r'; break; default: *out->cur++ = *str; break; } break; case '3': // Unicode str = dump_unicode(str, end, out); break; case '6': // control characters *out->cur++ = '\\'; *out->cur++ = 'u'; *out->cur++ = '0'; *out->cur++ = '0'; dump_hex((uint8_t)*str, out); break; default: break; // ignore, should never happen if the table is correct } } *out->cur++ = '"'; } *out->cur = '\0'; } static void dump_str_comp(VALUE obj, Out out) { dump_cstr(rb_string_value_ptr((VALUE*)&obj), RSTRING_LEN(obj), 0, 0, out); } static void dump_str_obj(VALUE obj, Out out) { const char *s = rb_string_value_ptr((VALUE*)&obj); size_t len = RSTRING_LEN(obj); char s1 = s[1]; dump_cstr(s, len, 0, (':' == *s || ('^' == *s && ('r' == s1 || 'i' == s1))), out); } static void dump_sym_comp(VALUE obj, Out out) { const char *sym = rb_id2name(SYM2ID(obj)); dump_cstr(sym, strlen(sym), 0, 0, out); } static void dump_sym_obj(VALUE obj, Out out) { const char *sym = rb_id2name(SYM2ID(obj)); size_t len = strlen(sym); dump_cstr(sym, len, 1, 0, out); } static void dump_class_comp(VALUE obj, Out out) { const char *s = rb_class2name(obj); dump_cstr(s, strlen(s), 0, 0, out); } static void dump_class_obj(VALUE obj, Out out) { const char *s = rb_class2name(obj); size_t len = strlen(s); if (out->end - out->cur <= 6) { grow(out, 6); } *out->cur++ = '{'; *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'c'; *out->cur++ = '"'; *out->cur++ = ':'; dump_cstr(s, len, 0, 0, out); *out->cur++ = '}'; *out->cur = '\0'; } static void dump_array(VALUE a, int depth, Out out) { size_t size; int i, cnt; int d2 = depth + 1; long id = check_circular(a, out); if (id < 0) { return; } cnt = (int)RARRAY_LEN(a); *out->cur++ = '['; if (0 < id) { size = d2 * out->indent + 16; if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'i'; dump_ulong(id, out); *out->cur++ = '"'; } size = 2; if (out->end - out->cur <= (long)size) { grow(out, size); } if (0 == cnt) { *out->cur++ = ']'; } else { if (0 < id) { *out->cur++ = ','; } if (0 == out->opts->dump_opts) { size = d2 * out->indent + 2; } else { size = d2 * out->opts->dump_opts->indent_size + out->opts->dump_opts->array_size + 1; } cnt--; for (i = 0; i <= cnt; i++) { if (out->end - out->cur <= (long)size) { grow(out, size); } if (0 == out->opts->dump_opts) { fill_indent(out, d2); } else { if (0 < out->opts->dump_opts->array_size) { strcpy(out->cur, out->opts->dump_opts->array_nl); out->cur += out->opts->dump_opts->array_size; } if (0 < out->opts->dump_opts->indent_size) { int i; for (i = d2; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts->indent); out->cur += out->opts->dump_opts->indent_size; } } } dump_val(rb_ary_entry(a, i), d2, out); if (i < cnt) { *out->cur++ = ','; } } size = depth * out->indent + 1; if (out->end - out->cur <= (long)size) { grow(out, size); } if (0 == out->opts->dump_opts) { fill_indent(out, depth); } else { //printf("*** d2: %u indent: %u '%s'\n", d2, out->opts->dump_opts->indent_size, out->opts->dump_opts->indent); if (0 < out->opts->dump_opts->array_size) { strcpy(out->cur, out->opts->dump_opts->array_nl); out->cur += out->opts->dump_opts->array_size; } if (0 < out->opts->dump_opts->indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts->indent); out->cur += out->opts->dump_opts->indent_size; } } } *out->cur++ = ']'; } *out->cur = '\0'; } static int hash_cb_strict(VALUE key, VALUE value, Out out) { int depth = out->depth; long size; if (rb_type(key) != T_STRING) { rb_raise(rb_eTypeError, "In :strict mode all Hash keys must be Strings, not %s.\n", rb_class2name(rb_obj_class(key))); } if (0 == out->opts->dump_opts) { size = depth * out->indent + 1; if (out->end - out->cur <= size) { grow(out, size); } fill_indent(out, depth); dump_str_comp(key, out); *out->cur++ = ':'; } else { size = depth * out->opts->dump_opts->indent_size + out->opts->dump_opts->hash_size + 1; if (out->end - out->cur <= size) { grow(out, size); } if (0 < out->opts->dump_opts->hash_size) { strcpy(out->cur, out->opts->dump_opts->hash_nl); out->cur += out->opts->dump_opts->hash_size; } if (0 < out->opts->dump_opts->indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts->indent); out->cur += out->opts->dump_opts->indent_size; } } dump_str_comp(key, out); size = out->opts->dump_opts->before_size + out->opts->dump_opts->after_size + 2; if (out->end - out->cur <= size) { grow(out, size); } if (0 < out->opts->dump_opts->before_size) { strcpy(out->cur, out->opts->dump_opts->before_sep); out->cur += out->opts->dump_opts->before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts->after_size) { strcpy(out->cur, out->opts->dump_opts->after_sep); out->cur += out->opts->dump_opts->after_size; } } dump_val(value, depth, out); out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static int hash_cb_compat(VALUE key, VALUE value, Out out) { int depth = out->depth; long size; if (0 == out->opts->dump_opts) { size = depth * out->indent + 1; if (out->end - out->cur <= size) { grow(out, size); } fill_indent(out, depth); } else { size = depth * out->opts->dump_opts->indent_size + out->opts->dump_opts->hash_size + 1; if (out->end - out->cur <= size) { grow(out, size); } if (0 < out->opts->dump_opts->hash_size) { strcpy(out->cur, out->opts->dump_opts->hash_nl); out->cur += out->opts->dump_opts->hash_size; } if (0 < out->opts->dump_opts->indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts->indent); out->cur += out->opts->dump_opts->indent_size; } } } switch (rb_type(key)) { case T_STRING: dump_str_comp(key, out); break; case T_SYMBOL: dump_sym_comp(key, out); break; default: /*rb_raise(rb_eTypeError, "In :compat mode all Hash keys must be Strings or Symbols, not %s.\n", rb_class2name(rb_obj_class(key)));*/ dump_str_comp(rb_funcall(key, oj_to_s_id, 0), out); break; } if (0 == out->opts->dump_opts) { *out->cur++ = ':'; } else { size = out->opts->dump_opts->before_size + out->opts->dump_opts->after_size + 2; if (out->end - out->cur <= size) { grow(out, size); } if (0 < out->opts->dump_opts->before_size) { strcpy(out->cur, out->opts->dump_opts->before_sep); out->cur += out->opts->dump_opts->before_size; } *out->cur++ = ':'; if (0 < out->opts->dump_opts->after_size) { strcpy(out->cur, out->opts->dump_opts->after_sep); out->cur += out->opts->dump_opts->after_size; } } dump_val(value, depth, out); out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static int hash_cb_object(VALUE key, VALUE value, Out out) { int depth = out->depth; long size = depth * out->indent + 1; if (out->end - out->cur <= size) { grow(out, size); } fill_indent(out, depth); if (rb_type(key) == T_STRING) { dump_str_obj(key, out); *out->cur++ = ':'; dump_val(value, depth, out); } else if (rb_type(key) == T_SYMBOL) { dump_sym_obj(key, out); *out->cur++ = ':'; dump_val(value, depth, out); } else { int d2 = depth + 1; long s2 = size + out->indent + 1; int i; int started = 0; uint8_t b; if (out->end - out->cur <= s2 + 15) { grow(out, s2 + 15); } *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = '#'; out->hash_cnt++; for (i = 28; 0 <= i; i -= 4) { b = (uint8_t)((out->hash_cnt >> i) & 0x0000000F); if ('\0' != b) { started = 1; } if (started) { *out->cur++ = hex_chars[b]; } } *out->cur++ = '"'; *out->cur++ = ':'; *out->cur++ = '['; fill_indent(out, d2); dump_val(key, d2, out); if (out->end - out->cur <= (long)s2) { grow(out, s2); } *out->cur++ = ','; fill_indent(out, d2); dump_val(value, d2, out); if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, depth); *out->cur++ = ']'; } out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } static void dump_hash(VALUE obj, int depth, int mode, Out out) { int cnt = (int)RHASH_SIZE(obj); size_t size = depth * out->indent + 2; if (out->end - out->cur <= 2) { grow(out, 2); } if (0 == cnt) { *out->cur++ = '{'; *out->cur++ = '}'; } else { long id = check_circular(obj, out); if (0 > id) { return; } *out->cur++ = '{'; if (0 < id) { if (out->end - out->cur <= (long)size + 16) { grow(out, size + 16); } fill_indent(out, depth + 1); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'i'; *out->cur++ = '"'; *out->cur++ = ':'; dump_ulong(id, out); *out->cur++ = ','; } out->depth = depth + 1; if (ObjectMode == mode) { rb_hash_foreach(obj, hash_cb_object, (VALUE)out); } else if (CompatMode == mode) { rb_hash_foreach(obj, hash_cb_compat, (VALUE)out); } else { rb_hash_foreach(obj, hash_cb_strict, (VALUE)out); } if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } if (0 == out->opts->dump_opts) { if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, depth); } else { size = depth * out->opts->dump_opts->indent_size + out->opts->dump_opts->hash_size + 1; if (out->end - out->cur <= (long)size) { grow(out, size); } if (0 < out->opts->dump_opts->hash_size) { strcpy(out->cur, out->opts->dump_opts->hash_nl); out->cur += out->opts->dump_opts->hash_size; } if (0 < out->opts->dump_opts->indent_size) { int i; for (i = depth; 0 < i; i--) { strcpy(out->cur, out->opts->dump_opts->indent); out->cur += out->opts->dump_opts->indent_size; } } } *out->cur++ = '}'; } *out->cur = '\0'; } static void dump_time(VALUE obj, Out out) { char buf[64]; char *b = buf + sizeof(buf) - 1; long size; char *dot = b - 10; int neg = 0; long one = 1000000000; #if HAS_RB_TIME_TIMESPEC struct timespec ts = rb_time_timespec(obj); time_t sec = ts.tv_sec; long nsec = ts.tv_nsec; #else time_t sec = NUM2LONG(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); #if HAS_NANO_TIME long nsec = NUM2LONG(rb_funcall2(obj, oj_tv_nsec_id, 0, 0)); #else long nsec = NUM2LONG(rb_funcall2(obj, oj_tv_usec_id, 0, 0)) * 1000; #endif #endif if (0 > sec) { neg = 1; sec = -sec; if (0 < nsec) { nsec = 1000000000 - nsec; #ifndef JRUBY_RUBY sec--; #endif } } *b-- = '\0'; if (0 < out->opts->sec_prec) { if (9 > out->opts->sec_prec) { int i; for (i = 9 - out->opts->sec_prec; 0 < i; i--) { dot++; nsec = (nsec + 5) / 10; one /= 10; } } if (one <= nsec) { nsec -= one; sec++; } for (; dot < b; b--, nsec /= 10) { *b = '0' + (nsec % 10); } *b-- = '.'; } if (0 == sec) { *b-- = '0'; } else { for (; 0 < sec; b--, sec /= 10) { *b = '0' + (sec % 10); } } if (neg) { *b-- = '-'; } b++; size = sizeof(buf) - (b - buf) - 1; if (out->end - out->cur <= size) { grow(out, size); } memcpy(out->cur, b, size); out->cur += size; *out->cur = '\0'; } static void dump_ruby_time(VALUE obj, Out out) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out); } static void dump_xml_time(VALUE obj, Out out) { char buf[64]; struct tm *tm; long one = 1000000000; #if HAS_RB_TIME_TIMESPEC struct timespec ts = rb_time_timespec(obj); time_t sec = ts.tv_sec; long nsec = ts.tv_nsec; #else time_t sec = NUM2LONG(rb_funcall2(obj, oj_tv_sec_id, 0, 0)); #if HAS_NANO_TIME long nsec = NUM2LONG(rb_funcall2(obj, oj_tv_nsec_id, 0, 0)); #else long nsec = NUM2LONG(rb_funcall2(obj, oj_tv_usec_id, 0, 0)) * 1000; #endif #endif long tz_secs = NUM2LONG(rb_funcall2(obj, oj_utc_offset_id, 0, 0)); int tzhour, tzmin; char tzsign = '+'; if (out->end - out->cur <= 36) { grow(out, 36); } if (9 > out->opts->sec_prec) { int i; for (i = 9 - out->opts->sec_prec; 0 < i; i--) { nsec = (nsec + 5) / 10; one /= 10; } if (one <= nsec) { nsec -= one; sec++; } } // 2012-01-05T23:58:07.123456000+09:00 //tm = localtime(&sec); sec += tz_secs; tm = gmtime(&sec); #if 1 if (0 > tz_secs) { tzsign = '-'; tzhour = (int)(tz_secs / -3600); tzmin = (int)(tz_secs / -60) - (tzhour * 60); } else { tzhour = (int)(tz_secs / 3600); tzmin = (int)(tz_secs / 60) - (tzhour * 60); } #else if (0 > tm->tm_gmtoff) { tzsign = '-'; tzhour = (int)(tm->tm_gmtoff / -3600); tzmin = (int)(tm->tm_gmtoff / -60) - (tzhour * 60); } else { tzhour = (int)(tm->tm_gmtoff / 3600); tzmin = (int)(tm->tm_gmtoff / 60) - (tzhour * 60); } #endif if (0 == nsec || 0 == out->opts->sec_prec) { if (0 == tzhour && 0 == tzmin) { sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02dZ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); dump_cstr(buf, 20, 0, 0, out); } else { sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, tzsign, tzhour, tzmin); dump_cstr(buf, 25, 0, 0, out); } } else { char format[64] = "%04d-%02d-%02dT%02d:%02d:%02d.%09ld%c%02d:%02d"; int len = 35; if (9 > out->opts->sec_prec) { format[32] = '0' + out->opts->sec_prec; len -= 9 - out->opts->sec_prec; } sprintf(buf, format, tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, nsec, tzsign, tzhour, tzmin); dump_cstr(buf, len, 0, 0, out); } } static void dump_data_strict(VALUE obj, Out out) { VALUE clas = rb_obj_class(obj); if (oj_bigdecimal_class == clas) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out); } else { raise_strict(obj); } } static void dump_data_null(VALUE obj, Out out) { VALUE clas = rb_obj_class(obj); if (oj_bigdecimal_class == clas) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out); } else { dump_nil(out); } } static void dump_data_comp(VALUE obj, int depth, Out out) { volatile VALUE o2; if (rb_respond_to(obj, oj_to_hash_id)) { volatile VALUE h = rb_funcall(obj, oj_to_hash_id, 0); if (T_HASH != rb_type(h)) { rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj))); } dump_hash(h, depth, out->opts->mode, out); } else if (rb_respond_to(obj, oj_as_json_id) && obj != (o2 = rb_funcall(obj, oj_as_json_id, 0))) { dump_val(o2, depth, out); } else if (rb_respond_to(obj, oj_to_json_id) && (!oj_rails_hack || last_obj != obj)) { volatile VALUE rs; const char *s; int len; last_obj = obj; rs = rb_funcall(obj, oj_to_json_id, 0); last_obj = Qundef; s = rb_string_value_ptr((VALUE*)&rs); len = (int)RSTRING_LEN(rs); if (out->end - out->cur <= len + 1) { grow(out, len); } memcpy(out->cur, s, len); out->cur += len; *out->cur = '\0'; } else { VALUE clas = rb_obj_class(obj); if (rb_cTime == clas) { switch (out->opts->time_format) { case RubyTime: dump_ruby_time(obj, out); break; case XmlTime: dump_xml_time(obj, out); break; case UnixTime: default: dump_time(obj, out); break; } } else if (oj_bigdecimal_class == clas) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); if (Yes == out->opts->bigdec_as_num) { dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out); } else { dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out); } } else { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out); } } } static void dump_data_obj(VALUE obj, int depth, Out out) { VALUE clas = rb_obj_class(obj); if (rb_cTime == clas) { if (out->end - out->cur <= 6) { grow(out, 6); } *out->cur++ = '{'; *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 't'; *out->cur++ = '"'; *out->cur++ = ':'; dump_time(obj, out); *out->cur++ = '}'; *out->cur = '\0'; } else { Odd odd = oj_get_odd(clas); if (0 == odd) { if (oj_bigdecimal_class == clas) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); if (Yes == out->opts->bigdec_as_num) { dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out); } else { dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out); } } else { dump_nil(out); } } else { dump_odd(obj, odd, clas, depth + 1, out); } } } static void dump_obj_comp(VALUE obj, int depth, Out out) { if (rb_respond_to(obj, oj_to_hash_id)) { volatile VALUE h = rb_funcall(obj, oj_to_hash_id, 0); if (T_HASH != rb_type(h)) { rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj))); } dump_hash(h, depth, out->opts->mode, out); } else if (rb_respond_to(obj, oj_as_json_id)) { dump_val(rb_funcall(obj, oj_as_json_id, 0), depth, out); } else if (rb_respond_to(obj, oj_to_json_id) && (!oj_rails_hack || last_obj != obj)) { volatile VALUE rs; const char *s; int len; last_obj = obj; rs = rb_funcall(obj, oj_to_json_id, 0); last_obj = Qundef; s = rb_string_value_ptr((VALUE*)&rs); len = (int)RSTRING_LEN(rs); if (out->end - out->cur <= len + 1) { grow(out, len); } memcpy(out->cur, s, len); out->cur += len; *out->cur = '\0'; } else { VALUE clas = rb_obj_class(obj); if (oj_bigdecimal_class == clas) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); if (Yes == out->opts->bigdec_as_num) { dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out); } else { dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out); } } else if (oj_datetime_class == clas || oj_date_class == clas) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); dump_cstr(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), 0, 0, out); } else { Odd odd = oj_get_odd(clas); if (0 == odd) { dump_obj_attrs(obj, 0, 0, depth, out); } else { dump_odd(obj, odd, 0, depth + 1, out); } } } *out->cur = '\0'; } inline static void dump_obj_obj(VALUE obj, int depth, Out out) { long id = check_circular(obj, out); if (0 <= id) { VALUE clas = rb_obj_class(obj); Odd odd = oj_get_odd(clas); if (0 == odd) { if (oj_bigdecimal_class == clas) { volatile VALUE rstr = rb_funcall(obj, oj_to_s_id, 0); dump_raw(rb_string_value_ptr((VALUE*)&rstr), RSTRING_LEN(rstr), out); } else { dump_obj_attrs(obj, clas, id, depth, out); } } else { dump_odd(obj, odd, clas, depth + 1, out); } } } #if HAS_IVAR_HELPERS static int dump_attr_cb(ID key, VALUE value, Out out) { int depth = out->depth; size_t size = depth * out->indent + 1; const char *attr = rb_id2name(key); #if HAS_EXCEPTION_MAGIC if (0 == strcmp("bt", attr) || 0 == strcmp("mesg", attr)) { return ST_CONTINUE; } #endif if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, depth); if ('@' == *attr) { attr++; dump_cstr(attr, strlen(attr), 0, 0, out); } else { char buf[32]; *buf = '~'; strncpy(buf + 1, attr, sizeof(buf) - 2); buf[sizeof(buf) - 1] = '\0'; dump_cstr(buf, strlen(buf), 0, 0, out); } *out->cur++ = ':'; dump_val(value, depth, out); out->depth = depth; *out->cur++ = ','; return ST_CONTINUE; } #endif static void dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out) { size_t size = 0; int d2 = depth + 1; if (out->end - out->cur <= 2) { grow(out, 2); } *out->cur++ = '{'; if (0 != clas) { const char *class_name = rb_class2name(clas); int clen = (int)strlen(class_name); size = d2 * out->indent + clen + 10; if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'o'; *out->cur++ = '"'; *out->cur++ = ':'; dump_cstr(class_name, clen, 0, 0, out); } if (0 < id) { size = d2 * out->indent + 16; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = ','; fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'i'; *out->cur++ = '"'; *out->cur++ = ':'; dump_ulong(id, out); } { int cnt; #if HAS_IVAR_HELPERS cnt = (int)rb_ivar_count(obj); #else volatile VALUE vars = rb_funcall2(obj, oj_instance_variables_id, 0, 0); VALUE *np = RARRAY_PTR(vars); ID vid; const char *attr; int i; cnt = (int)RARRAY_LEN(vars); #endif if (0 != clas && 0 < cnt) { *out->cur++ = ','; } out->depth = depth + 1; #if HAS_IVAR_HELPERS rb_ivar_foreach(obj, dump_attr_cb, (VALUE)out); if (',' == *(out->cur - 1)) { out->cur--; // backup to overwrite last comma } #else size = d2 * out->indent + 1; for (i = cnt; 0 < i; i--, np++) { if (out->end - out->cur <= (long)size) { grow(out, size); } vid = rb_to_id(*np); fill_indent(out, d2); attr = rb_id2name(vid); if ('@' == *attr) { attr++; dump_cstr(attr, strlen(attr), 0, 0, out); } else { char buf[32]; *buf = '~'; strncpy(buf + 1, attr, sizeof(buf) - 2); buf[sizeof(buf) - 1] = '\0'; dump_cstr(buf, strlen(attr) + 1, 0, 0, out); } *out->cur++ = ':'; dump_val(rb_ivar_get(obj, vid), d2, out); if (out->end - out->cur <= 2) { grow(out, 2); } if (1 < i) { *out->cur++ = ','; } } #endif #if HAS_EXCEPTION_MAGIC if (Qtrue == rb_obj_is_kind_of(obj, rb_eException)) { volatile VALUE rv; if (',' != *(out->cur - 1)) { *out->cur++ = ','; } // message if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d2); dump_cstr("~mesg", 5, 0, 0, out); *out->cur++ = ':'; rv = rb_funcall2(obj, rb_intern("message"), 0, 0); dump_val(rv, d2, out); if (out->end - out->cur <= 2) { grow(out, 2); } *out->cur++ = ','; // backtrace if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d2); dump_cstr("~bt", 3, 0, 0, out); *out->cur++ = ':'; rv = rb_funcall2(obj, rb_intern("backtrace"), 0, 0); dump_val(rv, d2, out); if (out->end - out->cur <= 2) { grow(out, 2); } } #endif out->depth = depth; } fill_indent(out, depth); *out->cur++ = '}'; *out->cur = '\0'; } static void dump_struct_comp(VALUE obj, int depth, Out out) { if (rb_respond_to(obj, oj_to_hash_id)) { volatile VALUE h = rb_funcall(obj, oj_to_hash_id, 0); if (T_HASH != rb_type(h)) { rb_raise(rb_eTypeError, "%s.to_hash() did not return a Hash.\n", rb_class2name(rb_obj_class(obj))); } dump_hash(h, depth, out->opts->mode, out); } else if (rb_respond_to(obj, oj_to_json_id)) { volatile VALUE rs = rb_funcall(obj, oj_to_json_id, 0); const char *s; int len; s = rb_string_value_ptr((VALUE*)&rs); len = (int)RSTRING_LEN(rs); if (out->end - out->cur <= len) { grow(out, len); } memcpy(out->cur, s, len); out->cur += len; } else { dump_struct_obj(obj, depth, out); } } static void dump_struct_obj(VALUE obj, int depth, Out out) { VALUE clas = rb_obj_class(obj); const char *class_name = rb_class2name(clas); int i; int d2 = depth + 1; int d3 = d2 + 1; size_t len = strlen(class_name); size_t size = d2 * out->indent + d3 * out->indent + 10 + len; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = '{'; fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'u'; *out->cur++ = '"'; *out->cur++ = ':'; *out->cur++ = '['; fill_indent(out, d3); *out->cur++ = '"'; memcpy(out->cur, class_name, len); out->cur += len; *out->cur++ = '"'; *out->cur++ = ','; size = d3 * out->indent + 2; #ifdef RSTRUCT_LEN { VALUE *vp; for (i = (int)RSTRUCT_LEN(obj), vp = RSTRUCT_PTR(obj); 0 < i; i--, vp++) { if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d3); dump_val(*vp, d3, out); *out->cur++ = ','; } } #else { // This is a bit risky as a struct in C ruby is not the same as a Struct // class in interpreted Ruby so length() may not be defined. int slen = FIX2INT(rb_funcall2(obj, oj_length_id, 0, 0)); for (i = 0; i < slen; i++) { if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d3); dump_val(rb_struct_aref(obj, INT2FIX(i)), d3, out); *out->cur++ = ','; } } #endif out->cur--; *out->cur++ = ']'; *out->cur++ = '}'; *out->cur = '\0'; } static void dump_odd(VALUE obj, Odd odd, VALUE clas, int depth, Out out) { ID *idp; volatile VALUE v; const char *name; size_t size; int d2 = depth + 1; if (out->end - out->cur <= 2) { grow(out, 2); } *out->cur++ = '{'; if (0 != clas) { const char *class_name = rb_class2name(clas); int clen = (int)strlen(class_name); size = d2 * out->indent + clen + 10; if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d2); *out->cur++ = '"'; *out->cur++ = '^'; *out->cur++ = 'O'; *out->cur++ = '"'; *out->cur++ = ':'; dump_cstr(class_name, clen, 0, 0, out); *out->cur++ = ','; } size = d2 * out->indent + 1; for (idp = odd->attrs; 0 != *idp; idp++) { if (out->end - out->cur <= (long)size) { grow(out, size); } name = rb_id2name(*idp); v = rb_funcall(obj, *idp, 0); fill_indent(out, d2); dump_cstr(name, strlen(name), 0, 0, out); *out->cur++ = ':'; dump_val(v, d2, out); if (out->end - out->cur <= 2) { grow(out, 2); } *out->cur++ = ','; } out->cur--; *out->cur++ = '}'; *out->cur = '\0'; } static void raise_strict(VALUE obj) { rb_raise(rb_eTypeError, "Failed to dump %s Object to JSON in strict mode.\n", rb_class2name(rb_obj_class(obj))); } static void dump_val(VALUE obj, int depth, Out out) { switch (rb_type(obj)) { case T_NIL: dump_nil(out); break; case T_TRUE: dump_true(out); break; case T_FALSE: dump_false(out); break; case T_FIXNUM: dump_fixnum(obj, out); break; case T_FLOAT: dump_float(obj, out); break; case T_BIGNUM: dump_bignum(obj, out); break; case T_STRING: switch (out->opts->mode) { case StrictMode: case NullMode: case CompatMode: dump_str_comp(obj, out); break; case ObjectMode: default: dump_str_obj(obj, out); break; } break; case T_SYMBOL: switch (out->opts->mode) { case StrictMode: raise_strict(obj); break; case NullMode: dump_nil(out); break; case CompatMode: dump_sym_comp(obj, out); break; case ObjectMode: default: dump_sym_obj(obj, out); break; } break; case T_ARRAY: dump_array(obj, depth, out); break; case T_HASH: dump_hash(obj, depth, out->opts->mode, out); break; case T_CLASS: switch (out->opts->mode) { case StrictMode: raise_strict(obj); break; case NullMode: dump_nil(out); break; case CompatMode: dump_class_comp(obj, out); break; case ObjectMode: default: dump_class_obj(obj, out); break; } break; #if (defined T_RATIONAL && defined RRATIONAL) case T_RATIONAL: #endif case T_OBJECT: switch (out->opts->mode) { case StrictMode: dump_data_strict(obj, out); break; case NullMode: dump_data_null(obj, out); break; case CompatMode: dump_obj_comp(obj, depth, out); break; case ObjectMode: default: dump_obj_obj(obj, depth, out); break; } break; case T_DATA: switch (out->opts->mode) { case StrictMode: dump_data_strict(obj, out); break; case NullMode: dump_data_null(obj, out); break; case CompatMode: dump_data_comp(obj, depth, out);break; case ObjectMode: default: dump_data_obj(obj, depth, out); break; } break; case T_STRUCT: // for Range switch (out->opts->mode) { case StrictMode: raise_strict(obj); break; case NullMode: dump_nil(out); break; case CompatMode: dump_struct_comp(obj, depth, out); break; case ObjectMode: default: dump_struct_obj(obj, depth, out); break; } break; #if (defined T_COMPLEX && defined RCOMPLEX) case T_COMPLEX: #endif case T_REGEXP: switch (out->opts->mode) { case StrictMode: raise_strict(obj); break; case NullMode: dump_nil(out); break; case CompatMode: case ObjectMode: default: dump_obj_comp(obj, depth, out); break; } break; default: rb_raise(rb_eNotImpError, "Failed to dump '%s' Object (%02x)\n", rb_class2name(rb_obj_class(obj)), rb_type(obj)); break; } } void oj_dump_obj_to_json(VALUE obj, Options copts, Out out) { if (0 == out->buf) { out->buf = ALLOC_N(char, 4096); out->end = out->buf + 4095 - BUFFER_EXTRA; // 1 less than end plus extra for possible errors out->allocated = 1; } out->cur = out->buf; out->circ_cnt = 0; out->opts = copts; out->hash_cnt = 0; if (Yes == copts->circular) { oj_cache8_new(&out->circ_cache); } out->indent = copts->indent; if (0 > oj_rails_hack) { oj_rails_hack = (rb_const_defined_at(rb_cObject, rb_intern("ActiveSupport"))); } dump_val(obj, 0, out); if (Yes == copts->circular) { oj_cache8_delete(out->circ_cache); } } void oj_write_obj_to_file(VALUE obj, const char *path, Options copts) { char buf[4096]; struct _Out out; size_t size; FILE *f; int ok; out.buf = buf; out.end = buf + sizeof(buf) - BUFFER_EXTRA; out.allocated = 0; oj_dump_obj_to_json(obj, copts, &out); size = out.cur - out.buf; if (0 == (f = fopen(path, "w"))) { if (out.allocated) { xfree(out.buf); } rb_raise(rb_eIOError, "%s\n", strerror(errno)); } ok = (size == fwrite(out.buf, 1, size, f)); if (out.allocated) { xfree(out.buf); } fclose(f); if (!ok) { int err = ferror(f); rb_raise(rb_eIOError, "Write failed. [%d:%s]\n", err, strerror(err)); } } void oj_write_obj_to_stream(VALUE obj, VALUE stream, Options copts) { char buf[4096]; struct _Out out; ssize_t size; VALUE clas = rb_obj_class(stream); #if !IS_WINDOWS VALUE s; #endif out.buf = buf; out.end = buf + sizeof(buf) - BUFFER_EXTRA; out.allocated = 0; oj_dump_obj_to_json(obj, copts, &out); size = out.cur - out.buf; if (oj_stringio_class == clas) { rb_funcall(stream, oj_write_id, 1, rb_str_new(out.buf, size)); #if !IS_WINDOWS } else if (rb_respond_to(stream, oj_fileno_id) && Qnil != (s = rb_funcall(stream, oj_fileno_id, 0))) { int fd = FIX2INT(s); if (size != write(fd, out.buf, size)) { if (out.allocated) { xfree(out.buf); } rb_raise(rb_eIOError, "Write failed. [%d:%s]\n", errno, strerror(errno)); } #endif } else if (rb_respond_to(stream, oj_write_id)) { rb_funcall(stream, oj_write_id, 1, rb_str_new(out.buf, size)); } else { if (out.allocated) { xfree(out.buf); } rb_raise(rb_eArgError, "to_stream() expected an IO Object."); } if (out.allocated) { xfree(out.buf); } } // dump leaf functions inline static void dump_chars(const char *s, size_t size, Out out) { if (out->end - out->cur <= (long)size) { grow(out, size); } memcpy(out->cur, s, size); out->cur += size; *out->cur = '\0'; } static void dump_leaf_str(Leaf leaf, Out out) { switch (leaf->value_type) { case STR_VAL: dump_cstr(leaf->str, strlen(leaf->str), 0, 0, out); break; case RUBY_VAL: dump_cstr(rb_string_value_cstr(&leaf->value), RSTRING_LEN(leaf->value), 0, 0, out); break; case COL_VAL: default: rb_raise(rb_eTypeError, "Unexpected value type %02x.\n", leaf->value_type); break; } } static void dump_leaf_fixnum(Leaf leaf, Out out) { switch (leaf->value_type) { case STR_VAL: dump_chars(leaf->str, strlen(leaf->str), out); break; case RUBY_VAL: if (T_BIGNUM == rb_type(leaf->value)) { dump_bignum(leaf->value, out); } else { dump_fixnum(leaf->value, out); } break; case COL_VAL: default: rb_raise(rb_eTypeError, "Unexpected value type %02x.\n", leaf->value_type); break; } } static void dump_leaf_float(Leaf leaf, Out out) { switch (leaf->value_type) { case STR_VAL: dump_chars(leaf->str, strlen(leaf->str), out); break; case RUBY_VAL: dump_float(leaf->value, out); break; case COL_VAL: default: rb_raise(rb_eTypeError, "Unexpected value type %02x.\n", leaf->value_type); break; } } static void dump_leaf_array(Leaf leaf, int depth, Out out) { size_t size; int d2 = depth + 1; size = 2; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = '['; if (0 == leaf->elements) { *out->cur++ = ']'; } else { Leaf first = leaf->elements->next; Leaf e = first; size = d2 * out->indent + 2; do { if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d2); dump_leaf(e, d2, out); if (e->next != first) { *out->cur++ = ','; } e = e->next; } while (e != first); size = depth * out->indent + 1; if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, depth); *out->cur++ = ']'; } *out->cur = '\0'; } static void dump_leaf_hash(Leaf leaf, int depth, Out out) { size_t size; int d2 = depth + 1; size = 2; if (out->end - out->cur <= (long)size) { grow(out, size); } *out->cur++ = '{'; if (0 == leaf->elements) { *out->cur++ = '}'; } else { Leaf first = leaf->elements->next; Leaf e = first; size = d2 * out->indent + 2; do { if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, d2); dump_cstr(e->key, strlen(e->key), 0, 0, out); *out->cur++ = ':'; dump_leaf(e, d2, out); if (e->next != first) { *out->cur++ = ','; } e = e->next; } while (e != first); size = depth * out->indent + 1; if (out->end - out->cur <= (long)size) { grow(out, size); } fill_indent(out, depth); *out->cur++ = '}'; } *out->cur = '\0'; } static void dump_leaf(Leaf leaf, int depth, Out out) { switch (leaf->type) { case T_NIL: dump_nil(out); break; case T_TRUE: dump_true(out); break; case T_FALSE: dump_false(out); break; case T_STRING: dump_leaf_str(leaf, out); break; case T_FIXNUM: dump_leaf_fixnum(leaf, out); break; case T_FLOAT: dump_leaf_float(leaf, out); break; case T_ARRAY: dump_leaf_array(leaf, depth, out); break; case T_HASH: dump_leaf_hash(leaf, depth, out); break; default: rb_raise(rb_eTypeError, "Unexpected type %02x.\n", leaf->type); break; } } void oj_dump_leaf_to_json(Leaf leaf, Options copts, Out out) { if (0 == out->buf) { out->buf = ALLOC_N(char, 4096); out->end = out->buf + 4095 - BUFFER_EXTRA; // 1 less than end plus extra for possible errors out->allocated = 1; } out->cur = out->buf; out->circ_cnt = 0; out->opts = copts; out->hash_cnt = 0; out->indent = copts->indent; dump_leaf(leaf, 0, out); } void oj_write_leaf_to_file(Leaf leaf, const char *path, Options copts) { char buf[4096]; struct _Out out; size_t size; FILE *f; out.buf = buf; out.end = buf + sizeof(buf) - BUFFER_EXTRA; out.allocated = 0; oj_dump_leaf_to_json(leaf, copts, &out); size = out.cur - out.buf; if (0 == (f = fopen(path, "w"))) { rb_raise(rb_eIOError, "%s\n", strerror(errno)); } if (size != fwrite(out.buf, 1, size, f)) { int err = ferror(f); rb_raise(rb_eIOError, "Write failed. [%d:%s]\n", err, strerror(err)); } if (out.allocated) { xfree(out.buf); } fclose(f); } // string writer functions static void key_check(StrWriter sw, const char *key) { DumpType type = sw->types[sw->depth]; if (0 == key && (ObjectNew == type || ObjectType == type)) { rb_raise(rb_eStandardError, "Can not push onto an Object without a key."); } } static void push_type(StrWriter sw, DumpType type) { if (sw->types_end <= sw->types + sw->depth + 1) { size_t size = (sw->types_end - sw->types) * 2; REALLOC_N(sw->types, char, size); sw->types_end = sw->types + size; } sw->depth++; sw->types[sw->depth] = type; } static void maybe_comma(StrWriter sw) { switch (sw->types[sw->depth]) { case ObjectNew: sw->types[sw->depth] = ObjectType; break; case ArrayNew: sw->types[sw->depth] = ArrayType; break; case ObjectType: case ArrayType: // Always have a few characters available in the out.buf. *sw->out.cur++ = ','; break; } } void oj_str_writer_push_object(StrWriter sw, const char *key) { long size; key_check(sw, key); size = sw->depth * sw->out.indent + 3; if (sw->out.end - sw->out.cur <= (long)size) { grow(&sw->out, size); } maybe_comma(sw); if (0 < sw->depth) { fill_indent(&sw->out, sw->depth); } if (0 != key) { dump_cstr(key, strlen(key), 0, 0, &sw->out); *sw->out.cur++ = ':'; } *sw->out.cur++ = '{'; push_type(sw, ObjectNew); } void oj_str_writer_push_array(StrWriter sw, const char *key) { long size; key_check(sw, key); size = sw->depth * sw->out.indent + 3; if (sw->out.end - sw->out.cur <= size) { grow(&sw->out, size); } maybe_comma(sw); if (0 < sw->depth) { fill_indent(&sw->out, sw->depth); } if (0 != key) { dump_cstr(key, strlen(key), 0, 0, &sw->out); *sw->out.cur++ = ':'; } *sw->out.cur++ = '['; push_type(sw, ArrayNew); } void oj_str_writer_push_value(StrWriter sw, VALUE val, const char *key) { long size; key_check(sw, key); size = sw->depth * sw->out.indent + 3; if (sw->out.end - sw->out.cur <= size) { grow(&sw->out, size); } maybe_comma(sw); if (0 < sw->depth) { fill_indent(&sw->out, sw->depth); } if (0 != key) { dump_cstr(key, strlen(key), 0, 0, &sw->out); *sw->out.cur++ = ':'; } dump_val(val, sw->depth, &sw->out); } void oj_str_writer_push_json(StrWriter sw, const char *json, const char *key) { long size; key_check(sw, key); size = sw->depth * sw->out.indent + 3; if (sw->out.end - sw->out.cur <= size) { grow(&sw->out, size); } maybe_comma(sw); if (0 < sw->depth) { fill_indent(&sw->out, sw->depth); } if (0 != key) { dump_cstr(key, strlen(key), 0, 0, &sw->out); *sw->out.cur++ = ':'; } dump_raw(json, strlen(json), &sw->out); } void oj_str_writer_pop(StrWriter sw) { long size; DumpType type = sw->types[sw->depth]; sw->depth--; if (0 > sw->depth) { rb_raise(rb_eStandardError, "Can not pop with no open array or object."); } size = sw->depth * sw->out.indent + 2; if (sw->out.end - sw->out.cur <= size) { grow(&sw->out, size); } fill_indent(&sw->out, sw->depth); switch (type) { case ObjectNew: case ObjectType: *sw->out.cur++ = '}'; break; case ArrayNew: case ArrayType: *sw->out.cur++ = ']'; break; } if (0 == sw->depth && 0 < sw->out.indent) { *sw->out.cur++ = '\n'; } } void oj_str_writer_pop_all(StrWriter sw) { while (0 < sw->depth) { oj_str_writer_pop(sw); } } oj-2.5.3/ext/oj/oj.h0000644000004100000410000001671712263716750014177 0ustar www-datawww-data/* oj.h * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __OJ_H__ #define __OJ_H__ #if defined(__cplusplus) extern "C" { #if 0 } /* satisfy cc-mode */ #endif #endif #define RSTRING_NOT_MODIFIED #include "ruby.h" #if HAS_ENCODING_SUPPORT #include "ruby/encoding.h" #endif #include "stdint.h" #if USE_PTHREAD_MUTEX #include #endif #include "cache8.h" #ifdef RUBINIUS_RUBY #undef T_RATIONAL #undef T_COMPLEX enum st_retval {ST_CONTINUE = 0, ST_STOP = 1, ST_DELETE = 2, ST_CHECK}; #else #if HAS_TOP_LEVEL_ST_H // Only on travis, local is where it is for all others. Seems to vary depending on the travis machine picked up. #include "st.h" #else #include "ruby/st.h" #endif #endif #include "err.h" typedef enum { Yes = 'y', No = 'n', NotSet = 0 } YesNo; typedef enum { StrictMode = 's', ObjectMode = 'o', NullMode = 'n', CompatMode = 'c' } Mode; typedef enum { UnixTime = 'u', XmlTime = 'x', RubyTime = 'r' } TimeFormat; typedef enum { JSONEsc = 'j', XSSEsc = 'x', ASCIIEsc = 'a' } Encoding; typedef enum { BigDec = 'b', FloatDec = 'f', AutoDec = 'a' } BigLoad; typedef enum { ArrayNew = 'A', ArrayType = 'a', ObjectNew = 'O', ObjectType = 'o', } DumpType; typedef enum { STRING_IO = 'c', STREAM_IO = 's', FILE_IO = 'f', } StreamWriterType; typedef struct _DumpOpts { const char *indent; const char *before_sep; const char *after_sep; const char *hash_nl; const char *array_nl; uint8_t indent_size; uint8_t before_size; uint8_t after_size; uint8_t hash_size; uint8_t array_size; } *DumpOpts; typedef struct _Options { int indent; // indention for dump, default 2 char circular; // YesNo char auto_define; // YesNo char sym_key; // YesNo char escape_mode; // Escape_Mode char mode; // Mode char class_cache; // YesNo char time_format; // TimeFormat char bigdec_as_num; // YesNo char bigdec_load; // BigLoad const char *create_id; // 0 or string size_t create_id_len; // length of create_id int sec_prec; // second precision when dumping time char allow_gc; // allow GC during parse DumpOpts dump_opts; } *Options; typedef struct _Out { char *buf; char *end; char *cur; Cache8 circ_cache; slot_t circ_cnt; int indent; int depth; // used by dump_hash Options opts; uint32_t hash_cnt; int allocated; } *Out; typedef struct _StrWriter { struct _Out out; struct _Options opts; int depth; char *types; // DumpType char *types_end; } *StrWriter; typedef struct _StreamWriter { struct _StrWriter sw; StreamWriterType type; VALUE stream; int fd; } *StreamWriter; enum { STR_VAL = 0x00, COL_VAL = 0x01, RUBY_VAL = 0x02 }; typedef struct _Leaf { struct _Leaf *next; union { const char *key; // hash key size_t index; // array index, 0 is not set }; union { char *str; // pointer to location in json string struct _Leaf *elements; // array and hash elements VALUE value; }; uint8_t type; uint8_t parent_type; uint8_t value_type; } *Leaf; extern VALUE oj_saj_parse(int argc, VALUE *argv, VALUE self); extern VALUE oj_sc_parse(int argc, VALUE *argv, VALUE self); extern VALUE oj_strict_parse(int argc, VALUE *argv, VALUE self); extern VALUE oj_compat_parse(int argc, VALUE *argv, VALUE self); extern VALUE oj_object_parse(int argc, VALUE *argv, VALUE self); extern VALUE oj_strict_parse_cstr(int argc, VALUE *argv, char *json, size_t len); extern VALUE oj_compat_parse_cstr(int argc, VALUE *argv, char *json, size_t len); extern VALUE oj_object_parse_cstr(int argc, VALUE *argv, char *json, size_t len); extern void oj_parse_options(VALUE ropts, Options copts); extern void oj_dump_obj_to_json(VALUE obj, Options copts, Out out); extern void oj_write_obj_to_file(VALUE obj, const char *path, Options copts); extern void oj_write_obj_to_stream(VALUE obj, VALUE stream, Options copts); extern void oj_dump_leaf_to_json(Leaf leaf, Options copts, Out out); extern void oj_write_leaf_to_file(Leaf leaf, const char *path, Options copts); extern void oj_str_writer_push_object(StrWriter sw, const char *key); extern void oj_str_writer_push_array(StrWriter sw, const char *key); extern void oj_str_writer_push_value(StrWriter sw, VALUE val, const char *key); extern void oj_str_writer_push_json(StrWriter sw, const char *json, const char *key); extern void oj_str_writer_pop(StrWriter sw); extern void oj_str_writer_pop_all(StrWriter sw); extern void oj_init_doc(void); extern VALUE Oj; extern struct _Options oj_default_options; #if HAS_ENCODING_SUPPORT extern rb_encoding *oj_utf8_encoding; #else extern VALUE oj_utf8_encoding; #endif extern VALUE oj_bag_class; extern VALUE oj_bigdecimal_class; extern VALUE oj_cstack_class; extern VALUE oj_date_class; extern VALUE oj_datetime_class; extern VALUE oj_doc_class; extern VALUE oj_stream_writer_class; extern VALUE oj_string_writer_class; extern VALUE oj_stringio_class; extern VALUE oj_struct_class; extern VALUE oj_time_class; extern VALUE oj_slash_string; extern ID oj_add_value_id; extern ID oj_array_append_id; extern ID oj_array_end_id; extern ID oj_array_start_id; extern ID oj_as_json_id; extern ID oj_error_id; extern ID oj_fileno_id; extern ID oj_hash_end_id; extern ID oj_hash_set_id; extern ID oj_hash_start_id; extern ID oj_iconv_id; extern ID oj_instance_variables_id; extern ID oj_json_create_id; extern ID oj_length_id; extern ID oj_new_id; extern ID oj_read_id; extern ID oj_string_id; extern ID oj_to_hash_id; extern ID oj_to_json_id; extern ID oj_to_s_id; extern ID oj_to_sym_id; extern ID oj_to_time_id; extern ID oj_tv_nsec_id; extern ID oj_tv_sec_id; extern ID oj_tv_usec_id; extern ID oj_utc_offset_id; extern ID oj_write_id; #if USE_PTHREAD_MUTEX extern pthread_mutex_t oj_cache_mutex; #elif USE_RB_MUTEX extern VALUE oj_cache_mutex; #endif #if defined(__cplusplus) #if 0 { /* satisfy cc-mode */ #endif } /* extern "C" { */ #endif #endif /* __OJ_H__ */ oj-2.5.3/ext/oj/oj.c0000644000004100000410000017721112263716750014167 0ustar www-datawww-data/* oj.c * Copyright (c) 2012, Peter Ohler * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include "oj.h" #include "parse.h" #include "hash.h" #include "odd.h" #include "encode.h" typedef struct _YesNoOpt { VALUE sym; char *attr; } *YesNoOpt; void Init_oj(); VALUE Oj = Qnil; ID oj_add_value_id; ID oj_array_append_id; ID oj_array_end_id; ID oj_array_start_id; ID oj_as_json_id; ID oj_error_id; ID oj_fileno_id; ID oj_hash_end_id; ID oj_hash_set_id; ID oj_hash_start_id; ID oj_iconv_id; ID oj_instance_variables_id; ID oj_json_create_id; ID oj_length_id; ID oj_new_id; ID oj_read_id; ID oj_string_id; ID oj_to_hash_id; ID oj_to_json_id; ID oj_to_s_id; ID oj_to_sym_id; ID oj_to_time_id; ID oj_tv_nsec_id; ID oj_tv_sec_id; ID oj_tv_usec_id; ID oj_utc_offset_id; ID oj_write_id; VALUE oj_bag_class; VALUE oj_bigdecimal_class; VALUE oj_cstack_class; VALUE oj_date_class; VALUE oj_datetime_class; VALUE oj_parse_error_class; VALUE oj_stream_writer_class; VALUE oj_string_writer_class; VALUE oj_stringio_class; VALUE oj_struct_class; VALUE oj_time_class; VALUE oj_slash_string; static VALUE allow_gc_sym; static VALUE ascii_only_sym; static VALUE ascii_sym; static VALUE auto_define_sym; static VALUE auto_sym; static VALUE bigdecimal_as_decimal_sym; static VALUE bigdecimal_load_sym; static VALUE bigdecimal_sym; static VALUE circular_sym; static VALUE class_cache_sym; static VALUE compat_sym; static VALUE create_id_sym; static VALUE escape_mode_sym; static VALUE float_sym; static VALUE indent_sym; static VALUE json_sym; static VALUE mode_sym; static VALUE null_sym; static VALUE object_sym; static VALUE ruby_sym; static VALUE sec_prec_sym; static VALUE strict_sym; static VALUE symbol_keys_sym; static VALUE time_format_sym; static VALUE unix_sym; static VALUE xmlschema_sym; static VALUE xss_safe_sym; static VALUE array_nl_sym; static VALUE create_additions_sym; static VALUE object_nl_sym; static VALUE space_before_sym; static VALUE space_sym; static VALUE symbolize_names_sym; static VALUE mimic = Qnil; #if HAS_ENCODING_SUPPORT rb_encoding *oj_utf8_encoding = 0; #else VALUE oj_utf8_encoding = Qnil; #endif #if USE_PTHREAD_MUTEX pthread_mutex_t oj_cache_mutex; #elif USE_RB_MUTEX VALUE oj_cache_mutex = Qnil; #endif static const char json_class[] = "json_class"; struct _Options oj_default_options = { 0, // indent No, // circular No, // auto_define No, // sym_key JSONEsc, // escape_mode ObjectMode, // mode Yes, // class_cache UnixTime, // time_format Yes, // bigdec_as_num AutoDec, // bigdec_load json_class, // create_id 10, // create_id_len 9, // sec_prec Yes, // allow_gc 0, // dump_opts }; static VALUE define_mimic_json(int argc, VALUE *argv, VALUE self); /* call-seq: default_options() => Hash * * Returns the default load and dump options as a Hash. The options are * - indent: [Fixnum] number of spaces to indent each element in an JSON document * - circular: [true|false|nil] support circular references while dumping * - auto_define: [true|false|nil] automatically define classes if they do not exist * - symbol_keys: [true|false|nil] use symbols instead of strings for hash keys * - escape_mode: [:json|:xss_safe|:ascii|nil] use symbols instead of strings for hash keys * - class_cache: [true|false|nil] cache classes for faster parsing (if dynamically modifying classes or reloading classes then don't use this) * - mode: [:object|:strict|:compat|:null] load and dump modes to use for JSON * - time_format: [:unix|:xmlschema|:ruby] time format when dumping in :compat mode * - bigdecimal_as_decimal: [true|false|nil] dump BigDecimal as a decimal number or as a String * - bigdecimal_load: [:bigdecimal|:float|:auto] load decimals as BigDecimal instead of as a Float. :auto pick the most precise for the number of digits. * - create_id: [String|nil] create id for json compatible object encoding, default is 'json_create' * - second_precision: [Fixnum|nil] number of digits after the decimal when dumping the seconds portion of time * - allow_gc: [true|false|nil] allow or prohibit GC during parsing, default is true (allow) * @return [Hash] all current option settings. */ static VALUE get_def_opts(VALUE self) { VALUE opts = rb_hash_new(); rb_hash_aset(opts, indent_sym, INT2FIX(oj_default_options.indent)); rb_hash_aset(opts, sec_prec_sym, INT2FIX(oj_default_options.sec_prec)); rb_hash_aset(opts, circular_sym, (Yes == oj_default_options.circular) ? Qtrue : ((No == oj_default_options.circular) ? Qfalse : Qnil)); rb_hash_aset(opts, class_cache_sym, (Yes == oj_default_options.class_cache) ? Qtrue : ((No == oj_default_options.class_cache) ? Qfalse : Qnil)); rb_hash_aset(opts, auto_define_sym, (Yes == oj_default_options.auto_define) ? Qtrue : ((No == oj_default_options.auto_define) ? Qfalse : Qnil)); rb_hash_aset(opts, symbol_keys_sym, (Yes == oj_default_options.sym_key) ? Qtrue : ((No == oj_default_options.sym_key) ? Qfalse : Qnil)); rb_hash_aset(opts, bigdecimal_as_decimal_sym, (Yes == oj_default_options.bigdec_as_num) ? Qtrue : ((No == oj_default_options.bigdec_as_num) ? Qfalse : Qnil)); rb_hash_aset(opts, allow_gc_sym, (Yes == oj_default_options.allow_gc) ? Qtrue : ((No == oj_default_options.allow_gc) ? Qfalse : Qnil)); switch (oj_default_options.mode) { case StrictMode: rb_hash_aset(opts, mode_sym, strict_sym); break; case CompatMode: rb_hash_aset(opts, mode_sym, compat_sym); break; case NullMode: rb_hash_aset(opts, mode_sym, null_sym); break; case ObjectMode: default: rb_hash_aset(opts, mode_sym, object_sym); break; } switch (oj_default_options.escape_mode) { case JSONEsc: rb_hash_aset(opts, escape_mode_sym, json_sym); break; case XSSEsc: rb_hash_aset(opts, escape_mode_sym, xss_safe_sym); break; case ASCIIEsc: rb_hash_aset(opts, escape_mode_sym, ascii_sym); break; default: rb_hash_aset(opts, escape_mode_sym, json_sym); break; } switch (oj_default_options.time_format) { case XmlTime: rb_hash_aset(opts, time_format_sym, xmlschema_sym); break; case RubyTime: rb_hash_aset(opts, time_format_sym, ruby_sym); break; case UnixTime: default: rb_hash_aset(opts, time_format_sym, unix_sym); break; } switch (oj_default_options.bigdec_load) { case BigDec: rb_hash_aset(opts, bigdecimal_load_sym, bigdecimal_sym); break; case FloatDec: rb_hash_aset(opts, bigdecimal_load_sym, float_sym); break; case AutoDec: default: rb_hash_aset(opts, bigdecimal_load_sym, auto_sym); break; } rb_hash_aset(opts, create_id_sym, (0 == oj_default_options.create_id) ? Qnil : rb_str_new2(oj_default_options.create_id)); return opts; } /* call-seq: default_options=(opts) * * Sets the default options for load and dump. * @param [Hash] opts options to change * @param [Fixnum] :indent number of spaces to indent each element in an JSON document * @param [true|false|nil] :circular support circular references while dumping * @param [true|false|nil] :auto_define automatically define classes if they do not exist * @param [true|false|nil] :symbol_keys convert hash keys to symbols * @param [true|false|nil] :class_cache cache classes for faster parsing * @param [:json|:xss_safe|:ascii|nil] :escape mode encodes all high-bit characters as * escaped sequences if :ascii, :json is standand UTF-8 JSON encoding, * and :xss_safe escapes &, <, and >, and some others. * @param [true|false|nil] :bigdecimal_as_decimal dump BigDecimal as a decimal number or as a String * @param [:bigdecimal|:float|:auto|nil] :bigdecimal_load load decimals as BigDecimal instead of as a Float. :auto pick the most precise for the number of digits. * @param [:object|:strict|:compat|:null] load and dump mode to use for JSON * :strict raises an exception when a non-supported Object is * encountered. :compat attempts to extract variable values from an * Object using to_json() or to_hash() then it walks the Object's * variables if neither is found. The :object mode ignores to_hash() * and to_json() methods and encodes variables using code internal to * the Oj gem. The :null mode ignores non-supported Objects and * replaces them with a null. * @param [:unix|:xmlschema|:ruby] time format when dumping in :compat mode * :unix decimal number denoting the number of seconds since 1/1/1970, * :xmlschema date-time format taken from XML Schema as a String, * :ruby Time.to_s formatted String * @param [String|nil] :create_id create id for json compatible object encoding * @param [Fixnum|nil] :second_precision number of digits after the decimal when dumping the seconds portion of time * @param [true|false|nil] :allow_gc allow or prohibit GC during parsing, default is true (allow) * @return [nil] */ static VALUE set_def_opts(VALUE self, VALUE opts) { struct _YesNoOpt ynos[] = { { circular_sym, &oj_default_options.circular }, { auto_define_sym, &oj_default_options.auto_define }, { symbol_keys_sym, &oj_default_options.sym_key }, { class_cache_sym, &oj_default_options.class_cache }, { bigdecimal_as_decimal_sym, &oj_default_options.bigdec_as_num }, { allow_gc_sym, &oj_default_options.allow_gc }, { Qnil, 0 } }; YesNoOpt o; VALUE v; Check_Type(opts, T_HASH); v = rb_hash_aref(opts, indent_sym); if (Qnil != v) { Check_Type(v, T_FIXNUM); oj_default_options.indent = FIX2INT(v); } v = rb_hash_aref(opts, sec_prec_sym); if (Qnil != v) { int n; Check_Type(v, T_FIXNUM); n = FIX2INT(v); if (0 > n) { n = 0; } else if (9 < n) { n = 9; } oj_default_options.sec_prec = n; } v = rb_hash_lookup(opts, mode_sym); if (Qnil == v) { // ignore } else if (object_sym == v) { oj_default_options.mode = ObjectMode; } else if (strict_sym == v) { oj_default_options.mode = StrictMode; } else if (compat_sym == v) { oj_default_options.mode = CompatMode; } else if (null_sym == v) { oj_default_options.mode = NullMode; } else { rb_raise(rb_eArgError, ":mode must be :object, :strict, :compat, or :null."); } v = rb_hash_lookup(opts, time_format_sym); if (Qnil == v) { // ignore } else if (unix_sym == v) { oj_default_options.time_format = UnixTime; } else if (xmlschema_sym == v) { oj_default_options.time_format = XmlTime; } else if (ruby_sym == v) { oj_default_options.time_format = RubyTime; } else { rb_raise(rb_eArgError, ":time_format must be :unix, :xmlschema, or :ruby."); } v = rb_hash_lookup(opts, escape_mode_sym); if (Qnil == v) { // ignore } else if (json_sym == v) { oj_default_options.escape_mode = JSONEsc; } else if (xss_safe_sym == v) { oj_default_options.escape_mode = XSSEsc; } else if (ascii_sym == v) { oj_default_options.escape_mode = ASCIIEsc; } else { rb_raise(rb_eArgError, ":encoding must be :json, :rails, or :ascii."); } v = rb_hash_lookup(opts, bigdecimal_load_sym); if (Qnil == v) { // ignore } else if (bigdecimal_sym == v || Qtrue == v) { oj_default_options.bigdec_load = BigDec; } else if (float_sym == v) { oj_default_options.bigdec_load = FloatDec; } else if (auto_sym == v || Qfalse == v) { oj_default_options.bigdec_load = AutoDec; } else { rb_raise(rb_eArgError, ":bigdecimal_load must be :bigdecimal, :float, or :auto."); } if (Qtrue == rb_funcall(opts, rb_intern("has_key?"), 1, create_id_sym)) { if (0 != oj_default_options.create_id) { if (json_class != oj_default_options.create_id) { xfree((char*)oj_default_options.create_id); } oj_default_options.create_id = 0; oj_default_options.create_id_len = 0; } v = rb_hash_lookup(opts, create_id_sym); if (Qnil != v) { size_t len = RSTRING_LEN(v) + 1; oj_default_options.create_id = ALLOC_N(char, len); strcpy((char*)oj_default_options.create_id, StringValuePtr(v)); oj_default_options.create_id_len = len - 1; } } for (o = ynos; 0 != o->attr; o++) { if (Qtrue != rb_funcall(opts, rb_intern("has_key?"), 1, o->sym)) { continue; } if (Qnil != (v = rb_hash_lookup(opts, o->sym))) { if (Qtrue == v) { *o->attr = Yes; } else if (Qfalse == v) { *o->attr = No; } else { rb_raise(rb_eArgError, "%s must be true, false, or nil.", rb_id2name(SYM2ID(o->sym))); } } } // This is here only for backwards compatibility with the original Oj. v = rb_hash_lookup(opts, ascii_only_sym); if (Qtrue == v) { oj_default_options.escape_mode = ASCIIEsc; } else if (Qfalse == v) { oj_default_options.escape_mode = JSONEsc; } return Qnil; } void oj_parse_options(VALUE ropts, Options copts) { struct _YesNoOpt ynos[] = { { circular_sym, &copts->circular }, { auto_define_sym, &copts->auto_define }, { symbol_keys_sym, &copts->sym_key }, { class_cache_sym, &copts->class_cache }, { bigdecimal_as_decimal_sym, &copts->bigdec_as_num }, { allow_gc_sym, &copts->allow_gc }, { Qnil, 0 } }; YesNoOpt o; if (rb_cHash == rb_obj_class(ropts)) { VALUE v; if (Qnil != (v = rb_hash_lookup(ropts, indent_sym))) { if (rb_cFixnum != rb_obj_class(v)) { rb_raise(rb_eArgError, ":indent must be a Fixnum."); } copts->indent = NUM2INT(v); } if (Qnil != (v = rb_hash_lookup(ropts, sec_prec_sym))) { int n; if (rb_cFixnum != rb_obj_class(v)) { rb_raise(rb_eArgError, ":second_precision must be a Fixnum."); } n = NUM2INT(v); if (0 > n) { n = 0; } else if (9 < n) { n = 9; } copts->sec_prec = n; } if (Qnil != (v = rb_hash_lookup(ropts, mode_sym))) { if (object_sym == v) { copts->mode = ObjectMode; } else if (strict_sym == v) { copts->mode = StrictMode; } else if (compat_sym == v) { copts->mode = CompatMode; } else if (null_sym == v) { copts->mode = NullMode; } else { rb_raise(rb_eArgError, ":mode must be :object, :strict, :compat, or :null."); } } if (Qnil != (v = rb_hash_lookup(ropts, time_format_sym))) { if (unix_sym == v) { copts->time_format = UnixTime; } else if (xmlschema_sym == v) { copts->time_format = XmlTime; } else if (ruby_sym == v) { copts->time_format = RubyTime; } else { rb_raise(rb_eArgError, ":time_format must be :unix, :xmlschema, or :ruby."); } } if (Qnil != (v = rb_hash_lookup(ropts, escape_mode_sym))) { if (json_sym == v) { copts->escape_mode = JSONEsc; } else if (xss_safe_sym == v) { copts->escape_mode = XSSEsc; } else if (ascii_sym == v) { copts->escape_mode = ASCIIEsc; } else { rb_raise(rb_eArgError, ":encoding must be :json, :rails, or :ascii."); } } if (Qnil != (v = rb_hash_lookup(ropts, bigdecimal_load_sym))) { if (bigdecimal_sym == v || Qtrue == v) { copts->bigdec_load = BigDec; } else if (float_sym == v) { copts->bigdec_load = FloatDec; } else if (auto_sym == v || Qfalse == v) { copts->bigdec_load = AutoDec; } else { rb_raise(rb_eArgError, ":bigdecimal_load must be :bigdecimal, :float, or :auto."); } } if (Qtrue == rb_funcall(ropts, rb_intern("has_key?"), 1, create_id_sym)) { v = rb_hash_lookup(ropts, create_id_sym); if (Qnil == v) { if (json_class != oj_default_options.create_id) { xfree((char*)oj_default_options.create_id); } copts->create_id = 0; copts->create_id_len = 0; } else if (T_STRING == rb_type(v)) { size_t len = RSTRING_LEN(v); const char *str = StringValuePtr(v); if (len != copts->create_id_len || 0 != strcmp(copts->create_id, str)) { copts->create_id = ALLOC_N(char, len + 1); strcpy((char*)copts->create_id, str); copts->create_id_len = len; } } else { rb_raise(rb_eArgError, ":create_id must be string."); } } for (o = ynos; 0 != o->attr; o++) { if (Qnil != (v = rb_hash_lookup(ropts, o->sym))) { if (Qtrue == v) { *o->attr = Yes; } else if (Qfalse == v) { *o->attr = No; } else { rb_raise(rb_eArgError, "%s must be true or false.", rb_id2name(SYM2ID(o->sym))); } } } // This is here only for backwards compatibility with the original Oj. v = rb_hash_lookup(ropts, ascii_only_sym); if (Qtrue == v) { copts->escape_mode = ASCIIEsc; } else if (Qfalse == v) { copts->escape_mode = JSONEsc; } } } /* Document-method: strict_load * call-seq: strict_load(json, options) => Hash, Array, String, Fixnum, Float, true, false, or nil * * Parses a JSON document String into an Hash, Array, String, Fixnum, Float, * true, false, or nil. It parses using a mode that is strict in that it maps * each primitive JSON type to a similar Ruby type. The :create_id is not * honored in this mode. Note that a Ruby Hash is used to represent the JSON * Object type. These two are not the same since teh JSON Object type can have * repeating entries with the same key and Ruby Hash can not. * * Raises an exception if the JSON is malformed or the classes specified are not * valid. If the input is not a valid JSON document (an empty string is not a * valid JSON document) an exception is raised. * * @param [String|IO] json JSON String or an Object that responds to read() * @param [Hash] options load options (same as default_options) */ /* Document-method: compat_load * call-seq: compat_load(json, options) => Object, Hash, Array, String, Fixnum, Float, true, false, or nil * * Parses a JSON document String into an Object, Hash, Array, String, Fixnum, * Float, true, false, or nil. It parses using a mode that is generally * compatible with other Ruby JSON parsers in that it will create objects based * on the :create_id value. It is not compatible in every way to every other * parser though as each parser has it's own variations. * * Raises an exception if the JSON is malformed or the classes specified are not * valid. If the input is not a valid JSON document (an empty string is not a * valid JSON document) an exception is raised. * * @param [String|IO] json JSON String or an Object that responds to read() * @param [Hash] options load options (same as default_options) */ /* Document-method: object_load * call-seq: object_load(json, options) => Object, Hash, Array, String, Fixnum, Float, true, false, or nil * * Parses a JSON document String into an Object, Hash, Array, String, Fixnum, * Float, true, false, or nil. In the :object mode the JSON should have been * generated by Oj.dump(). The parser will reconstitute the original marshalled * or dumped Object. The :auto_define and :circular options have meaning with * this parsing mode. * * Raises an exception if the JSON is malformed or the classes specified are not * valid. If the input is not a valid JSON document (an empty string is not a * valid JSON document) an exception is raised. * * Note: Oj is not able to automatically deserialize all classes that are a * subclass of a Ruby Exception. Only exception that take one required string * argument in the initialize() method are supported. This is an example of how * to write an Exception subclass that supports both a single string intializer * and an Exception as an argument. Additional optional arguments can be added * as well. * * The reason for this restriction has to do with a design decision on the part * of the Ruby developers. Exceptions are special Objects. They do not follow the * rules of other Objects. Exceptions have 'mesg' and a 'bt' attribute. Note that * these are not '@mesg' and '@bt'. They can not be set using the normal C or * Ruby calls. The only way I have found to set the 'mesg' attribute is through * the initializer. Unfortunately that means any subclass that provides a * different initializer can not be automatically decoded. A way around this is * to use a create function but this example shows an alternative. * * @param [String|IO] json JSON String or an Object that responds to read() * @param [Hash] options load options (same as default_options) */ /* call-seq: load(json, options) => Object, Hash, Array, String, Fixnum, Float, true, false, or nil * * Parses a JSON document String into a Object, Hash, Array, String, Fixnum, * Float, true, false, or nil according to the default mode or the mode * specified. Raises an exception if the JSON is malformed or the classes * specified are not valid. If the string input is not a valid JSON document (an * empty string is not a valid JSON document) an exception is raised. * * @param [String|IO] json JSON String or an Object that responds to read() * @param [Hash] options load options (same as default_options) */ static VALUE load(int argc, VALUE *argv, VALUE self) { Mode mode = oj_default_options.mode; if (1 > argc) { rb_raise(rb_eArgError, "Wrong number of arguments to load()."); } if (2 <= argc) { VALUE ropts = argv[1]; VALUE v; if (Qnil != (v = rb_hash_lookup(ropts, mode_sym))) { if (object_sym == v) { mode = ObjectMode; } else if (strict_sym == v) { mode = StrictMode; } else if (compat_sym == v) { mode = CompatMode; } else if (null_sym == v) { mode = NullMode; } else { rb_raise(rb_eArgError, ":mode must be :object, :strict, :compat, or :null."); } } } switch (mode) { case StrictMode: return oj_strict_parse(argc, argv, self); case NullMode: case CompatMode: return oj_compat_parse(argc, argv, self); case ObjectMode: default: break; } return oj_object_parse(argc, argv, self); } /* Document-method: load_file * call-seq: load_file(path, options) => Object, Hash, Array, String, Fixnum, Float, true, false, or nil * * Parses a JSON document String into a Object, Hash, Array, String, Fixnum, * Float, true, false, or nil according to the default mode or the mode * specified. Raises an exception if the JSON is malformed or the classes * specified are not valid. If the string input is not a valid JSON document (an * empty string is not a valid JSON document) an exception is raised. * * If the input file is not a valid JSON document (an empty file is not a valid * JSON document) an exception is raised. * * @param [String] path path to a file containing a JSON document * @param [Hash] options load options (same as default_options) */ static VALUE load_file(int argc, VALUE *argv, VALUE self) { char *path; char *json; FILE *f; unsigned long len; Mode mode = oj_default_options.mode; Check_Type(*argv, T_STRING); path = StringValuePtr(*argv); if (0 == (f = fopen(path, "r"))) { rb_raise(rb_eIOError, "%s", strerror(errno)); } fseek(f, 0, SEEK_END); len = ftell(f); json = ALLOC_N(char, len + 1); fseek(f, 0, SEEK_SET); if (len != fread(json, 1, len, f)) { xfree(json); fclose(f); rb_raise(rb_const_get_at(Oj, rb_intern("LoadError")), "Failed to read %ld bytes from %s.", len, path); } fclose(f); json[len] = '\0'; if (1 > argc) { rb_raise(rb_eArgError, "Wrong number of arguments to load()."); } if (2 <= argc) { VALUE ropts = argv[1]; VALUE v; if (Qnil != (v = rb_hash_lookup(ropts, mode_sym))) { if (object_sym == v) { mode = ObjectMode; } else if (strict_sym == v) { mode = StrictMode; } else if (compat_sym == v) { mode = CompatMode; } else if (null_sym == v) { mode = NullMode; } else { rb_raise(rb_eArgError, ":mode must be :object, :strict, :compat, or :null."); } } } // The json string is freed in the parser when it is finished with it. switch (mode) { case StrictMode: return oj_strict_parse_cstr(argc, argv, json, len); case NullMode: case CompatMode: return oj_compat_parse_cstr(argc, argv, json, len); case ObjectMode: default: break; } return oj_object_parse_cstr(argc, argv, json, len); } /* call-seq: safe_load(doc) * * Loads a JSON document in strict mode with :auto_define and :symbol_keys * turned off. This function should be safe to use with JSON received on an * unprotected public interface. * * @param [String|IO] doc JSON String or IO to load * @return [Hash|Array|String|Fixnum|Bignum|BigDecimal|nil|True|False] */ static VALUE safe_load(VALUE self, VALUE doc) { struct _ParseInfo pi; VALUE args[1]; pi.options = oj_default_options; pi.options.auto_define = No; pi.options.sym_key = No; pi.options.mode = StrictMode; oj_set_strict_callbacks(&pi); *args = doc; return oj_pi_parse(1, args, &pi, 0, 0); } /* call-seq: saj_parse(handler, io) * * Parses an IO stream or file containing a JSON document. Raises an exception * if the JSON is malformed. This is a callback parser that calls the methods in * the handler if they exist. A sample is the Oj::Saj class which can be used as * a base class for the handler. * * @param [Oj::Saj] handler responds to Oj::Saj methods * @param [IO|String] io IO Object to read from */ /* call-seq: sc_parse(handler, io) * * Parses an IO stream or file containing a JSON document. Raises an exception * if the JSON is malformed. This is a callback parser (Simple Callback Parser) * that calls the methods in the handler if they exist. A sample is the * Oj::ScHandler class which can be used as a base class for the handler. This * callback parser is slightly more efficient than the Saj callback parser and * requires less argument checking. * * @param [Oj::ScHandler] handler responds to Oj::ScHandler methods * @param [IO|String] io IO Object to read from */ /* call-seq: dump(obj, options) => json-string * * Dumps an Object (obj) to a string. * @param [Object] obj Object to serialize as an JSON document String * @param [Hash] options same as default_options */ static VALUE dump(int argc, VALUE *argv, VALUE self) { char buf[4096]; struct _Out out; struct _Options copts = oj_default_options; VALUE rstr; if (2 == argc) { oj_parse_options(argv[1], &copts); } out.buf = buf; out.end = buf + sizeof(buf) - 10; out.allocated = 0; oj_dump_obj_to_json(*argv, &copts, &out); if (0 == out.buf) { rb_raise(rb_eNoMemError, "Not enough memory."); } rstr = rb_str_new2(out.buf); rstr = oj_encode(rstr); if (out.allocated) { xfree(out.buf); } return rstr; } /* call-seq: to_file(file_path, obj, options) * * Dumps an Object to the specified file. * @param [String] file_path file path to write the JSON document to * @param [Object] obj Object to serialize as an JSON document String * @param [Hash] options formating options * @param [Fixnum] :indent format expected * @param [true|false] :circular allow circular references, default: false */ static VALUE to_file(int argc, VALUE *argv, VALUE self) { struct _Options copts = oj_default_options; if (3 == argc) { oj_parse_options(argv[2], &copts); } Check_Type(*argv, T_STRING); oj_write_obj_to_file(argv[1], StringValuePtr(*argv), &copts); return Qnil; } /* call-seq: to_stream(io, obj, options) * * Dumps an Object to the specified IO stream. * @param [IO] io IO stream to write the JSON document to * @param [Object] obj Object to serialize as an JSON document String * @param [Hash] options formating options * @param [Fixnum] :indent format expected * @param [true|false] :circular allow circular references, default: false */ static VALUE to_stream(int argc, VALUE *argv, VALUE self) { struct _Options copts = oj_default_options; if (3 == argc) { oj_parse_options(argv[2], &copts); } oj_write_obj_to_stream(argv[1], *argv, &copts); return Qnil; } static void str_writer_free(void *ptr) { StrWriter sw; if (0 == ptr) { return; } sw = (StrWriter)ptr; xfree(sw->out.buf); xfree(sw->types); xfree(ptr); } /* Document-class: Oj::StringWriter * * Supports building a JSON document one element at a time. Build the document * by pushing values into the document. Pushing an array or an object will * create that element in the JSON document and subsequent pushes will add the * elements to that array or object until a pop() is called. When complete * calling to_s() will return the JSON document. Note tha calling to_s() before * construction is complete will return the document in it's current state. */ static void str_writer_init(StrWriter sw) { sw->opts = oj_default_options; sw->depth = 0; sw->types = ALLOC_N(char, 256); sw->types_end = sw->types + 256; *sw->types = '\0'; sw->out.buf = ALLOC_N(char, 4096); sw->out.end = sw->out.buf + 4086; sw->out.allocated = 1; sw->out.cur = sw->out.buf; *sw->out.cur = '\0'; sw->out.circ_cnt = 0; sw->out.hash_cnt = 0; sw->out.opts = &sw->opts; sw->out.indent = sw->opts.indent; sw->out.depth = 0; } /* call-seq: new(options) * * Creates a new StringWriter. * @param [Hash] options formating options */ static VALUE str_writer_new(int argc, VALUE *argv, VALUE self) { StrWriter sw = ALLOC(struct _StrWriter); str_writer_init(sw); if (1 == argc) { oj_parse_options(argv[0], &sw->opts); } sw->out.indent = sw->opts.indent; return Data_Wrap_Struct(oj_string_writer_class, 0, str_writer_free, sw); } /* call-seq: push_object(key=nil) * * Pushes an object onto the JSON document. Future pushes will be to this object * until a pop() is called. * @param [String] key the key if adding to an object in the JSON document */ static VALUE str_writer_push_object(int argc, VALUE *argv, VALUE self) { StrWriter sw = (StrWriter)DATA_PTR(self); switch (argc) { case 0: oj_str_writer_push_object(sw, 0); break; case 1: if (Qnil == argv[0]) { oj_str_writer_push_object(sw, 0); } else { rb_check_type(argv[0], T_STRING); oj_str_writer_push_object(sw, StringValuePtr(argv[0])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'."); break; } if (rb_block_given_p()) { rb_yield(Qnil); oj_str_writer_pop(sw); } return Qnil; } /* call-seq: push_array(key=nil) * * Pushes an array onto the JSON document. Future pushes will be to this object * until a pop() is called. * @param [String] key the key if adding to an object in the JSON document */ static VALUE str_writer_push_array(int argc, VALUE *argv, VALUE self) { StrWriter sw = (StrWriter)DATA_PTR(self); switch (argc) { case 0: oj_str_writer_push_array(sw, 0); break; case 1: if (Qnil == argv[0]) { oj_str_writer_push_array(sw, 0); } else { rb_check_type(argv[0], T_STRING); oj_str_writer_push_array(sw, StringValuePtr(argv[0])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'."); break; } if (rb_block_given_p()) { rb_yield(Qnil); oj_str_writer_pop(sw); } return Qnil; } /* call-seq: push_value(value, key=nil) * * Pushes a value onto the JSON document. * @param [Object] value value to add to the JSON document * @param [String] key the key if adding to an object in the JSON document */ static VALUE str_writer_push_value(int argc, VALUE *argv, VALUE self) { switch (argc) { case 1: oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, 0); break; case 2: if (Qnil == argv[1]) { oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, 0); } else { rb_check_type(argv[1], T_STRING); oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, StringValuePtr(argv[1])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_value'."); break; } return Qnil; } /* call-seq: push_json(value, key=nil) * * Pushes a string onto the JSON document. The String must be a valid JSON * encoded string. No additional checking is done to verify the validity of the * string. * @param [String] value JSON document to add to the JSON document * @param [String] key the key if adding to an object in the JSON document */ static VALUE str_writer_push_json(int argc, VALUE *argv, VALUE self) { rb_check_type(argv[0], T_STRING); switch (argc) { case 1: oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), 0); break; case 2: if (Qnil == argv[1]) { oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), 0); } else { rb_check_type(argv[1], T_STRING); oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), StringValuePtr(argv[1])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_json'."); break; } return Qnil; } /* call-seq: pop() * * Pops up a level in the JSON document closing the array or object that is * currently open. */ static VALUE str_writer_pop(VALUE self) { oj_str_writer_pop((StrWriter)DATA_PTR(self)); return Qnil; } /* call-seq: pop_all() * * Pops all level in the JSON document closing all the array or object that is * currently open. */ static VALUE str_writer_pop_all(VALUE self) { oj_str_writer_pop_all((StrWriter)DATA_PTR(self)); return Qnil; } /* call-seq: reset() * * Reset the writer back to the empty state. */ static VALUE str_writer_reset(VALUE self) { StrWriter sw = (StrWriter)DATA_PTR(self); sw->depth = 0; *sw->types = '\0'; sw->out.cur = sw->out.buf; *sw->out.cur = '\0'; return Qnil; } /* call-seq: to_s() * * Returns the JSON document string in what ever state the construction is at. */ static VALUE str_writer_to_s(VALUE self) { StrWriter sw = (StrWriter)DATA_PTR(self); VALUE rstr = rb_str_new(sw->out.buf, sw->out.cur - sw->out.buf); return oj_encode(rstr); } // StreamWriter static void stream_writer_free(void *ptr) { StreamWriter sw; if (0 == ptr) { return; } sw = (StreamWriter)ptr; xfree(sw->sw.out.buf); xfree(sw->sw.types); xfree(ptr); } static void stream_writer_write(StreamWriter sw) { ssize_t size = sw->sw.out.cur - sw->sw.out.buf; switch (sw->type) { case STRING_IO: rb_funcall(sw->stream, oj_write_id, 1, rb_str_new(sw->sw.out.buf, size)); break; case STREAM_IO: rb_funcall(sw->stream, oj_write_id, 1, rb_str_new(sw->sw.out.buf, size)); break; case FILE_IO: if (size != write(sw->fd, sw->sw.out.buf, size)) { rb_raise(rb_eIOError, "Write failed. [%d:%s]\n", errno, strerror(errno)); } break; default: rb_raise(rb_eArgError, "expected an IO Object."); } } static void stream_writer_reset_buf(StreamWriter sw) { sw->sw.out.cur = sw->sw.out.buf; *sw->sw.out.cur = '\0'; } /* call-seq: new(options) * * Creates a new StreamWriter. * @param [Hash] options formating options */ /* call-seq: new(options) * * Creates a new StreamWriter. * @param [Hash] options formating options */ static VALUE stream_writer_new(int argc, VALUE *argv, VALUE self) { StreamWriterType type = STREAM_IO; int fd = 0; VALUE stream = argv[0]; VALUE clas = rb_obj_class(stream); StreamWriter sw; #if !IS_WINDOWS VALUE s; #endif if (oj_stringio_class == clas) { type = STRING_IO; #if !IS_WINDOWS } else if (rb_respond_to(stream, oj_fileno_id) && Qnil != (s = rb_funcall(stream, oj_fileno_id, 0))) { type = FILE_IO; fd = FIX2INT(s); #endif } else if (rb_respond_to(stream, oj_write_id)) { type = STREAM_IO; } else { rb_raise(rb_eArgError, "expected an IO Object."); } sw = ALLOC(struct _StreamWriter); str_writer_init(&sw->sw); if (1 == argc) { oj_parse_options(argv[0], &sw->sw.opts); } sw->stream = stream; sw->type = type; sw->fd = fd; return Data_Wrap_Struct(oj_stream_writer_class, 0, stream_writer_free, sw); } /* call-seq: push_object(key=nil) * * Pushes an object onto the JSON document. Future pushes will be to this object * until a pop() is called. * @param [String] key the key if adding to an object in the JSON document */ static VALUE stream_writer_push_object(int argc, VALUE *argv, VALUE self) { StreamWriter sw = (StreamWriter)DATA_PTR(self); stream_writer_reset_buf(sw); switch (argc) { case 0: oj_str_writer_push_object(&sw->sw, 0); break; case 1: if (Qnil == argv[0]) { oj_str_writer_push_object(&sw->sw, 0); } else { rb_check_type(argv[0], T_STRING); oj_str_writer_push_object(&sw->sw, StringValuePtr(argv[0])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'."); break; } stream_writer_write(sw); return Qnil; } /* call-seq: push_array(key=nil) * * Pushes an array onto the JSON document. Future pushes will be to this object * until a pop() is called. * @param [String] key the key if adding to an object in the JSON document */ static VALUE stream_writer_push_array(int argc, VALUE *argv, VALUE self) { StreamWriter sw = (StreamWriter)DATA_PTR(self); stream_writer_reset_buf(sw); switch (argc) { case 0: oj_str_writer_push_array(&sw->sw, 0); break; case 1: if (Qnil == argv[0]) { oj_str_writer_push_array(&sw->sw, 0); } else { rb_check_type(argv[0], T_STRING); oj_str_writer_push_array(&sw->sw, StringValuePtr(argv[0])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_object'."); break; } stream_writer_write(sw); return Qnil; } /* call-seq: push_value(value, key=nil) * * Pushes a value onto the JSON document. * @param [Object] value value to add to the JSON document * @param [String] key the key if adding to an object in the JSON document */ static VALUE stream_writer_push_value(int argc, VALUE *argv, VALUE self) { StreamWriter sw = (StreamWriter)DATA_PTR(self); stream_writer_reset_buf(sw); switch (argc) { case 1: oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, 0); break; case 2: if (Qnil == argv[0]) { oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, 0); } else { rb_check_type(argv[1], T_STRING); oj_str_writer_push_value((StrWriter)DATA_PTR(self), *argv, StringValuePtr(argv[1])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_value'."); break; } stream_writer_write(sw); return Qnil; } /* call-seq: push_value(value, key=nil) * * Pushes a string onto the JSON document. The String must be a valid JSON * encoded string. No additional checking is done to verify the validity of the * string. * @param [Object] value value to add to the JSON document * @param [String] key the key if adding to an object in the JSON document */ static VALUE stream_writer_push_json(int argc, VALUE *argv, VALUE self) { StreamWriter sw = (StreamWriter)DATA_PTR(self); rb_check_type(argv[0], T_STRING); stream_writer_reset_buf(sw); switch (argc) { case 1: oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), 0); break; case 2: if (Qnil == argv[0]) { oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), 0); } else { rb_check_type(argv[1], T_STRING); oj_str_writer_push_json((StrWriter)DATA_PTR(self), StringValuePtr(*argv), StringValuePtr(argv[1])); } break; default: rb_raise(rb_eArgError, "Wrong number of argument to 'push_json'."); break; } stream_writer_write(sw); return Qnil; } /* call-seq: pop() * * Pops up a level in the JSON document closing the array or object that is * currently open. */ static VALUE stream_writer_pop(VALUE self) { StreamWriter sw = (StreamWriter)DATA_PTR(self); stream_writer_reset_buf(sw); oj_str_writer_pop(&sw->sw); stream_writer_write(sw); return Qnil; } /* call-seq: pop_all() * * Pops all level in the JSON document closing all the array or object that is * currently open. */ static VALUE stream_writer_pop_all(VALUE self) { StreamWriter sw = (StreamWriter)DATA_PTR(self); stream_writer_reset_buf(sw); oj_str_writer_pop_all(&sw->sw); stream_writer_write(sw); return Qnil; } // Mimic JSON section static VALUE mimic_dump(int argc, VALUE *argv, VALUE self) { char buf[4096]; struct _Out out; struct _Options copts = oj_default_options; VALUE rstr; out.buf = buf; out.end = buf + sizeof(buf) - 10; out.allocated = 0; oj_dump_obj_to_json(*argv, &copts, &out); if (0 == out.buf) { rb_raise(rb_eNoMemError, "Not enough memory."); } rstr = rb_str_new2(out.buf); rstr = oj_encode(rstr); if (2 <= argc && Qnil != argv[1]) { VALUE io = argv[1]; VALUE args[1]; *args = rstr; rb_funcall2(io, oj_write_id, 1, args); rstr = io; } if (out.allocated) { xfree(out.buf); } return rstr; } // This is the signature for the hash_foreach callback also. static int mimic_walk(VALUE key, VALUE obj, VALUE proc) { switch (rb_type(obj)) { case T_HASH: rb_hash_foreach(obj, mimic_walk, proc); break; case T_ARRAY: { VALUE *np = RARRAY_PTR(obj); size_t cnt = RARRAY_LEN(obj); for (; 0 < cnt; cnt--, np++) { mimic_walk(Qnil, *np, proc); } break; } default: break; } if (Qnil == proc) { if (rb_block_given_p()) { rb_yield(obj); } } else { #if HAS_PROC_WITH_BLOCK VALUE args[1]; *args = obj; rb_proc_call_with_block(proc, 1, args, Qnil); #else rb_raise(rb_eNotImpError, "Calling a Proc with a block not supported in this version. Use func() {|x| } syntax instead."); #endif } return ST_CONTINUE; } static VALUE mimic_load(int argc, VALUE *argv, VALUE self) { VALUE obj = load(1, argv, self); VALUE p = Qnil; if (2 <= argc) { p = argv[1]; } mimic_walk(Qnil, obj, p); return obj; } static VALUE mimic_dump_load(int argc, VALUE *argv, VALUE self) { if (1 > argc) { rb_raise(rb_eArgError, "wrong number of arguments (0 for 1)"); } else if (T_STRING == rb_type(*argv)) { return mimic_load(argc, argv, self); } else { return mimic_dump(argc, argv, self); } return Qnil; } static VALUE mimic_generate_core(int argc, VALUE *argv, Options copts) { char buf[4096]; struct _Out out; VALUE rstr; out.buf = buf; out.end = buf + sizeof(buf) - 10; out.allocated = 0; if (2 == argc && Qnil != argv[1]) { struct _DumpOpts dump_opts; VALUE ropts = argv[1]; VALUE v; memset(&dump_opts, 0, sizeof(dump_opts)); // may not be needed if (T_HASH != rb_type(ropts)) { rb_raise(rb_eArgError, "options must be a hash."); } if (Qnil != (v = rb_hash_lookup(ropts, indent_sym))) { rb_check_type(v, T_STRING); if (0 == copts->dump_opts) { copts->dump_opts = &dump_opts; } copts->dump_opts->indent = StringValuePtr(v); copts->dump_opts->indent_size = (uint8_t)strlen(copts->dump_opts->indent); } if (Qnil != (v = rb_hash_lookup(ropts, space_sym))) { rb_check_type(v, T_STRING); if (0 == copts->dump_opts) { copts->dump_opts = &dump_opts; } copts->dump_opts->after_sep = StringValuePtr(v); copts->dump_opts->after_size = (uint8_t)strlen(copts->dump_opts->after_sep); } if (Qnil != (v = rb_hash_lookup(ropts, space_before_sym))) { rb_check_type(v, T_STRING); if (0 == copts->dump_opts) { copts->dump_opts = &dump_opts; } copts->dump_opts->before_sep = StringValuePtr(v); copts->dump_opts->before_size = (uint8_t)strlen(copts->dump_opts->before_sep); } if (Qnil != (v = rb_hash_lookup(ropts, object_nl_sym))) { rb_check_type(v, T_STRING); if (0 == copts->dump_opts) { copts->dump_opts = &dump_opts; } copts->dump_opts->hash_nl = StringValuePtr(v); copts->dump_opts->hash_size = (uint8_t)strlen(copts->dump_opts->hash_nl); } if (Qnil != (v = rb_hash_lookup(ropts, array_nl_sym))) { rb_check_type(v, T_STRING); if (0 == copts->dump_opts) { copts->dump_opts = &dump_opts; } copts->dump_opts->array_nl = StringValuePtr(v); copts->dump_opts->array_size = (uint8_t)strlen(copts->dump_opts->array_nl); } // :allow_nan is not supported as Oj always allows_nan // :max_nesting is always set to 100 } oj_dump_obj_to_json(*argv, copts, &out); if (0 == out.buf) { rb_raise(rb_eNoMemError, "Not enough memory."); } rstr = rb_str_new2(out.buf); rstr = oj_encode(rstr); if (out.allocated) { xfree(out.buf); } return rstr; } static VALUE mimic_generate(int argc, VALUE *argv, VALUE self) { struct _Options copts = oj_default_options; return mimic_generate_core(argc, argv, &copts); } static VALUE mimic_pretty_generate(int argc, VALUE *argv, VALUE self) { struct _Options copts = oj_default_options; struct _DumpOpts dump_opts; dump_opts.indent = " "; dump_opts.indent_size = (uint8_t)strlen(dump_opts.indent); dump_opts.before_sep = " "; dump_opts.before_size = (uint8_t)strlen(dump_opts.before_sep); dump_opts.after_sep = " "; dump_opts.after_size = (uint8_t)strlen(dump_opts.after_sep); dump_opts.hash_nl = "\n"; dump_opts.hash_size = (uint8_t)strlen(dump_opts.hash_nl); dump_opts.array_nl = "\n"; dump_opts.array_size = (uint8_t)strlen(dump_opts.array_nl); copts.dump_opts = &dump_opts; return mimic_generate_core(argc, argv, &copts); } static VALUE mimic_parse(int argc, VALUE *argv, VALUE self) { struct _ParseInfo pi; VALUE args[1]; if (argc < 1) { rb_raise(rb_eArgError, "Wrong number of arguments to parse."); } oj_set_compat_callbacks(&pi); pi.options = oj_default_options; pi.options.auto_define = No; if (2 <= argc) { VALUE ropts = argv[1]; VALUE v; if (T_HASH != rb_type(ropts)) { rb_raise(rb_eArgError, "options must be a hash."); } if (Qnil != (v = rb_hash_lookup(ropts, symbolize_names_sym))) { pi.options.sym_key = (Qtrue == v) ? Yes : No; } if (Qnil != (v = rb_hash_lookup(ropts, create_additions_sym))) { if (Qfalse == v) { oj_set_strict_callbacks(&pi); } } // :allow_nan is not supported as Oj always allows nan // :max_nesting is ignored as Oj has not nesting limit // :object_class is always Hash // :array_class is always Array } *args = *argv; return oj_pi_parse(1, args, &pi, 0, 0); } static VALUE mimic_recurse_proc(VALUE self, VALUE obj) { rb_need_block(); mimic_walk(Qnil, obj, Qnil); return Qnil; } static VALUE no_op1(VALUE self, VALUE obj) { return Qnil; } static VALUE mimic_create_id(VALUE self, VALUE id) { Check_Type(id, T_STRING); if (0 != oj_default_options.create_id) { if (json_class != oj_default_options.create_id) { xfree((char*)oj_default_options.create_id); } oj_default_options.create_id = 0; oj_default_options.create_id_len = 0; } if (Qnil != id) { size_t len = RSTRING_LEN(id) + 1; oj_default_options.create_id = ALLOC_N(char, len); strcpy((char*)oj_default_options.create_id, StringValuePtr(id)); oj_default_options.create_id_len = len - 1; } return id; } /* Document-method: mimic_JSON * call-seq: mimic_JSON() => Module * * Creates the JSON module with methods and classes to mimic the JSON gem. After * this method is invoked calls that expect the JSON module will use Oj instead * and be faster than the original JSON. Most options that could be passed to * the JSON methods are supported. The calls to set parser or generator will not * raise an Exception but will not have any effect. The method can also be * called after the json gem is loaded. The necessary methods on the json gem * will be replaced with Oj methods. * * Note that this also sets the default options of :mode to :compat and * :encoding to :ascii. */ static VALUE define_mimic_json(int argc, VALUE *argv, VALUE self) { VALUE ext; VALUE dummy; // Either set the paths to indicate JSON has been loaded or replaces the // methods if it has been loaded. if (rb_const_defined_at(rb_cObject, rb_intern("JSON"))) { mimic = rb_const_get_at(rb_cObject, rb_intern("JSON")); } else { mimic = rb_define_module("JSON"); } if (rb_const_defined_at(mimic, rb_intern("Ext"))) { ext = rb_const_get_at(mimic, rb_intern("Ext")); } else { ext = rb_define_module_under(mimic, "Ext"); } if (!rb_const_defined_at(ext, rb_intern("Parser"))) { dummy = rb_define_class_under(ext, "Parser", rb_cObject); } if (!rb_const_defined_at(ext, rb_intern("Generator"))) { dummy = rb_define_class_under(ext, "Generator", rb_cObject); } // convince Ruby that the json gem has already been loaded dummy = rb_gv_get("$LOADED_FEATURES"); if (rb_type(dummy) == T_ARRAY) { rb_ary_push(dummy, rb_str_new2("json")); if (0 < argc) { VALUE mimic_args[1]; *mimic_args = *argv; rb_funcall2(Oj, rb_intern("mimic_loaded"), 1, mimic_args); } else { rb_funcall2(Oj, rb_intern("mimic_loaded"), 0, 0); } } dummy = rb_gv_get("$VERBOSE"); rb_gv_set("$VERBOSE", Qfalse); rb_define_module_function(mimic, "parser=", no_op1, 1); rb_define_module_function(mimic, "generator=", no_op1, 1); rb_define_module_function(mimic, "create_id=", mimic_create_id, 1); rb_define_module_function(mimic, "dump", mimic_dump, -1); rb_define_module_function(mimic, "load", mimic_load, -1); rb_define_module_function(mimic, "restore", mimic_load, -1); rb_define_module_function(mimic, "recurse_proc", mimic_recurse_proc, 1); rb_define_module_function(mimic, "[]", mimic_dump_load, -1); rb_define_module_function(mimic, "generate", mimic_generate, -1); rb_define_module_function(mimic, "fast_generate", mimic_generate, -1); rb_define_module_function(mimic, "pretty_generate", mimic_pretty_generate, -1); /* for older versions of JSON, the deprecated unparse methods */ rb_define_module_function(mimic, "unparse", mimic_generate, -1); rb_define_module_function(mimic, "fast_unparse", mimic_generate, -1); rb_define_module_function(mimic, "pretty_unparse", mimic_pretty_generate, -1); rb_define_module_function(mimic, "parse", mimic_parse, -1); rb_define_module_function(mimic, "parse!", mimic_parse, -1); rb_gv_set("$VERBOSE", dummy); array_nl_sym = ID2SYM(rb_intern("array_nl")); rb_gc_register_address(&array_nl_sym); create_additions_sym = ID2SYM(rb_intern("create_additions")); rb_gc_register_address(&create_additions_sym); object_nl_sym = ID2SYM(rb_intern("object_nl")); rb_gc_register_address(&object_nl_sym); space_before_sym = ID2SYM(rb_intern("space_before")); rb_gc_register_address(&space_before_sym); space_sym = ID2SYM(rb_intern("space")); rb_gc_register_address(&space_sym); symbolize_names_sym = ID2SYM(rb_intern("symbolize_names")); rb_gc_register_address(&symbolize_names_sym); oj_default_options.mode = CompatMode; oj_default_options.escape_mode = ASCIIEsc; return mimic; } /* extern void oj_hash_test(); static VALUE hash_test(VALUE self) { oj_hash_test(); return Qnil; } */ #if !HAS_ENCODING_SUPPORT static VALUE iconv_encoder(VALUE x) { VALUE iconv; rb_require("iconv"); iconv = rb_const_get(rb_cObject, rb_intern("Iconv")); return rb_funcall(iconv, rb_intern("new"), 2, rb_str_new2("ASCII//TRANSLIT"), rb_str_new2("UTF-8")); } static VALUE iconv_rescue(VALUE x) { return Qnil; } #endif static VALUE protect_require(VALUE x) { rb_require("bigdecimal"); return Qnil; } void Init_oj() { int err = 0; Oj = rb_define_module("Oj"); oj_cstack_class = rb_define_class_under(Oj, "CStack", rb_cObject); oj_string_writer_class = rb_define_class_under(Oj, "StringWriter", rb_cObject); rb_define_module_function(oj_string_writer_class, "new", str_writer_new, -1); rb_define_method(oj_string_writer_class, "push_object", str_writer_push_object, -1); rb_define_method(oj_string_writer_class, "push_array", str_writer_push_array, -1); rb_define_method(oj_string_writer_class, "push_value", str_writer_push_value, -1); rb_define_method(oj_string_writer_class, "push_json", str_writer_push_json, -1); rb_define_method(oj_string_writer_class, "pop", str_writer_pop, 0); rb_define_method(oj_string_writer_class, "pop_all", str_writer_pop_all, 0); rb_define_method(oj_string_writer_class, "reset", str_writer_reset, 0); rb_define_method(oj_string_writer_class, "to_s", str_writer_to_s, 0); oj_stream_writer_class = rb_define_class_under(Oj, "StreamWriter", rb_cObject); rb_define_module_function(oj_stream_writer_class, "new", stream_writer_new, -1); rb_define_method(oj_stream_writer_class, "push_object", stream_writer_push_object, -1); rb_define_method(oj_stream_writer_class, "push_array", stream_writer_push_array, -1); rb_define_method(oj_stream_writer_class, "push_value", stream_writer_push_value, -1); rb_define_method(oj_stream_writer_class, "push_json", stream_writer_push_json, -1); rb_define_method(oj_stream_writer_class, "pop", stream_writer_pop, 0); rb_define_method(oj_stream_writer_class, "pop_all", stream_writer_pop_all, 0); rb_require("time"); rb_require("date"); // On Rubinius the require fails but can be done from a ruby file. rb_protect(protect_require, Qnil, &err); #if NEEDS_RATIONAL rb_require("rational"); #endif rb_require("stringio"); #if HAS_ENCODING_SUPPORT oj_utf8_encoding = rb_enc_find("UTF-8"); #else // need an option to turn this on oj_utf8_encoding = rb_rescue(iconv_encoder, Qnil, iconv_rescue, Qnil); oj_utf8_encoding = Qnil; #endif //rb_define_module_function(Oj, "hash_test", hash_test, 0); rb_define_module_function(Oj, "default_options", get_def_opts, 0); rb_define_module_function(Oj, "default_options=", set_def_opts, 1); rb_define_module_function(Oj, "mimic_JSON", define_mimic_json, -1); rb_define_module_function(Oj, "load", load, -1); rb_define_module_function(Oj, "load_file", load_file, -1); rb_define_module_function(Oj, "safe_load", safe_load, 1); rb_define_module_function(Oj, "strict_load", oj_strict_parse, -1); rb_define_module_function(Oj, "compat_load", oj_compat_parse, -1); rb_define_module_function(Oj, "object_load", oj_object_parse, -1); rb_define_module_function(Oj, "dump", dump, -1); rb_define_module_function(Oj, "to_file", to_file, -1); rb_define_module_function(Oj, "to_stream", to_stream, -1); rb_define_module_function(Oj, "saj_parse", oj_saj_parse, -1); rb_define_module_function(Oj, "sc_parse", oj_sc_parse, -1); oj_add_value_id = rb_intern("add_value"); oj_array_append_id = rb_intern("array_append"); oj_array_end_id = rb_intern("array_end"); oj_array_start_id = rb_intern("array_start"); oj_as_json_id = rb_intern("as_json"); oj_error_id = rb_intern("error"); oj_fileno_id = rb_intern("fileno"); oj_hash_end_id = rb_intern("hash_end"); oj_hash_set_id = rb_intern("hash_set"); oj_hash_start_id = rb_intern("hash_start"); oj_iconv_id = rb_intern("iconv"); oj_instance_variables_id = rb_intern("instance_variables"); oj_json_create_id = rb_intern("json_create"); oj_length_id = rb_intern("length"); oj_new_id = rb_intern("new"); oj_read_id = rb_intern("read"); oj_string_id = rb_intern("string"); oj_to_hash_id = rb_intern("to_hash"); oj_to_json_id = rb_intern("to_json"); oj_to_s_id = rb_intern("to_s"); oj_to_sym_id = rb_intern("to_sym"); oj_to_time_id = rb_intern("to_time"); oj_tv_nsec_id = rb_intern("tv_nsec"); oj_tv_sec_id = rb_intern("tv_sec"); oj_tv_usec_id = rb_intern("tv_usec"); oj_utc_offset_id = rb_intern("utc_offset"); oj_write_id = rb_intern("write"); oj_bag_class = rb_const_get_at(Oj, rb_intern("Bag")); oj_bigdecimal_class = rb_const_get(rb_cObject, rb_intern("BigDecimal")); oj_date_class = rb_const_get(rb_cObject, rb_intern("Date")); oj_datetime_class = rb_const_get(rb_cObject, rb_intern("DateTime")); oj_parse_error_class = rb_const_get_at(Oj, rb_intern("ParseError")); oj_stringio_class = rb_const_get(rb_cObject, rb_intern("StringIO")); oj_struct_class = rb_const_get(rb_cObject, rb_intern("Struct")); oj_time_class = rb_const_get(rb_cObject, rb_intern("Time")); allow_gc_sym = ID2SYM(rb_intern("allow_gc")); rb_gc_register_address(&allow_gc_sym); ascii_only_sym = ID2SYM(rb_intern("ascii_only")); rb_gc_register_address(&ascii_only_sym); ascii_sym = ID2SYM(rb_intern("ascii")); rb_gc_register_address(&ascii_sym); auto_define_sym = ID2SYM(rb_intern("auto_define")); rb_gc_register_address(&auto_define_sym); auto_sym = ID2SYM(rb_intern("auto")); rb_gc_register_address(&auto_sym); bigdecimal_as_decimal_sym = ID2SYM(rb_intern("bigdecimal_as_decimal"));rb_gc_register_address(&bigdecimal_as_decimal_sym); bigdecimal_load_sym = ID2SYM(rb_intern("bigdecimal_load"));rb_gc_register_address(&bigdecimal_load_sym); bigdecimal_sym = ID2SYM(rb_intern("bigdecimal")); rb_gc_register_address(&bigdecimal_sym); circular_sym = ID2SYM(rb_intern("circular")); rb_gc_register_address(&circular_sym); class_cache_sym = ID2SYM(rb_intern("class_cache")); rb_gc_register_address(&class_cache_sym); compat_sym = ID2SYM(rb_intern("compat")); rb_gc_register_address(&compat_sym); create_id_sym = ID2SYM(rb_intern("create_id")); rb_gc_register_address(&create_id_sym); escape_mode_sym = ID2SYM(rb_intern("escape_mode")); rb_gc_register_address(&escape_mode_sym); float_sym = ID2SYM(rb_intern("float")); rb_gc_register_address(&float_sym); indent_sym = ID2SYM(rb_intern("indent")); rb_gc_register_address(&indent_sym); json_sym = ID2SYM(rb_intern("json")); rb_gc_register_address(&json_sym); mode_sym = ID2SYM(rb_intern("mode")); rb_gc_register_address(&mode_sym); null_sym = ID2SYM(rb_intern("null")); rb_gc_register_address(&null_sym); object_sym = ID2SYM(rb_intern("object")); rb_gc_register_address(&object_sym); ruby_sym = ID2SYM(rb_intern("ruby")); rb_gc_register_address(&ruby_sym); sec_prec_sym = ID2SYM(rb_intern("second_precision"));rb_gc_register_address(&sec_prec_sym); strict_sym = ID2SYM(rb_intern("strict")); rb_gc_register_address(&strict_sym); symbol_keys_sym = ID2SYM(rb_intern("symbol_keys")); rb_gc_register_address(&symbol_keys_sym); time_format_sym = ID2SYM(rb_intern("time_format")); rb_gc_register_address(&time_format_sym); unix_sym = ID2SYM(rb_intern("unix")); rb_gc_register_address(&unix_sym); xmlschema_sym = ID2SYM(rb_intern("xmlschema")); rb_gc_register_address(&xmlschema_sym); xss_safe_sym = ID2SYM(rb_intern("xss_safe")); rb_gc_register_address(&xss_safe_sym); oj_slash_string = rb_str_new2("/"); rb_gc_register_address(&oj_slash_string); oj_default_options.mode = ObjectMode; oj_hash_init(); oj_odd_init(); #if USE_PTHREAD_MUTEX pthread_mutex_init(&oj_cache_mutex, 0); #elif USE_RB_MUTEX oj_cache_mutex = rb_mutex_new(); rb_gc_register_address(&oj_cache_mutex); #endif oj_init_doc(); } // mimic JSON documentation /* Document-module: JSON * * JSON is a JSON parser. This module when defined by the Oj module is a * faster replacement for the original. */ /* Document-module: JSON::Ext * * The Ext module is a placeholder in the mimic JSON module used for * compatibility only. */ /* Document-class: JSON::Ext::Parser * * The JSON::Ext::Parser is a placeholder in the mimic JSON module used for * compatibility only. */ /* Document-class: JSON::Ext::Generator * * The JSON::Ext::Generator is a placeholder in the mimic JSON module used for * compatibility only. */ /* Document-method: create_id= * call-seq: create_id=(id) -> String * * Sets the create_id tag to look for in JSON document. That key triggers the * creation of a class with the same name. * * @param [nil|String] id new create_id * @return the id */ /* Document-method: parser= * call-seq: parser=(parser) -> nil * * Does nothing other than provide compatibiltiy. * @param [Object] parser ignored */ /* Document-method: generator= * call-seq: generator=(generator) -> nil * * Does nothing other than provide compatibiltiy. * @param [Object] generator ignored */ /* Document-method: dump * call-seq: dump(obj, anIO=nil, limit = nil) -> String * * Encodes an object as a JSON String. * * @param [Object] obj object to convert to encode as JSON * @param [IO] anIO an IO that allows writing * @param [Fixnum] limit ignored */ /* Document-method: load * call-seq: load(source, proc=nil) -> Object * * Loads a Ruby Object from a JSON source that can be either a String or an * IO. If Proc is given or a block is providedit is called with each nested * element of the loaded Object. * * @param [String|IO] source JSON source * @param [Proc] proc to yield to on each element or nil */ /* Document-method: restore * call-seq: restore(source, proc=nil) -> Object * * Loads a Ruby Object from a JSON source that can be either a String or an * IO. If Proc is given or a block is providedit is called with each nested * element of the loaded Object. * * @param [String|IO] source JSON source * @param [Proc] proc to yield to on each element or nil */ /* Document-method: recurse_proc * call-seq: recurse_proc(obj, &proc) -> nil * * Yields to the proc for every element in the obj recursivly. * * @param [Hash|Array] obj object to walk * @param [Proc] proc to yield to on each element */ /* Document-method: [] * call-seq: [](obj, opts={}) -> Object * * If the obj argument is a String then it is assumed to be a JSON String and * parsed otherwise the obj is encoded as a JSON String. * * @param [String|Hash|Array] obj object to convert * @param [Hash] opts same options as either generate or parse */ /* Document-method: generate * call-seq: generate(obj, opts=nil) -> String * * Encode obj as a JSON String. The obj argument must be a Hash, Array, or * respond to to_h or to_json. Options other than those listed such as * +:allow_nan+ or +:max_nesting+ are ignored. * * @param [Object|Hash|Array] obj object to convert to a JSON String * @param [Hash] opts options * @param [String] :indent String to use for indentation * @param [String] :space String placed after a , or : delimiter * @param [String] :space_before String placed before a : delimiter * @param [String] :object_nl String placed after a JSON object * @param [String] :array_nl String placed after a JSON array */ /* Document-method: fast_generate * call-seq: fast_generate(obj, opts=nil) -> String * Same as generate(). * @see generate */ /* Document-method: pretty_generate * call-seq: pretty_generate(obj, opts=nil) -> String * Same as generate() but with different defaults for the spacing options. * @see generate */ /* Document-method: parse * call-seq: parse(source, opts=nil) -> Object * * Parses a JSON String or IO into a Ruby Object. Options other than those * listed such as +:allow_nan+ or +:max_nesting+ are ignored. +:object_class+ and * +:array_object+ are not supported. * * @param [String|IO] source source to parse * @param [Hash] opts options * @param [true|false] :symbolize_names flag indicating JSON object keys should be Symbols instead of Strings * @param [true|false] :create_additions flag indicating a key matching +create_id+ in a JSON object should trigger the creation of Ruby Object * @see create_id= */ /* Document-method: parse! * call-seq: parse!(source, opts=nil) -> Object * Same as parse(). * @see parse */ oj-2.5.3/ext/oj/odd.h0000644000004100000410000000446712263716750014334 0ustar www-datawww-data/* odd.h * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __OJ_ODD_H__ #define __OJ_ODD_H__ #include "ruby.h" #define MAX_ODD_ARGS 10 typedef struct _Odd { const char *classname; size_t clen; VALUE clas; // Ruby class VALUE create_obj; ID create_op; int attr_cnt; const char *attr_names[MAX_ODD_ARGS]; // 0 terminated attr IDs ID attrs[MAX_ODD_ARGS]; // 0 terminated attr IDs } *Odd; typedef struct _OddArgs { Odd odd; VALUE args[MAX_ODD_ARGS]; } *OddArgs; extern void oj_odd_init(void); extern Odd oj_get_odd(VALUE clas); extern Odd oj_get_oddc(const char *classname, size_t len); extern OddArgs oj_odd_alloc_args(Odd odd); extern void oj_odd_free(OddArgs args); extern int oj_odd_set_arg(OddArgs args, const char *key, size_t klen, VALUE value); #endif /* __OJ_ODD_H__ */ oj-2.5.3/ext/oj/encode.h0000644000004100000410000000371012263716750015011 0ustar www-datawww-data/* encode.h * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __OJ_ENCODE_H__ #define __OJ_ENCODE_H__ #include "ruby.h" #if HAS_ENCODING_SUPPORT #include "ruby/encoding.h" #endif static inline VALUE oj_encode(VALUE rstr) { #if HAS_ENCODING_SUPPORT rb_enc_associate(rstr, oj_utf8_encoding); #else if (Qnil != oj_utf8_encoding) { rstr = rb_funcall(oj_utf8_encoding, oj_iconv_id, 1, rstr); } #endif return rstr; } #endif /* __OJ_ENCODE_H__ */ oj-2.5.3/ext/oj/parse.c0000644000004100000410000005120412263716750014662 0ustar www-datawww-data/* parse.c * Copyright (c) 2013, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include "oj.h" #include "parse.h" #include "buf.h" #include "val_stack.h" // Workaround in case INFINITY is not defined in math.h or if the OS is CentOS #define OJ_INFINITY (1.0/0.0) #ifdef RUBINIUS_RUBY #define NUM_MAX 0x07FFFFFF #else #define NUM_MAX (FIXNUM_MAX >> 8) #endif #define EXP_MAX 1023 #define DEC_MAX 14 static void next_non_white(ParseInfo pi) { for (; 1; pi->cur++) { switch(*pi->cur) { case ' ': case '\t': case '\f': case '\n': case '\r': break; default: return; } } } static void skip_comment(ParseInfo pi) { if ('*' == *pi->cur) { pi->cur++; for (; '\0' != *pi->cur; pi->cur++) { if ('*' == *pi->cur && '/' == *(pi->cur + 1)) { pi->cur += 2; return; } else if ('\0' == *pi->cur) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "comment not terminated"); return; } } } else if ('/' == *pi->cur) { for (; 1; pi->cur++) { switch (*pi->cur) { case '\n': case '\r': case '\f': case '\0': return; default: break; } } } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid comment format"); } } static void add_value(ParseInfo pi, VALUE rval) { Val parent = stack_peek(&pi->stack); if (0 == parent) { // simple add pi->add_value(pi, rval); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_value(pi, rval); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_VALUE: pi->hash_set_value(pi, parent->key, parent->klen, rval); if (0 != parent->key && (parent->key < pi->json || pi->cur < parent->key)) { xfree((char*)parent->key); parent->key = 0; } parent->next = NEXT_HASH_COMMA; break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: case NEXT_HASH_COMMA: case NEXT_NONE: case NEXT_ARRAY_COMMA: case NEXT_HASH_COLON: default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s", oj_stack_next_string(parent->next)); break; } } } static void read_null(ParseInfo pi) { if ('u' == *pi->cur++ && 'l' == *pi->cur++ && 'l' == *pi->cur++) { add_value(pi, Qnil); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected null"); } } static void read_true(ParseInfo pi) { if ('r' == *pi->cur++ && 'u' == *pi->cur++ && 'e' == *pi->cur++) { add_value(pi, Qtrue); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected true"); } } static void read_false(ParseInfo pi) { if ('a' == *pi->cur++ && 'l' == *pi->cur++ && 's' == *pi->cur++ && 'e' == *pi->cur++) { add_value(pi, Qfalse); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected false"); } } static uint32_t read_hex(ParseInfo pi, const char *h) { uint32_t b = 0; int i; for (i = 0; i < 4; i++, h++) { b = b << 4; if ('0' <= *h && *h <= '9') { b += *h - '0'; } else if ('A' <= *h && *h <= 'F') { b += *h - 'A' + 10; } else if ('a' <= *h && *h <= 'f') { b += *h - 'a' + 10; } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid hex character"); return 0; } } return b; } static void unicode_to_chars(ParseInfo pi, Buf buf, uint32_t code) { if (0x0000007F >= code) { buf_append(buf, (char)code); } else if (0x000007FF >= code) { buf_append(buf, 0xC0 | (code >> 6)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x0000FFFF >= code) { buf_append(buf, 0xE0 | (code >> 12)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x001FFFFF >= code) { buf_append(buf, 0xF0 | (code >> 18)); buf_append(buf, 0x80 | ((code >> 12) & 0x3F)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x03FFFFFF >= code) { buf_append(buf, 0xF8 | (code >> 24)); buf_append(buf, 0x80 | ((code >> 18) & 0x3F)); buf_append(buf, 0x80 | ((code >> 12) & 0x3F)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else if (0x7FFFFFFF >= code) { buf_append(buf, 0xFC | (code >> 30)); buf_append(buf, 0x80 | ((code >> 24) & 0x3F)); buf_append(buf, 0x80 | ((code >> 18) & 0x3F)); buf_append(buf, 0x80 | ((code >> 12) & 0x3F)); buf_append(buf, 0x80 | ((code >> 6) & 0x3F)); buf_append(buf, 0x80 | (0x3F & code)); } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid Unicode character"); } } // entered at / static void read_escaped_str(ParseInfo pi, const char *start) { struct _Buf buf; const char *s; int cnt = (int)(pi->cur - start); uint32_t code; Val parent = stack_peek(&pi->stack); buf_init(&buf); if (0 < cnt) { buf_append_string(&buf, start, cnt); } for (s = pi->cur; '"' != *s; s++) { if ('\0' == *s) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "quoted string not terminated"); buf_cleanup(&buf); return; } else if ('\\' == *s) { s++; switch (*s) { case 'n': buf_append(&buf, '\n'); break; case 'r': buf_append(&buf, '\r'); break; case 't': buf_append(&buf, '\t'); break; case 'f': buf_append(&buf, '\f'); break; case 'b': buf_append(&buf, '\b'); break; case '"': buf_append(&buf, '"'); break; case '/': buf_append(&buf, '/'); break; case '\\': buf_append(&buf, '\\'); break; case 'u': s++; if (0 == (code = read_hex(pi, s)) && err_has(&pi->err)) { buf_cleanup(&buf); return; } s += 3; if (0x0000D800 <= code && code <= 0x0000DFFF) { uint32_t c1 = (code - 0x0000D800) & 0x000003FF; uint32_t c2; s++; if ('\\' != *s || 'u' != *(s + 1)) { pi->cur = s; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid escaped character"); buf_cleanup(&buf); return; } s += 2; if (0 == (c2 = read_hex(pi, s)) && err_has(&pi->err)) { buf_cleanup(&buf); return; } s += 3; c2 = (c2 - 0x0000DC00) & 0x000003FF; code = ((c1 << 10) | c2) + 0x00010000; } unicode_to_chars(pi, &buf, code); if (err_has(&pi->err)) { buf_cleanup(&buf); return; } break; default: pi->cur = s; oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "invalid escaped character"); buf_cleanup(&buf); return; } } else { buf_append(&buf, *s); } } if (0 == parent) { pi->add_cstr(pi, buf.head, buf_len(&buf), start); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_cstr(pi, buf.head, buf_len(&buf), start); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: // key will not be between pi->json and pi->cur. parent->key = strdup(buf.head); parent->klen = buf_len(&buf); parent->k1 = *start; parent->next = NEXT_HASH_COLON; break; case NEXT_HASH_VALUE: pi->hash_set_cstr(pi, parent->key, parent->klen, buf.head, buf_len(&buf), start); if (0 != parent->key && (parent->key < pi->json || pi->cur < parent->key)) { xfree((char*)parent->key); parent->key = 0; } parent->next = NEXT_HASH_COMMA; break; case NEXT_HASH_COMMA: case NEXT_NONE: case NEXT_ARRAY_COMMA: case NEXT_HASH_COLON: default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a string", oj_stack_next_string(parent->next)); break; } } pi->cur = s + 1; buf_cleanup(&buf); } static void read_str(ParseInfo pi) { const char *str = pi->cur; Val parent = stack_peek(&pi->stack); for (; '"' != *pi->cur; pi->cur++) { if ('\0' == *pi->cur) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "quoted string not terminated"); return; } else if ('\\' == *pi->cur) { read_escaped_str(pi, str); return; } } if (0 == parent) { // simple add pi->add_cstr(pi, str, pi->cur - str, str); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_cstr(pi, str, pi->cur - str, str); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_NEW: case NEXT_HASH_KEY: parent->key = str; parent->klen = pi->cur - str; parent->k1 = *str; parent->next = NEXT_HASH_COLON; break; case NEXT_HASH_VALUE: pi->hash_set_cstr(pi, parent->key, parent->klen, str, pi->cur - str, str); if (0 != parent->key && (parent->key < pi->json || pi->cur < parent->key)) { xfree((char*)parent->key); parent->key = 0; } parent->next = NEXT_HASH_COMMA; break; case NEXT_HASH_COMMA: case NEXT_NONE: case NEXT_ARRAY_COMMA: case NEXT_HASH_COLON: default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a string", oj_stack_next_string(parent->next)); break; } } pi->cur++; // move past " } static void read_num(ParseInfo pi) { struct _NumInfo ni; Val parent = stack_peek(&pi->stack); int zero_cnt = 0; ni.str = pi->cur; ni.i = 0; ni.num = 0; ni.div = 1; ni.len = 0; ni.exp = 0; ni.dec_cnt = 0; ni.big = 0; ni.infinity = 0; ni.nan = 0; ni.neg = 0; ni.no_big = (FloatDec == pi->options.bigdec_load); if ('-' == *pi->cur) { pi->cur++; ni.neg = 1; } else if ('+' == *pi->cur) { pi->cur++; } if ('I' == *pi->cur) { if (0 != strncmp("Infinity", pi->cur, 8)) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); return; } pi->cur += 8; ni.infinity = 1; } else if ('N' == *pi->cur || 'n' == *pi->cur) { if ('a' != pi->cur[1] || ('N' != pi->cur[2] && 'n' != pi->cur[2])) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value"); return; } pi->cur += 3; ni.nan = 1; } else { for (; '0' <= *pi->cur && *pi->cur <= '9'; pi->cur++) { ni.dec_cnt++; if (ni.big) { ni.big++; } else { int d = (*pi->cur - '0'); if (0 == d) { zero_cnt++; } else { zero_cnt = 0; } ni.i = ni.i * 10 + d; if (LONG_MAX <= ni.i || DEC_MAX < ni.dec_cnt - zero_cnt) { ni.big = 1; } } } if ('.' == *pi->cur) { pi->cur++; for (; '0' <= *pi->cur && *pi->cur <= '9'; pi->cur++) { int d = (*pi->cur - '0'); if (0 == d) { zero_cnt++; } else { zero_cnt = 0; } ni.dec_cnt++; ni.num = ni.num * 10 + d; ni.div *= 10; if (LONG_MAX <= ni.div || DEC_MAX < ni.dec_cnt - zero_cnt) { ni.big = 1; } } } if ('e' == *pi->cur || 'E' == *pi->cur) { int eneg = 0; pi->cur++; if ('-' == *pi->cur) { pi->cur++; eneg = 1; } else if ('+' == *pi->cur) { pi->cur++; } for (; '0' <= *pi->cur && *pi->cur <= '9'; pi->cur++) { ni.exp = ni.exp * 10 + (*pi->cur - '0'); if (EXP_MAX <= ni.exp) { ni.big = 1; } } if (eneg) { ni.exp = -ni.exp; } } ni.dec_cnt -= zero_cnt; ni.len = pi->cur - ni.str; } if (BigDec == pi->options.bigdec_load) { ni.big = 1; } if (0 == parent) { pi->add_num(pi, &ni); } else { switch (parent->next) { case NEXT_ARRAY_NEW: case NEXT_ARRAY_ELEMENT: pi->array_append_num(pi, &ni); parent->next = NEXT_ARRAY_COMMA; break; case NEXT_HASH_VALUE: pi->hash_set_num(pi, parent->key, parent->klen, &ni); if (0 != parent->key && (parent->key < pi->json || pi->cur < parent->key)) { xfree((char*)parent->key); parent->key = 0; } parent->next = NEXT_HASH_COMMA; break; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s", oj_stack_next_string(parent->next)); break; } } } static void array_start(ParseInfo pi) { VALUE v = pi->start_array(pi); stack_push(&pi->stack, v, NEXT_ARRAY_NEW); } static void array_end(ParseInfo pi) { Val array = stack_pop(&pi->stack); if (0 == array) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected array close"); } else if (NEXT_ARRAY_COMMA != array->next && NEXT_ARRAY_NEW != array->next) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not an array close", oj_stack_next_string(array->next)); } else { pi->end_array(pi); add_value(pi, array->val); } } static void hash_start(ParseInfo pi) { volatile VALUE v = pi->start_hash(pi); stack_push(&pi->stack, v, NEXT_HASH_NEW); } static void hash_end(ParseInfo pi) { volatile Val hash = stack_peek(&pi->stack); // leave hash on stack until just before if (0 == hash) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected hash close"); } else if (NEXT_HASH_COMMA != hash->next && NEXT_HASH_NEW != hash->next) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "expected %s, not a hash close", oj_stack_next_string(hash->next)); } else { pi->end_hash(pi); stack_pop(&pi->stack); add_value(pi, hash->val); } } static void comma(ParseInfo pi) { Val parent = stack_peek(&pi->stack); if (0 == parent) { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected comma"); } else if (NEXT_ARRAY_COMMA == parent->next) { parent->next = NEXT_ARRAY_ELEMENT; } else if (NEXT_HASH_COMMA == parent->next) { parent->next = NEXT_HASH_KEY; } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected comma"); } } static void colon(ParseInfo pi) { Val parent = stack_peek(&pi->stack); if (0 != parent && NEXT_HASH_COLON == parent->next) { parent->next = NEXT_HASH_VALUE; } else { oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected colon"); } } void oj_parse2(ParseInfo pi) { pi->cur = pi->json; err_init(&pi->err); while (1) { next_non_white(pi); switch (*pi->cur++) { case '{': hash_start(pi); break; case '}': hash_end(pi); break; case ':': colon(pi); break; case '[': array_start(pi); break; case ']': array_end(pi); break; case ',': comma(pi); break; case '"': read_str(pi); break; case '+': case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'I': case 'N': pi->cur--; read_num(pi); break; case 't': read_true(pi); break; case 'f': read_false(pi); break; case 'n': if ('u' == *pi->cur) { read_null(pi); } else { pi->cur--; read_num(pi); } break; case '/': skip_comment(pi); break; case '\0': pi->cur--; return; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); return; } if (err_has(&pi->err)) { return; } } } VALUE oj_num_as_value(NumInfo ni) { VALUE rnum = Qnil; if (ni->infinity) { if (ni->neg) { rnum = rb_float_new(-OJ_INFINITY); } else { rnum = rb_float_new(OJ_INFINITY); } } else if (ni->nan) { rnum = rb_float_new(0.0/0.0); } else if (1 == ni->div && 0 == ni->exp) { // fixnum if (ni->big) { if (256 > ni->len) { char buf[256]; memcpy(buf, ni->str, ni->len); buf[ni->len] = '\0'; rnum = rb_cstr_to_inum(buf, 10, 0); } else { char *buf = ALLOC_N(char, ni->len + 1); memcpy(buf, ni->str, ni->len); buf[ni->len] = '\0'; rnum = rb_cstr_to_inum(buf, 10, 0); xfree(buf); } } else { if (ni->neg) { rnum = LONG2NUM(-ni->i); } else { rnum = LONG2NUM(ni->i); } } } else { // decimal if (ni->big) { rnum = rb_funcall(oj_bigdecimal_class, oj_new_id, 1, rb_str_new(ni->str, ni->len)); if (ni->no_big) { rnum = rb_funcall(rnum, rb_intern("to_f"), 0); } } else { double d = (double)ni->i + (double)ni->num / (double)ni->div; if (ni->neg) { d = -d; } if (0 != ni->exp) { d *= pow(10.0, ni->exp); } rnum = rb_float_new(d); } } return rnum; } void oj_set_error_at(ParseInfo pi, VALUE err_clas, const char* file, int line, const char *format, ...) { va_list ap; char msg[128]; va_start(ap, format); vsnprintf(msg, sizeof(msg) - 1, format, ap); va_end(ap); pi->err.clas = err_clas; _oj_err_set_with_location(&pi->err, err_clas, msg, pi->json, pi->cur - 1, file, line); } static VALUE protect_parse(VALUE pip) { oj_parse2((ParseInfo)pip); return Qnil; } VALUE oj_pi_parse(int argc, VALUE *argv, ParseInfo pi, char *json, size_t len) { char *buf = 0; volatile VALUE input; volatile VALUE wrapped_stack; VALUE result = Qnil; int line = 0; int free_json = 0; if (argc < 1) { rb_raise(rb_eArgError, "Wrong number of arguments to parse."); } input = argv[0]; if (2 == argc) { oj_parse_options(argv[1], &pi->options); } pi->cbc = (void*)0; if (0 != json) { pi->json = json; pi->end = json + len; free_json = 1; } else if (rb_type(input) == T_STRING) { pi->json = rb_string_value_cstr((VALUE*)&input); pi->end = pi->json + RSTRING_LEN(input); } else { VALUE clas = rb_obj_class(input); volatile VALUE s; if (oj_stringio_class == clas) { s = rb_funcall2(input, oj_string_id, 0, 0); pi->json = rb_string_value_cstr((VALUE*)&s); pi->end = pi->json + RSTRING_LEN(s); #if !IS_WINDOWS } else if (rb_respond_to(input, oj_fileno_id) && Qnil != (s = rb_funcall(input, oj_fileno_id, 0))) { int fd = FIX2INT(s); ssize_t cnt; size_t len = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); buf = ALLOC_N(char, len + 1); pi->json = buf; pi->end = buf + len; if (0 >= (cnt = read(fd, (char*)pi->json, len)) || cnt != (ssize_t)len) { if (0 != buf) { xfree(buf); } rb_raise(rb_eIOError, "failed to read from IO Object."); } ((char*)pi->json)[len] = '\0'; /* skip UTF-8 BOM if present */ if (0xEF == (uint8_t)*pi->json && 0xBB == (uint8_t)pi->json[1] && 0xBF == (uint8_t)pi->json[2]) { pi->json += 3; } #endif } else if (rb_respond_to(input, oj_read_id)) { s = rb_funcall2(input, oj_read_id, 0, 0); pi->json = rb_string_value_cstr((VALUE*)&s); pi->end = pi->json + RSTRING_LEN(s); } else { rb_raise(rb_eArgError, "strict_parse() expected a String or IO Object."); } } if (Yes == pi->options.circular) { pi->circ_array = oj_circ_array_new(); } else { pi->circ_array = 0; } if (No == pi->options.allow_gc) { rb_gc_disable(); } // GC can run at any time. When it runs any Object created by C will be // freed. We protect against this by wrapping the value stack in a ruby // data object and poviding a mark function for ruby objects on the // value stack (while it is in scope). wrapped_stack = oj_stack_init(&pi->stack); rb_protect(protect_parse, (VALUE)pi, &line); result = stack_head_val(&pi->stack); DATA_PTR(wrapped_stack) = 0; if (No == pi->options.allow_gc) { rb_gc_enable(); } // proceed with cleanup if (0 != pi->circ_array) { oj_circ_array_free(pi->circ_array); } if (0 != buf) { xfree(buf); } else if (free_json) { xfree(json); } stack_cleanup(&pi->stack); if (0 != line) { rb_jump_tag(line); } if (err_has(&pi->err)) { oj_err_raise(&pi->err); } return result; } oj-2.5.3/ext/oj/parse.h0000644000004100000410000000700512263716750014667 0ustar www-datawww-data/* parse.h * Copyright (c) 2011, Peter Ohler * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * - Neither the name of Peter Ohler nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __OJ_PARSE_H__ #define __OJ_PARSE_H__ #include #include "ruby.h" #include "oj.h" #include "val_stack.h" #include "circarray.h" typedef struct _NumInfo { int64_t i; int64_t num; int64_t div; const char *str; size_t len; long exp; int dec_cnt; int big; int infinity; int nan; int neg; int no_big; } *NumInfo; typedef struct _ParseInfo { const char *json; const char *cur; const char *end; struct _Err err; struct _Options options; void *cbc; struct _ValStack stack; CircArray circ_array; int expect_value; VALUE (*start_hash)(struct _ParseInfo *pi); void (*end_hash)(struct _ParseInfo *pi); void (*hash_set_cstr)(struct _ParseInfo *pi, const char *key, size_t klen, const char *str, size_t len, const char *orig); void (*hash_set_num)(struct _ParseInfo *pi, const char *key, size_t klen, NumInfo ni); void (*hash_set_value)(struct _ParseInfo *pi, const char *key, size_t klen, VALUE value); VALUE (*start_array)(struct _ParseInfo *pi); void (*end_array)(struct _ParseInfo *pi); void (*array_append_cstr)(struct _ParseInfo *pi, const char *str, size_t len, const char *orig); void (*array_append_num)(struct _ParseInfo *pi, NumInfo ni); void (*array_append_value)(struct _ParseInfo *pi, VALUE value); void (*add_cstr)(struct _ParseInfo *pi, const char *str, size_t len, const char *orig); void (*add_num)(struct _ParseInfo *pi, NumInfo ni); void (*add_value)(struct _ParseInfo *pi, VALUE val); } *ParseInfo; extern void oj_parse2(ParseInfo pi); extern void oj_set_error_at(ParseInfo pi, VALUE err_clas, const char* file, int line, const char *format, ...); extern VALUE oj_pi_parse(int argc, VALUE *argv, ParseInfo pi, char *json, size_t len); extern VALUE oj_num_as_value(NumInfo ni); extern void oj_set_strict_callbacks(ParseInfo pi); extern void oj_set_compat_callbacks(ParseInfo pi); #endif /* __OJ_PARSE_H__ */ oj-2.5.3/README.md0000644000004100000410000001742012263716750013455 0ustar www-datawww-data# Oj gem A fast JSON parser and Object marshaller as a Ruby gem. ## Installation gem install oj ## Documentation *Documentation*: http://www.ohler.com/oj ## Source *GitHub* *repo*: https://github.com/ohler55/oj *RubyGems* *repo*: https://rubygems.org/gems/oj Follow [@peterohler on Twitter](http://twitter.com/#!/peterohler) for announcements and news about the Oj gem. ## Build Status [![Build Status](https://secure.travis-ci.org/ohler55/oj.png?branch=master)](http://travis-ci.org/ohler55/oj) ### Current Release 2.5.3 - Added support for blocks with StringWriter ### Current Release 2.5.2 - Fixed indent problem with StringWriter so it now indents properly ### Current Release 2.5.1 - Added push_json() to the StringWriter and StreamWriter to allow raw JSON to be added to a JSON document being constructed. [Older release notes](http://www.ohler.com/dev/oj_misc/release_notes.html). ## Description Optimized JSON (Oj), as the name implies was written to provide speed optimized JSON handling. It was designed as a faster alternative to Yajl and other the common Ruby JSON parsers. So far is has achieved that at about 2 time faster than any other Ruby JSON parser and 3 or more times faster writing JSON. Oj has several dump or serialization modes which control how Objects are converted to JSON. These modes are set with the :mode option in either the default options or as one of the options to the dump() method. The default mode is the :object mode. - :strict mode will only allow the 7 basic JSON types to be serialized. Any other Object will raise and Exception. - :null mode replaces any Object that is not one of the JSON types is replaced by a JSON null. - :object mode will dump any Object as a JSON Object with keys that match the Ruby Object's variable names without the '@' character. This is the highest performance mode. - :compat mode is is the compatible with other systems. It will serialize any Object but will check to see if the Object implements a to_hash() or to_json() method. If either exists that method is used for serializing the Object. The to_hash() is more flexible and produces more consistent output so it has a preference over the to_json() method. If neither the to_json() or to_hash() methods exist then the Oj internal Object variable encoding is used. Oj is compatible with Ruby 1.8.7, 1.9.2, 1.9.3, 2.0.0, and RBX. Support for JRuby has been removed as JRuby no longer supports C extensions and there are bugs in the older versions that are not being fixed. Oj is also compatible with Rails. Just make sure the Oj gem is installed and [multi_json](https://github.com/intridea/multi_json) will pick it up and use it. Oj offers a few alternative APIs for processing JSON. The fastest one is the Oj::Doc API. The Oj::Doc API takes a completely different approach by opening a JSON document and providing calls to navigate around the JSON while it is open. With this approach JSON access can be well over 20 times faster than conventional JSON parsing. Other parsers, the Oj::Saj and the Oj::ScHandler API are callback parsers that walk the JSON document depth first and makes callbacks for each element. Both callback parser are useful when only portions of the JSON are of interest. Performance up to 20 times faster than conventional JSON are possible. The API is simple to use but does require a different approach than the conventional parse followed by access approach used by conventional JSON parsing. ## Proper Use Two settings in Oj are useful for parsing but do expose a vunerability if used from an untrusted source. Symbolizing keys can be used to cause memory to be filled up since Ruby does not garbage collect Symbols. The same is true for auto defining classes. Memory can be exhausted if too many classes are automatically defined. Auto defining is a useful feature during development and from trusted sources but it does allow too many classes to be created in the object load mode and auto defined is used with an untrusted source. The Oj.strict_load() method sets uses the most strict and safest options. It should be used by developers who find it difficult to understand the options available in Oj. The options in Oj are designed to provide flexibility to the developer. This flexibility allows Objects to be serialized and deserialized. No methods are ever called on these created Objects but that does not stop the developer from calling methods on the Objects created. As in any system, check your inputs before working with them. Taking an arbitrary String from a user and evaluating it is never a good idea from an unsecure source. The same is true for Object attributes as they are not more than Strings. Always check inputs from untrusted sources. ## Release Notes ### Simple JSON Writing and Parsing: ```ruby require 'oj' h = { 'one' => 1, 'array' => [ true, false ] } json = Oj.dump(h) # json = # { # "one":1, # "array":[ # true, # false # ] # } h2 = Oj.load(json) puts "Same? #{h == h2}" # true ``` # Links ## Performance Comparisons [Oj Strict Mode Performance](http://www.ohler.com/dev/oj_misc/performance_strict.html) compares Oj strict mode parser performance to other JSON parsers. [Oj Compat Mode Performance](http://www.ohler.com/dev/oj_misc/performance_compat.html) compares Oj compat mode parser performance to other JSON parsers. [Oj Object Mode Performance](http://www.ohler.com/dev/oj_misc/performance_object.html) compares Oj object mode parser performance to other marshallers. [Oj Callback Performance](http://www.ohler.com/dev/oj_misc/performance_callback.html) compares Oj callback parser performance to other JSON parsers. ## Links of Interest *Fast XML parser and marshaller on RubyGems*: https://rubygems.org/gems/ox *Fast XML parser and marshaller on GitHub*: https://github.com/ohler55/ox [Oj Object Encoding Format](http://www.ohler.com/dev/oj_misc/encoding_format.html) describes the OJ Object JSON encoding format. [Need for Speed](http://www.ohler.com/dev/need_for_speed/need_for_speed.html) for an overview of how Oj::Doc was designed. ### License: Copyright (c) 2012, Peter Ohler All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Peter Ohler nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.