diff options
author | Florian Frank <flori@ping.de> | 2011-01-02 23:46:25 +0100 |
---|---|---|
committer | Florian Frank <flori@ping.de> | 2011-01-02 23:46:25 +0100 |
commit | 19bf4b3ea8869e7f1391f8c9de2a5fd4e3ddbc0b (patch) | |
tree | 1bce6e88780fec52afadb60135e4a12f7f8b5456 | |
parent | 3f97501c35f00dec5dbafad8389154c67b1e4542 (diff) | |
parent | 6dc725aee27ddf514593ffafc9a98e3b20e698d3 (diff) | |
download | json-19bf4b3ea8869e7f1391f8c9de2a5fd4e3ddbc0b.tar.gz |
Merge branch 'string-matching'
Conflicts:
.gitignore
Rakefile
tests/test_json_encoding.rb
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Rakefile | 253 | ||||
-rw-r--r-- | ext/json/ext/parser/extconf.rb | 1 | ||||
-rw-r--r-- | ext/json/ext/parser/parser.c | 101 | ||||
-rw-r--r-- | ext/json/ext/parser/parser.h | 7 | ||||
-rw-r--r-- | ext/json/ext/parser/parser.rl | 73 | ||||
-rw-r--r-- | java/src/json/ext/OptionsReader.java | 15 | ||||
-rw-r--r-- | java/src/json/ext/Parser.java | 200 | ||||
-rw-r--r-- | java/src/json/ext/Parser.rl | 56 | ||||
-rw-r--r-- | lib/json/pure/parser.rb | 22 | ||||
-rw-r--r-- | tests/setup_variant.rb | 11 | ||||
-rwxr-xr-x | tests/test_json.rb | 6 | ||||
-rwxr-xr-x | tests/test_json_addition.rb | 15 | ||||
-rw-r--r-- | tests/test_json_encoding.rb | 6 | ||||
-rwxr-xr-x | tests/test_json_fixtures.rb | 6 | ||||
-rwxr-xr-x | tests/test_json_generate.rb | 6 | ||||
-rw-r--r-- | tests/test_json_string_matching.rb | 40 | ||||
-rwxr-xr-x | tests/test_json_unicode.rb | 6 |
18 files changed, 505 insertions, 321 deletions
@@ -2,3 +2,5 @@ coverage pkg .nfs.* +.idea +java/Json.iml @@ -23,11 +23,12 @@ MAKE = ENV['MAKE'] || %w[gmake make].find { |c| system(c, '-v') } PKG_NAME = 'json' PKG_TITLE = 'JSON Implementation for Ruby' PKG_VERSION = File.read('VERSION').chomp -PKG_FILES = FileList["**/*"].exclude(/CVS|pkg|tmp|coverage|Makefile|\.nfs\./).exclude(/\.(so|bundle|o|class|#{CONFIG['DLEXT']})$/) +PKG_FILES = FileList["**/*"].exclude(/CVS|pkg|tmp|coverage|Makefile|\.nfs\.|\.iml\Z/).exclude(/\.(so|bundle|o|class|#{CONFIG['DLEXT']})$/) EXT_ROOT_DIR = 'ext/json/ext' EXT_PARSER_DIR = "#{EXT_ROOT_DIR}/parser" EXT_PARSER_DL = "#{EXT_PARSER_DIR}/parser.#{CONFIG['DLEXT']}" +RAGEL_PATH = "#{EXT_PARSER_DIR}/parser.rl" EXT_PARSER_SRC = "#{EXT_PARSER_DIR}/parser.c" PKG_FILES << EXT_PARSER_SRC EXT_GENERATOR_DIR = "#{EXT_ROOT_DIR}/generator" @@ -35,6 +36,7 @@ EXT_GENERATOR_DL = "#{EXT_GENERATOR_DIR}/generator.#{CONFIG['DLEXT']}" EXT_GENERATOR_SRC = "#{EXT_GENERATOR_DIR}/generator.c" JAVA_DIR = "java/src/json/ext" +JAVA_RAGEL_PATH = "#{JAVA_DIR}/Parser.rl" JAVA_PARSER_SRC = "#{JAVA_DIR}/Parser.java" JAVA_SOURCES = FileList["#{JAVA_DIR}/*.java"] JAVA_CLASSES = [] @@ -43,7 +45,6 @@ JRUBY_GENERATOR_JAR = File.expand_path("lib/json/ext/generator.jar") RAGEL_CODEGEN = %w[rlcodegen rlgen-cd ragel].find { |c| system(c, '-v') } RAGEL_DOTGEN = %w[rlgen-dot rlgen-cd ragel].find { |c| system(c, '-v') } -RAGEL_PATH = "#{EXT_PARSER_DIR}/parser.rl" def myruby(*args, &block) @myruby ||= File.join(CONFIG['bindir'], CONFIG['ruby_install_name']) @@ -81,110 +82,6 @@ else task :install => :install_ext end -desc "Compiling extension" -task :compile_ext => [ EXT_PARSER_DL, EXT_GENERATOR_DL ] - -file EXT_PARSER_DL => EXT_PARSER_SRC do - cd EXT_PARSER_DIR do - myruby 'extconf.rb' - sh MAKE - end - cp "#{EXT_PARSER_DIR}/parser.#{CONFIG['DLEXT']}", EXT_ROOT_DIR -end - -file EXT_GENERATOR_DL => EXT_GENERATOR_SRC do - cd EXT_GENERATOR_DIR do - myruby 'extconf.rb' - sh MAKE - end - cp "#{EXT_GENERATOR_DIR}/generator.#{CONFIG['DLEXT']}", EXT_ROOT_DIR -end - -desc "Generate parser with ragel" -task :ragel => EXT_PARSER_SRC - -desc "Delete the ragel generated C source" -task :ragel_clean do - rm_rf EXT_PARSER_SRC -end - -file EXT_PARSER_SRC => RAGEL_PATH do - cd EXT_PARSER_DIR do - if RAGEL_CODEGEN == 'ragel' - sh "ragel parser.rl -G2 -o parser.c" - else - sh "ragel -x parser.rl | #{RAGEL_CODEGEN} -G2" - end - end -end - -desc "Generate diagrams of ragel parser (ps)" -task :ragel_dot_ps do - root = 'diagrams' - specs = [] - File.new(RAGEL_PATH).grep(/^\s*machine\s*(\S+);\s*$/) { specs << $1 } - for s in specs - if RAGEL_DOTGEN == 'ragel' - sh "ragel #{RAGEL_PATH} -S#{s} -p -V | dot -Tps -o#{root}/#{s}.ps" - else - sh "ragel -x #{RAGEL_PATH} -S#{s} | #{RAGEL_DOTGEN} -p|dot -Tps -o#{root}/#{s}.ps" - end - end -end - -desc "Generate diagrams of ragel parser (png)" -task :ragel_dot_png do - root = 'diagrams' - specs = [] - File.new(RAGEL_PATH).grep(/^\s*machine\s*(\S+);\s*$/) { specs << $1 } - for s in specs - if RAGEL_DOTGEN == 'ragel' - sh "ragel #{RAGEL_PATH} -S#{s} -p -V | dot -Tpng -o#{root}/#{s}.png" - else - sh "ragel -x #{RAGEL_PATH} -S#{s} | #{RAGEL_DOTGEN} -p|dot -Tpng -o#{root}/#{s}.png" - end - end -end - -desc "Generate diagrams of ragel parser" -task :ragel_dot => [ :ragel_dot_png, :ragel_dot_ps ] - -desc "Testing library (pure ruby)" -task :test_pure => :clean do - ENV['JSON'] = 'pure' - ENV['RUBYOPT'] = "-Ilib #{ENV['RUBYOPT']}" - myruby '-S', 'testrb', *Dir['tests/*.rb'] -end - -desc "Testing library (extension)" -task :test_ext => :compile_ext do - ENV['JSON'] = 'ext' - ENV['RUBYOPT'] = "-Iext:lib #{ENV['RUBYOPT']}" - myruby '-S', 'testrb', *Dir['./tests/*.rb'] -end - -desc "Benchmarking parser" -task :benchmark_parser do - ENV['RUBYOPT'] = "-Ilib:ext #{ENV['RUBYOPT']}" - myruby 'benchmarks/parser_benchmark.rb' - myruby 'benchmarks/parser2_benchmark.rb' -end - -desc "Benchmarking generator" -task :benchmark_generator do - ENV['RUBYOPT'] = "-Ilib:ext #{ENV['RUBYOPT']}" - myruby 'benchmarks/generator_benchmark.rb' - myruby 'benchmarks/generator2_benchmark.rb' -end - -desc "Benchmarking library" -task :benchmark => [ :benchmark_parser, :benchmark_generator ] - -desc "Create RDOC documentation" -task :doc => [ :version, EXT_PARSER_SRC ] do - sh "sdoc -o doc -t '#{PKG_TITLE}' -m README README lib/json.rb #{FileList['lib/json/**/*.rb']} #{EXT_PARSER_SRC} #{EXT_GENERATOR_SRC}" -end - if defined?(Gem) and defined?(Rake::GemPackageTask) spec_pure = Gem::Specification.new do |s| s.name = 'json_pure' @@ -204,7 +101,7 @@ if defined?(Gem) and defined?(Rake::GemPackageTask) s.extra_rdoc_files << 'README' s.rdoc_options << '--title' << 'JSON implemention for ruby' << '--main' << 'README' - s.test_files.concat Dir['tests/*.rb'] + s.test_files.concat Dir['./tests/test_*.rb'] s.author = "Florian Frank" s.email = "flori@ping.de" @@ -241,7 +138,7 @@ if defined?(Gem) and defined?(Rake::GemPackageTask) and defined?(Rake::Extension s.extra_rdoc_files << 'README' s.rdoc_options << '--title' << 'JSON implemention for Ruby' << '--main' << 'README' - s.test_files.concat Dir['tests/*.rb'] + s.test_files.concat Dir['./tests/test_*.rb'] s.author = "Florian Frank" s.email = "flori@ping.de" @@ -290,8 +187,19 @@ EOT end end +desc "Testing library (pure ruby)" +task :test_pure => :clean do + ENV['JSON'] = 'pure' + ENV['RUBYOPT'] = "-Ilib #{ENV['RUBYOPT']}" + myruby '-S', 'testrb', *Dir['./tests/test_*.rb'] +end + +desc "Testing library (pure ruby and extension)" +task :test => [ :test_pure, :test_ext ] + + if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' - file JAVA_PARSER_SRC => RAGEL_PATH do + file JAVA_PARSER_SRC => JAVA_RAGEL_PATH do cd JAVA_DIR do if RAGEL_CODEGEN == 'ragel' sh "ragel Parser.rl -J -o Parser.java" @@ -301,6 +209,14 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' end end + desc "Generate parser for java with ragel" + task :ragel => JAVA_PARSER_SRC + + desc "Delete the ragel generated Java source" + task :ragel_clean do + rm_rf JAVA_PARSER_SRC + end + JRUBY_JAR = File.join(Config::CONFIG["libdir"], "jruby.jar") if File.exist?(JRUBY_JAR) JAVA_SOURCES.each do |src| @@ -315,16 +231,8 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' warn "WARNING: Cannot find jruby in path => Cannot build jruby extension!" end - desc "Generate parser for java with ragel" - task :ragel_java => JAVA_PARSER_SRC - - desc "Delete the ragel generated Java source" - task :ragel_clean_java do - rm_rf JAVA_PARSER_SRC - end - desc "Compiling jruby extension" - task :compile_jruby => JAVA_CLASSES + task :compile_ext => JAVA_CLASSES desc "Package the jruby gem" task :jruby_gem => :create_jar do @@ -334,12 +242,12 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' end desc "Testing library (jruby)" - task :test_jruby => :create_jar do + task :test_ext => :create_jar do ENV['JSON'] = 'ext' - myruby '-S', 'testrb', '-Ilib', *Dir['tests/*.rb'] + myruby '-S', 'testrb', '-Ilib', *Dir['./tests/test_*.rb'] end - file JRUBY_PARSER_JAR => :compile_jruby do + file JRUBY_PARSER_JAR => :compile_ext do cd 'java/src' do parser_classes = FileList[ "json/ext/ByteListTranscoder*.class", @@ -357,7 +265,7 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' desc "Create parser jar" task :create_parser_jar => JRUBY_PARSER_JAR - file JRUBY_GENERATOR_JAR => :compile_jruby do + file JRUBY_GENERATOR_JAR => :compile_ext do cd 'java/src' do generator_classes = FileList[ "json/ext/ByteListTranscoder*.class", @@ -380,12 +288,103 @@ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'jruby' desc "Build all gems and archives for a new release of the jruby extension." task :release => [ :clean, :version, :jruby_gem ] - - desc "Testing library (jruby extension)" - task :test => :test_jruby else - desc "Testing library (pure ruby and extension)" - task :test => [ :test_pure, :test_ext ] + desc "Compiling extension" + task :compile_ext => [ EXT_PARSER_DL, EXT_GENERATOR_DL ] + + file EXT_PARSER_DL => EXT_PARSER_SRC do + cd EXT_PARSER_DIR do + myruby 'extconf.rb' + sh MAKE + end + cp "#{EXT_PARSER_DIR}/parser.#{CONFIG['DLEXT']}", EXT_ROOT_DIR + end + + file EXT_GENERATOR_DL => EXT_GENERATOR_SRC do + cd EXT_GENERATOR_DIR do + myruby 'extconf.rb' + sh MAKE + end + cp "#{EXT_GENERATOR_DIR}/generator.#{CONFIG['DLEXT']}", EXT_ROOT_DIR + end + + desc "Testing library (extension)" + task :test_ext => :compile_ext do + ENV['JSON'] = 'ext' + ENV['RUBYOPT'] = "-Iext:lib #{ENV['RUBYOPT']}" + myruby '-S', 'testrb', *Dir['./tests/test_*.rb'] + end + + desc "Benchmarking parser" + task :benchmark_parser do + ENV['RUBYOPT'] = "-Ilib:ext #{ENV['RUBYOPT']}" + myruby 'benchmarks/parser_benchmark.rb' + myruby 'benchmarks/parser2_benchmark.rb' + end + + desc "Benchmarking generator" + task :benchmark_generator do + ENV['RUBYOPT'] = "-Ilib:ext #{ENV['RUBYOPT']}" + myruby 'benchmarks/generator_benchmark.rb' + myruby 'benchmarks/generator2_benchmark.rb' + end + + desc "Benchmarking library" + task :benchmark => [ :benchmark_parser, :benchmark_generator ] + + desc "Create RDOC documentation" + task :doc => [ :version, EXT_PARSER_SRC ] do + sh "sdoc -o doc -t '#{PKG_TITLE}' -m README README lib/json.rb #{FileList['lib/json/**/*.rb']} #{EXT_PARSER_SRC} #{EXT_GENERATOR_SRC}" + end + + desc "Generate parser with ragel" + task :ragel => EXT_PARSER_SRC + + desc "Delete the ragel generated C source" + task :ragel_clean do + rm_rf EXT_PARSER_SRC + end + + file EXT_PARSER_SRC => RAGEL_PATH do + cd EXT_PARSER_DIR do + if RAGEL_CODEGEN == 'ragel' + sh "ragel parser.rl -G2 -o parser.c" + else + sh "ragel -x parser.rl | #{RAGEL_CODEGEN} -G2" + end + end + end + + desc "Generate diagrams of ragel parser (ps)" + task :ragel_dot_ps do + root = 'diagrams' + specs = [] + File.new(RAGEL_PATH).grep(/^\s*machine\s*(\S+);\s*$/) { specs << $1 } + for s in specs + if RAGEL_DOTGEN == 'ragel' + sh "ragel #{RAGEL_PATH} -S#{s} -p -V | dot -Tps -o#{root}/#{s}.ps" + else + sh "ragel -x #{RAGEL_PATH} -S#{s} | #{RAGEL_DOTGEN} -p|dot -Tps -o#{root}/#{s}.ps" + end + end + end + + desc "Generate diagrams of ragel parser (png)" + task :ragel_dot_png do + root = 'diagrams' + specs = [] + File.new(RAGEL_PATH).grep(/^\s*machine\s*(\S+);\s*$/) { specs << $1 } + for s in specs + if RAGEL_DOTGEN == 'ragel' + sh "ragel #{RAGEL_PATH} -S#{s} -p -V | dot -Tpng -o#{root}/#{s}.png" + else + sh "ragel -x #{RAGEL_PATH} -S#{s} | #{RAGEL_DOTGEN} -p|dot -Tpng -o#{root}/#{s}.png" + end + end + end + + desc "Generate diagrams of ragel parser" + task :ragel_dot => [ :ragel_dot_png, :ragel_dot_ps ] desc "Build all gems and archives for a new release of json and json_pure." task :release => [ :clean, :version, :cross, :native, :gem, ] do diff --git a/ext/json/ext/parser/extconf.rb b/ext/json/ext/parser/extconf.rb index f61fc94..d2438cd 100644 --- a/ext/json/ext/parser/extconf.rb +++ b/ext/json/ext/parser/extconf.rb @@ -12,4 +12,5 @@ if CONFIG['CC'] =~ /gcc/ end have_header("re.h") +have_header("ruby/st.h") create_makefile 'json/ext/parser' diff --git a/ext/json/ext/parser/parser.c b/ext/json/ext/parser/parser.c index a2f4601..a16d068 100644 --- a/ext/json/ext/parser/parser.c +++ b/ext/json/ext/parser/parser.c @@ -79,7 +79,7 @@ static VALUE CNaN, CInfinity, CMinusInfinity; static ID i_json_creatable_p, i_json_create, i_create_id, i_create_additions, i_chr, i_max_nesting, i_allow_nan, i_symbolize_names, i_object_class, - i_array_class, i_key_p, i_deep_const_get; + i_array_class, i_key_p, i_deep_const_get, i_match; #line 108 "parser.rl" @@ -438,11 +438,11 @@ case 26: #line 160 "parser.rl" if (cs >= JSON_object_first_final) { - if (RTEST(json->create_id)) { + if (json->create_additions) { VALUE klassname = rb_hash_aref(*result, json->create_id); if (!NIL_P(klassname)) { VALUE klass = rb_funcall(mJSON, i_deep_const_get, 1, klassname); - if RTEST(rb_funcall(klass, i_json_creatable_p, 0)) { + if (RTEST(rb_funcall(klass, i_json_creatable_p, 0))) { *result = rb_funcall(klass, i_json_create, 1, *result); } } @@ -1361,21 +1361,34 @@ static const int JSON_string_en_main = 1; #line 471 "parser.rl" +static int +match_i(VALUE regexp, VALUE klass, VALUE memo) +{ + if (regexp == Qundef) return ST_STOP; + if (RTEST(rb_funcall(klass, i_json_creatable_p, 0)) && + RTEST(rb_funcall(regexp, i_match, 1, rb_ary_entry(memo, 0)))) { + rb_ary_push(memo, klass); + return ST_STOP; + } + return ST_CONTINUE; +} + static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result) { int cs = EVIL; + VALUE match; *result = rb_str_buf_new(0); -#line 1371 "parser.c" +#line 1384 "parser.c" { cs = JSON_string_start; } -#line 479 "parser.rl" +#line 492 "parser.rl" json->memo = p; -#line 1379 "parser.c" +#line 1392 "parser.c" { if ( p == pe ) goto _test_eof; @@ -1404,13 +1417,13 @@ tr2: { *result = json_string_unescape(*result, json->memo + 1, p); if (NIL_P(*result)) { - p--; - {p++; cs = 8; goto _out;} - } else { - FORCE_UTF8(*result); - {p = (( p + 1))-1;} - } - } + p--; + {p++; cs = 8; goto _out;} + } else { + FORCE_UTF8(*result); + {p = (( p + 1))-1;} + } + } #line 468 "parser.rl" { p--; {p++; cs = 8; goto _out;} } goto st8; @@ -1418,7 +1431,7 @@ st8: if ( ++p == pe ) goto _test_eof8; case 8: -#line 1422 "parser.c" +#line 1435 "parser.c" goto st0; st3: if ( ++p == pe ) @@ -1494,7 +1507,18 @@ case 7: _out: {} } -#line 481 "parser.rl" +#line 494 "parser.rl" + + if (json->create_additions && RTEST(match = json->match)) { + VALUE klass; + VALUE memo = rb_ary_new2(2); + rb_ary_push(memo, *result); + rb_hash_foreach(match, match_i, memo); + klass = rb_ary_entry(memo, 1); + if (RTEST(klass)) { + *result = rb_funcall(klass, i_json_create, 1, *result); + } + } if (json->symbolize_names && json->parsing_name) { *result = rb_str_intern(*result); @@ -1508,7 +1532,7 @@ case 7: -#line 1512 "parser.c" +#line 1536 "parser.c" static const int JSON_start = 1; static const int JSON_first_final = 10; static const int JSON_error = 0; @@ -1516,7 +1540,7 @@ static const int JSON_error = 0; static const int JSON_en_main = 1; -#line 518 "parser.rl" +#line 542 "parser.rl" /* @@ -1634,26 +1658,25 @@ static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self) } tmp = ID2SYM(i_allow_nan); if (option_given_p(opts, tmp)) { - VALUE allow_nan = rb_hash_aref(opts, tmp); - json->allow_nan = RTEST(allow_nan) ? 1 : 0; + json->allow_nan = RTEST(rb_hash_aref(opts, tmp)) ? 1 : 0; } else { json->allow_nan = 0; } tmp = ID2SYM(i_symbolize_names); if (option_given_p(opts, tmp)) { - VALUE symbolize_names = rb_hash_aref(opts, tmp); - json->symbolize_names = RTEST(symbolize_names) ? 1 : 0; + json->symbolize_names = RTEST(rb_hash_aref(opts, tmp)) ? 1 : 0; } else { json->symbolize_names = 0; } tmp = ID2SYM(i_create_additions); if (option_given_p(opts, tmp)) { - VALUE create_additions = rb_hash_aref(opts, tmp); - if (RTEST(create_additions)) { - json->create_id = rb_funcall(mJSON, i_create_id, 0); - } else { - json->create_id = Qnil; - } + json->create_additions = RTEST(rb_hash_aref(opts, tmp)); + } else { + json->create_additions = 1; + } + tmp = ID2SYM(i_create_id); + if (option_given_p(opts, tmp)) { + json->create_id = rb_hash_aref(opts, tmp); } else { json->create_id = rb_funcall(mJSON, i_create_id, 0); } @@ -1669,10 +1692,18 @@ static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self) } else { json->array_class = Qnil; } + tmp = ID2SYM(i_match); + if (option_given_p(opts, tmp)) { + VALUE match = rb_hash_aref(opts, tmp); + json->match = RTEST(match) ? match : Qnil; + } else { + json->match = Qnil; + } } } else { json->max_nesting = 19; json->allow_nan = 0; + json->create_additions = 1; json->create_id = rb_funcall(mJSON, i_create_id, 0); json->object_class = Qnil; json->array_class = Qnil; @@ -1698,16 +1729,16 @@ static VALUE cParser_parse(VALUE self) GET_PARSER; -#line 1702 "parser.c" +#line 1733 "parser.c" { cs = JSON_start; } -#line 699 "parser.rl" +#line 730 "parser.rl" p = json->source; pe = p + json->len; -#line 1711 "parser.c" +#line 1742 "parser.c" { if ( p == pe ) goto _test_eof; @@ -1763,7 +1794,7 @@ case 5: goto st1; goto st5; tr3: -#line 507 "parser.rl" +#line 531 "parser.rl" { char *np; json->current_nesting = 1; @@ -1772,7 +1803,7 @@ tr3: } goto st10; tr4: -#line 500 "parser.rl" +#line 524 "parser.rl" { char *np; json->current_nesting = 1; @@ -1784,7 +1815,7 @@ st10: if ( ++p == pe ) goto _test_eof10; case 10: -#line 1788 "parser.c" +#line 1819 "parser.c" switch( (*p) ) { case 13: goto st10; case 32: goto st10; @@ -1841,7 +1872,7 @@ case 9: _out: {} } -#line 702 "parser.rl" +#line 733 "parser.rl" if (cs >= JSON_first_final && p == pe) { return result; @@ -1864,6 +1895,7 @@ static void JSON_mark(JSON_Parser *json) rb_gc_mark_maybe(json->create_id); rb_gc_mark_maybe(json->object_class); rb_gc_mark_maybe(json->array_class); + rb_gc_mark_maybe(json->match); } static void JSON_free(JSON_Parser *json) @@ -1916,6 +1948,7 @@ void Init_parser() i_symbolize_names = rb_intern("symbolize_names"); i_object_class = rb_intern("object_class"); i_array_class = rb_intern("array_class"); + i_match = rb_intern("match"); i_key_p = rb_intern("key?"); i_deep_const_get = rb_intern("deep_const_get"); #ifdef HAVE_RUBY_ENCODING_H diff --git a/ext/json/ext/parser/parser.h b/ext/json/ext/parser/parser.h index 688ffda..877ef67 100644 --- a/ext/json/ext/parser/parser.h +++ b/ext/json/ext/parser/parser.h @@ -13,6 +13,11 @@ #else #define FORCE_UTF8(obj) #endif +#ifdef HAVE_RUBY_ST_H +#include "ruby/st.h" +#else +#include "st.h" +#endif #define option_given_p(opts, key) RTEST(rb_funcall(opts, i_key_p, 1, key)) @@ -41,6 +46,8 @@ typedef struct JSON_ParserStruct { int symbolize_names; VALUE object_class; VALUE array_class; + int create_additions; + VALUE match; } JSON_Parser; #define GET_PARSER \ diff --git a/ext/json/ext/parser/parser.rl b/ext/json/ext/parser/parser.rl index dd07485..53f1dbe 100644 --- a/ext/json/ext/parser/parser.rl +++ b/ext/json/ext/parser/parser.rl @@ -77,7 +77,7 @@ static VALUE CNaN, CInfinity, CMinusInfinity; static ID i_json_creatable_p, i_json_create, i_create_id, i_create_additions, i_chr, i_max_nesting, i_allow_nan, i_symbolize_names, i_object_class, - i_array_class, i_key_p, i_deep_const_get; + i_array_class, i_key_p, i_deep_const_get, i_match; %%{ machine JSON_common; @@ -159,11 +159,11 @@ static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *resu %% write exec; if (cs >= JSON_object_first_final) { - if (RTEST(json->create_id)) { + if (json->create_additions) { VALUE klassname = rb_hash_aref(*result, json->create_id); if (!NIL_P(klassname)) { VALUE klass = rb_funcall(mJSON, i_deep_const_get, 1, klassname); - if RTEST(rb_funcall(klass, i_json_creatable_p, 0)) { + if (RTEST(rb_funcall(klass, i_json_creatable_p, 0))) { *result = rb_funcall(klass, i_json_create, 1, *result); } } @@ -457,28 +457,52 @@ static VALUE json_string_unescape(VALUE result, char *string, char *stringEnd) action parse_string { *result = json_string_unescape(*result, json->memo + 1, p); if (NIL_P(*result)) { - fhold; - fbreak; - } else { - FORCE_UTF8(*result); - fexec p + 1; - } - } + fhold; + fbreak; + } else { + FORCE_UTF8(*result); + fexec p + 1; + } + } action exit { fhold; fbreak; } main := '"' ((^(["\\] | 0..0x1f) | '\\'["\\/bfnrt] | '\\u'[0-9a-fA-F]{4} | '\\'^(["\\/bfnrtu]|0..0x1f))* %parse_string) '"' @exit; }%% +static int +match_i(VALUE regexp, VALUE klass, VALUE memo) +{ + if (regexp == Qundef) return ST_STOP; + if (RTEST(rb_funcall(klass, i_json_creatable_p, 0)) && + RTEST(rb_funcall(regexp, i_match, 1, rb_ary_entry(memo, 0)))) { + rb_ary_push(memo, klass); + return ST_STOP; + } + return ST_CONTINUE; +} + static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result) { int cs = EVIL; + VALUE match; *result = rb_str_buf_new(0); %% write init; json->memo = p; %% write exec; + if (json->create_additions && RTEST(match = json->match)) { + VALUE klass; + VALUE memo = rb_ary_new2(2); + rb_ary_push(memo, *result); + rb_hash_foreach(match, match_i, memo); + klass = rb_ary_entry(memo, 1); + if (RTEST(klass)) { + *result = rb_funcall(klass, i_json_create, 1, *result); + } + } + if (json->symbolize_names && json->parsing_name) { *result = rb_str_intern(*result); } @@ -632,26 +656,25 @@ static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self) } tmp = ID2SYM(i_allow_nan); if (option_given_p(opts, tmp)) { - VALUE allow_nan = rb_hash_aref(opts, tmp); - json->allow_nan = RTEST(allow_nan) ? 1 : 0; + json->allow_nan = RTEST(rb_hash_aref(opts, tmp)) ? 1 : 0; } else { json->allow_nan = 0; } tmp = ID2SYM(i_symbolize_names); if (option_given_p(opts, tmp)) { - VALUE symbolize_names = rb_hash_aref(opts, tmp); - json->symbolize_names = RTEST(symbolize_names) ? 1 : 0; + json->symbolize_names = RTEST(rb_hash_aref(opts, tmp)) ? 1 : 0; } else { json->symbolize_names = 0; } tmp = ID2SYM(i_create_additions); if (option_given_p(opts, tmp)) { - VALUE create_additions = rb_hash_aref(opts, tmp); - if (RTEST(create_additions)) { - json->create_id = rb_funcall(mJSON, i_create_id, 0); - } else { - json->create_id = Qnil; - } + json->create_additions = RTEST(rb_hash_aref(opts, tmp)); + } else { + json->create_additions = 1; + } + tmp = ID2SYM(i_create_id); + if (option_given_p(opts, tmp)) { + json->create_id = rb_hash_aref(opts, tmp); } else { json->create_id = rb_funcall(mJSON, i_create_id, 0); } @@ -667,10 +690,18 @@ static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self) } else { json->array_class = Qnil; } + tmp = ID2SYM(i_match); + if (option_given_p(opts, tmp)) { + VALUE match = rb_hash_aref(opts, tmp); + json->match = RTEST(match) ? match : Qnil; + } else { + json->match = Qnil; + } } } else { json->max_nesting = 19; json->allow_nan = 0; + json->create_additions = 1; json->create_id = rb_funcall(mJSON, i_create_id, 0); json->object_class = Qnil; json->array_class = Qnil; @@ -721,6 +752,7 @@ static void JSON_mark(JSON_Parser *json) rb_gc_mark_maybe(json->create_id); rb_gc_mark_maybe(json->object_class); rb_gc_mark_maybe(json->array_class); + rb_gc_mark_maybe(json->match); } static void JSON_free(JSON_Parser *json) @@ -773,6 +805,7 @@ void Init_parser() i_symbolize_names = rb_intern("symbolize_names"); i_object_class = rb_intern("object_class"); i_array_class = rb_intern("array_class"); + i_match = rb_intern("match"); i_key_p = rb_intern("key?"); i_deep_const_get = rb_intern("deep_const_get"); #ifdef HAVE_RUBY_ENCODING_H diff --git a/java/src/json/ext/OptionsReader.java b/java/src/json/ext/OptionsReader.java index 3bc8d5f..018ace4 100644 --- a/java/src/json/ext/OptionsReader.java +++ b/java/src/json/ext/OptionsReader.java @@ -74,15 +74,20 @@ final class OptionsReader { * converted to string */ ByteList getString(String key) { + RubyString str = getString(key, null); + return str == null ? null : str.getByteList().dup(); + } + + RubyString getString(String key, RubyString defaultValue) { IRubyObject value = get(key); - if (value == null || !value.isTrue()) return null; + if (value == null || !value.isTrue()) return defaultValue; RubyString str = value.convertToString(); RuntimeInfo info = getRuntimeInfo(); if (info.encodingsSupported() && str.encoding(context) != info.utf8) { str = (RubyString)str.encode(context, info.utf8); } - return str.getByteList().dup(); + return str; } /** @@ -105,4 +110,10 @@ final class OptionsReader { throw runtime.newTypeError(key + " option must be a subclass of " + defaultValue); } + + public RubyHash getHash(String key) { + IRubyObject value = get(key); + if (value == null || value.isNil()) return new RubyHash(runtime); + return (RubyHash) value; + } } diff --git a/java/src/json/ext/Parser.java b/java/src/json/ext/Parser.java index c8f6f3d..71e2c4a 100644 --- a/java/src/json/ext/Parser.java +++ b/java/src/json/ext/Parser.java @@ -20,6 +20,7 @@ import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; +import org.jruby.exceptions.JumpException; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.Block; import org.jruby.runtime.ObjectAllocator; @@ -46,11 +47,13 @@ public class Parser extends RubyObject { private final RuntimeInfo info; private RubyString vSource; private RubyString createId; + private boolean createAdditions; private int maxNesting; private boolean allowNaN; private boolean symbolizeNames; private RubyClass objectClass; private RubyClass arrayClass; + private RubyHash match; private static final int DEFAULT_MAX_NESTING = 19; @@ -141,20 +144,18 @@ public class Parser extends RubyObject { @JRubyMethod(required = 1, optional = 1, visibility = Visibility.PRIVATE) public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - Ruby runtime = context.getRuntime(); + Ruby runtime = context.getRuntime(); RubyString source = convertEncoding(context, args[0].convertToString()); - OptionsReader opts = - new OptionsReader(context, args.length > 1 ? args[1] : null); - - this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); - this.allowNaN = opts.getBool("allow_nan", false); - this.symbolizeNames = opts.getBool("symbolize_names", false); - this.createId = - opts.getBool("create_additions", true) ? getCreateId(context) - : null; - this.objectClass = opts.getClass("object_class", runtime.getHash()); - this.arrayClass = opts.getClass("array_class", runtime.getArray()); + OptionsReader opts = new OptionsReader(context, args.length > 1 ? args[1] : null); + this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); + this.allowNaN = opts.getBool("allow_nan", false); + this.symbolizeNames = opts.getBool("symbolize_names", false); + this.createId = opts.getString("create_id", getCreateId(context)); + this.createAdditions = opts.getBool("create_additions", true); + this.objectClass = opts.getClass("object_class", runtime.getHash()); + this.arrayClass = opts.getClass("array_class", runtime.getArray()); + this.match = opts.getHash("match"); this.vSource = source; return this; @@ -297,11 +298,11 @@ public class Parser extends RubyObject { } -// line 323 "Parser.rl" +// line 324 "Parser.rl" -// line 305 "Parser.java" +// line 306 "Parser.java" private static byte[] init__JSON_value_actions_0() { return new byte [] { @@ -415,7 +416,7 @@ static final int JSON_value_error = 0; static final int JSON_value_en_main = 1; -// line 429 "Parser.rl" +// line 430 "Parser.rl" ParserResult parseValue(int p, int pe) { @@ -423,14 +424,14 @@ static final int JSON_value_en_main = 1; IRubyObject result = null; -// line 427 "Parser.java" +// line 428 "Parser.java" { cs = JSON_value_start; } -// line 436 "Parser.rl" +// line 437 "Parser.rl" -// line 434 "Parser.java" +// line 435 "Parser.java" { int _klen; int _trans = 0; @@ -456,13 +457,13 @@ case 1: while ( _nacts-- > 0 ) { switch ( _JSON_value_actions[_acts++] ) { case 9: -// line 414 "Parser.rl" +// line 415 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 466 "Parser.java" +// line 467 "Parser.java" } } @@ -525,25 +526,25 @@ case 1: switch ( _JSON_value_actions[_acts++] ) { case 0: -// line 331 "Parser.rl" +// line 332 "Parser.rl" { result = getRuntime().getNil(); } break; case 1: -// line 334 "Parser.rl" +// line 335 "Parser.rl" { result = getRuntime().getFalse(); } break; case 2: -// line 337 "Parser.rl" +// line 338 "Parser.rl" { result = getRuntime().getTrue(); } break; case 3: -// line 340 "Parser.rl" +// line 341 "Parser.rl" { if (parser.allowNaN) { result = getConstant(CONST_NAN); @@ -553,7 +554,7 @@ case 1: } break; case 4: -// line 347 "Parser.rl" +// line 348 "Parser.rl" { if (parser.allowNaN) { result = getConstant(CONST_INFINITY); @@ -563,7 +564,7 @@ case 1: } break; case 5: -// line 354 "Parser.rl" +// line 355 "Parser.rl" { if (pe > p + 9 && absSubSequence(p, p + 9).toString().equals(JSON_MINUS_INFINITY)) { @@ -592,7 +593,7 @@ case 1: } break; case 6: -// line 380 "Parser.rl" +// line 381 "Parser.rl" { ParserResult res = parseString(p, pe); if (res == null) { @@ -605,7 +606,7 @@ case 1: } break; case 7: -// line 390 "Parser.rl" +// line 391 "Parser.rl" { currentNesting++; ParserResult res = parseArray(p, pe); @@ -620,7 +621,7 @@ case 1: } break; case 8: -// line 402 "Parser.rl" +// line 403 "Parser.rl" { currentNesting++; ParserResult res = parseObject(p, pe); @@ -634,7 +635,7 @@ case 1: } } break; -// line 638 "Parser.java" +// line 639 "Parser.java" } } } @@ -654,7 +655,7 @@ case 5: break; } } -// line 437 "Parser.rl" +// line 438 "Parser.rl" if (cs >= JSON_value_first_final && result != null) { return new ParserResult(result, p); @@ -664,7 +665,7 @@ case 5: } -// line 668 "Parser.java" +// line 669 "Parser.java" private static byte[] init__JSON_integer_actions_0() { return new byte [] { @@ -763,22 +764,22 @@ static final int JSON_integer_error = 0; static final int JSON_integer_en_main = 1; -// line 456 "Parser.rl" +// line 457 "Parser.rl" ParserResult parseInteger(int p, int pe) { int cs = EVIL; -// line 774 "Parser.java" +// line 775 "Parser.java" { cs = JSON_integer_start; } -// line 462 "Parser.rl" +// line 463 "Parser.rl" int memo = p; -// line 782 "Parser.java" +// line 783 "Parser.java" { int _klen; int _trans = 0; @@ -859,13 +860,13 @@ case 1: switch ( _JSON_integer_actions[_acts++] ) { case 0: -// line 450 "Parser.rl" +// line 451 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 869 "Parser.java" +// line 870 "Parser.java" } } } @@ -885,7 +886,7 @@ case 5: break; } } -// line 464 "Parser.rl" +// line 465 "Parser.rl" if (cs < JSON_integer_first_final) { return null; @@ -900,7 +901,7 @@ case 5: } -// line 904 "Parser.java" +// line 905 "Parser.java" private static byte[] init__JSON_float_actions_0() { return new byte [] { @@ -1002,22 +1003,22 @@ static final int JSON_float_error = 0; static final int JSON_float_en_main = 1; -// line 492 "Parser.rl" +// line 493 "Parser.rl" ParserResult parseFloat(int p, int pe) { int cs = EVIL; -// line 1013 "Parser.java" +// line 1014 "Parser.java" { cs = JSON_float_start; } -// line 498 "Parser.rl" +// line 499 "Parser.rl" int memo = p; -// line 1021 "Parser.java" +// line 1022 "Parser.java" { int _klen; int _trans = 0; @@ -1098,13 +1099,13 @@ case 1: switch ( _JSON_float_actions[_acts++] ) { case 0: -// line 483 "Parser.rl" +// line 484 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1108 "Parser.java" +// line 1109 "Parser.java" } } } @@ -1124,7 +1125,7 @@ case 5: break; } } -// line 500 "Parser.rl" +// line 501 "Parser.rl" if (cs < JSON_float_first_final) { return null; @@ -1139,7 +1140,7 @@ case 5: } -// line 1143 "Parser.java" +// line 1144 "Parser.java" private static byte[] init__JSON_string_actions_0() { return new byte [] { @@ -1241,23 +1242,23 @@ static final int JSON_string_error = 0; static final int JSON_string_en_main = 1; -// line 544 "Parser.rl" +// line 545 "Parser.rl" ParserResult parseString(int p, int pe) { int cs = EVIL; - RubyString result = null; + IRubyObject result = null; -// line 1253 "Parser.java" +// line 1254 "Parser.java" { cs = JSON_string_start; } -// line 551 "Parser.rl" +// line 552 "Parser.rl" int memo = p; -// line 1261 "Parser.java" +// line 1262 "Parser.java" { int _klen; int _trans = 0; @@ -1338,7 +1339,7 @@ case 1: switch ( _JSON_string_actions[_acts++] ) { case 0: -// line 519 "Parser.rl" +// line 520 "Parser.rl" { int offset = byteList.begin(); ByteList decoded = decoder.decode(byteList, memo + 1 - offset, @@ -1353,13 +1354,13 @@ case 1: } break; case 1: -// line 532 "Parser.rl" +// line 533 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1363 "Parser.java" +// line 1364 "Parser.java" } } } @@ -1379,7 +1380,34 @@ case 5: break; } } -// line 553 "Parser.rl" +// line 554 "Parser.rl" + + if (parser.createAdditions) { + RubyHash match = parser.match; + if (match != null) { + final RubyArray memoArray = RubyArray.newArray(context.getRuntime(), 2); + memoArray.add(result); + try { + match.visitAll(new RubyHash.Visitor() { + @Override + public void visit(IRubyObject pattern, IRubyObject klass) { + if (pattern.callMethod(context, "===", memoArray.entry(0)).isTrue()) { + memoArray.add(klass); + throw JumpException.SPECIAL_JUMP; + } + } + }); + } catch (JumpException e) { } + IRubyObject matched = memoArray.entry(1); + if (!matched.isNil()) { + RubyClass klass = (RubyClass) matched; + if (klass.respondsTo("json_creatable?") && + klass.callMethod(context, "json_creatable?").isTrue()) { + result = klass.callMethod(context, "json_create", result); + } + } + } + } if (cs >= JSON_string_first_final && result != null) { return new ParserResult(result, p + 1); @@ -1389,7 +1417,7 @@ case 5: } -// line 1393 "Parser.java" +// line 1421 "Parser.java" private static byte[] init__JSON_array_actions_0() { return new byte [] { @@ -1502,7 +1530,7 @@ static final int JSON_array_error = 0; static final int JSON_array_en_main = 1; -// line 594 "Parser.rl" +// line 622 "Parser.rl" ParserResult parseArray(int p, int pe) { @@ -1520,14 +1548,14 @@ static final int JSON_array_en_main = 1; IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); -// line 1524 "Parser.java" +// line 1552 "Parser.java" { cs = JSON_array_start; } -// line 611 "Parser.rl" +// line 639 "Parser.rl" -// line 1531 "Parser.java" +// line 1559 "Parser.java" { int _klen; int _trans = 0; @@ -1608,7 +1636,7 @@ case 1: switch ( _JSON_array_actions[_acts++] ) { case 0: -// line 567 "Parser.rl" +// line 595 "Parser.rl" { ParserResult res = parseValue(p, pe); if (res == null) { @@ -1621,13 +1649,13 @@ case 1: } break; case 1: -// line 578 "Parser.rl" +// line 606 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1631 "Parser.java" +// line 1659 "Parser.java" } } } @@ -1647,7 +1675,7 @@ case 5: break; } } -// line 612 "Parser.rl" +// line 640 "Parser.rl" if (cs >= JSON_array_first_final) { return new ParserResult(result, p + 1); @@ -1657,7 +1685,7 @@ case 5: } -// line 1661 "Parser.java" +// line 1689 "Parser.java" private static byte[] init__JSON_object_actions_0() { return new byte [] { @@ -1780,7 +1808,7 @@ static final int JSON_object_error = 0; static final int JSON_object_en_main = 1; -// line 668 "Parser.rl" +// line 696 "Parser.rl" ParserResult parseObject(int p, int pe) { @@ -1799,14 +1827,14 @@ static final int JSON_object_en_main = 1; IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); -// line 1803 "Parser.java" +// line 1831 "Parser.java" { cs = JSON_object_start; } -// line 686 "Parser.rl" +// line 714 "Parser.rl" -// line 1810 "Parser.java" +// line 1838 "Parser.java" { int _klen; int _trans = 0; @@ -1887,7 +1915,7 @@ case 1: switch ( _JSON_object_actions[_acts++] ) { case 0: -// line 626 "Parser.rl" +// line 654 "Parser.rl" { ParserResult res = parseValue(p, pe); if (res == null) { @@ -1900,7 +1928,7 @@ case 1: } break; case 1: -// line 637 "Parser.rl" +// line 665 "Parser.rl" { ParserResult res = parseString(p, pe); if (res == null) { @@ -1920,13 +1948,13 @@ case 1: } break; case 2: -// line 655 "Parser.rl" +// line 683 "Parser.rl" { p--; { p += 1; _goto_targ = 5; if (true) continue _goto;} } break; -// line 1930 "Parser.java" +// line 1958 "Parser.java" } } } @@ -1946,7 +1974,7 @@ case 5: break; } } -// line 687 "Parser.rl" +// line 715 "Parser.rl" if (cs < JSON_object_first_final) { return null; @@ -1955,7 +1983,7 @@ case 5: IRubyObject returnedResult = result; // attempt to de-serialize object - if (parser.createId != null) { + if (parser.createAdditions) { IRubyObject vKlassName = result.op_aref(context, parser.createId); if (!vKlassName.isNil()) { // might throw ArgumentError, we let it propagate @@ -1972,7 +2000,7 @@ case 5: } -// line 1976 "Parser.java" +// line 2004 "Parser.java" private static byte[] init__JSON_actions_0() { return new byte [] { @@ -2076,7 +2104,7 @@ static final int JSON_error = 0; static final int JSON_en_main = 1; -// line 745 "Parser.rl" +// line 773 "Parser.rl" public IRubyObject parse() { @@ -2085,16 +2113,16 @@ static final int JSON_en_main = 1; IRubyObject result = null; -// line 2089 "Parser.java" +// line 2117 "Parser.java" { cs = JSON_start; } -// line 753 "Parser.rl" +// line 781 "Parser.rl" p = byteList.begin(); pe = p + byteList.length(); -// line 2098 "Parser.java" +// line 2126 "Parser.java" { int _klen; int _trans = 0; @@ -2175,7 +2203,7 @@ case 1: switch ( _JSON_actions[_acts++] ) { case 0: -// line 717 "Parser.rl" +// line 745 "Parser.rl" { currentNesting = 1; ParserResult res = parseObject(p, pe); @@ -2189,7 +2217,7 @@ case 1: } break; case 1: -// line 729 "Parser.rl" +// line 757 "Parser.rl" { currentNesting = 1; ParserResult res = parseArray(p, pe); @@ -2202,7 +2230,7 @@ case 1: } } break; -// line 2206 "Parser.java" +// line 2234 "Parser.java" } } } @@ -2222,7 +2250,7 @@ case 5: break; } } -// line 756 "Parser.rl" +// line 784 "Parser.rl" if (cs >= JSON_first_final && p == pe) { return result; diff --git a/java/src/json/ext/Parser.rl b/java/src/json/ext/Parser.rl index 00badc8..642e56c 100644 --- a/java/src/json/ext/Parser.rl +++ b/java/src/json/ext/Parser.rl @@ -18,6 +18,7 @@ import org.jruby.RubyNumeric; import org.jruby.RubyObject; import org.jruby.RubyString; import org.jruby.anno.JRubyMethod; +import org.jruby.exceptions.JumpException; import org.jruby.exceptions.RaiseException; import org.jruby.runtime.Block; import org.jruby.runtime.ObjectAllocator; @@ -44,11 +45,13 @@ public class Parser extends RubyObject { private final RuntimeInfo info; private RubyString vSource; private RubyString createId; + private boolean createAdditions; private int maxNesting; private boolean allowNaN; private boolean symbolizeNames; private RubyClass objectClass; private RubyClass arrayClass; + private RubyHash match; private static final int DEFAULT_MAX_NESTING = 19; @@ -139,20 +142,18 @@ public class Parser extends RubyObject { @JRubyMethod(required = 1, optional = 1, visibility = Visibility.PRIVATE) public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - Ruby runtime = context.getRuntime(); + Ruby runtime = context.getRuntime(); RubyString source = convertEncoding(context, args[0].convertToString()); - OptionsReader opts = - new OptionsReader(context, args.length > 1 ? args[1] : null); - - this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); - this.allowNaN = opts.getBool("allow_nan", false); - this.symbolizeNames = opts.getBool("symbolize_names", false); - this.createId = - opts.getBool("create_additions", true) ? getCreateId(context) - : null; - this.objectClass = opts.getClass("object_class", runtime.getHash()); - this.arrayClass = opts.getClass("array_class", runtime.getArray()); + OptionsReader opts = new OptionsReader(context, args.length > 1 ? args[1] : null); + this.maxNesting = opts.getInt("max_nesting", DEFAULT_MAX_NESTING); + this.allowNaN = opts.getBool("allow_nan", false); + this.symbolizeNames = opts.getBool("symbolize_names", false); + this.createId = opts.getString("create_id", getCreateId(context)); + this.createAdditions = opts.getBool("create_additions", true); + this.objectClass = opts.getClass("object_class", runtime.getHash()); + this.arrayClass = opts.getClass("array_class", runtime.getArray()); + this.match = opts.getHash("match"); this.vSource = source; return this; @@ -545,12 +546,39 @@ public class Parser extends RubyObject { ParserResult parseString(int p, int pe) { int cs = EVIL; - RubyString result = null; + IRubyObject result = null; %% write init; int memo = p; %% write exec; + if (parser.createAdditions) { + RubyHash match = parser.match; + if (match != null) { + final RubyArray memoArray = RubyArray.newArray(context.getRuntime(), 2); + memoArray.add(result); + try { + match.visitAll(new RubyHash.Visitor() { + @Override + public void visit(IRubyObject pattern, IRubyObject klass) { + if (pattern.callMethod(context, "===", memoArray.entry(0)).isTrue()) { + memoArray.add(klass); + throw JumpException.SPECIAL_JUMP; + } + } + }); + } catch (JumpException e) { } + IRubyObject matched = memoArray.entry(1); + if (!matched.isNil()) { + RubyClass klass = (RubyClass) matched; + if (klass.respondsTo("json_creatable?") && + klass.callMethod(context, "json_creatable?").isTrue()) { + result = klass.callMethod(context, "json_create", result); + } + } + } + } + if (cs >= JSON_string_first_final && result != null) { return new ParserResult(result, p + 1); } else { @@ -692,7 +720,7 @@ public class Parser extends RubyObject { IRubyObject returnedResult = result; // attempt to de-serialize object - if (parser.createId != null) { + if (parser.createAdditions) { IRubyObject vKlassName = result.op_aref(context, parser.createId); if (!vKlassName.isNil()) { // might throw ArgumentError, we let it propagate diff --git a/lib/json/pure/parser.rb b/lib/json/pure/parser.rb index 82a21e8..6a0192c 100644 --- a/lib/json/pure/parser.rb +++ b/lib/json/pure/parser.rb @@ -113,13 +113,13 @@ module JSON else @max_nesting = 0 end - @allow_nan = !!opts[:allow_nan] - @symbolize_names = !!opts[:symbolize_names] - ca = true - ca = opts[:create_additions] if opts.key?(:create_additions) - @create_id = ca ? JSON.create_id : nil - @object_class = opts[:object_class] || Hash - @array_class = opts[:array_class] || Array + @allow_nan = !!opts[:allow_nan] + @symbolize_names = !!opts[:symbolize_names] + @create_additions = opts.key?(:create_additions) ? !!opts[:create_additions] : true + @create_id = opts[:create_id] || JSON.create_id + @object_class = opts[:object_class] || Hash + @array_class = opts[:array_class] || Array + @json_match = opts[:match] # @match is an ivar in rbx's strscan end alias source string @@ -189,6 +189,12 @@ module JSON if string.respond_to?(:force_encoding) string.force_encoding(::Encoding::UTF_8) end + if @create_additions and @json_match + for (regexp, klass) in @json_match + klass.json_creatable? or next + string =~ regexp and return klass.json_create(string) + end + end string else UNPARSED @@ -295,7 +301,7 @@ module JSON if delim raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!" end - if @create_id and klassname = result[@create_id] + if @create_additions and klassname = result[@create_id] klass = JSON.deep_const_get klassname break unless klass and klass.json_creatable? result = klass.json_create(result) diff --git a/tests/setup_variant.rb b/tests/setup_variant.rb new file mode 100644 index 0000000..2dab184 --- /dev/null +++ b/tests/setup_variant.rb @@ -0,0 +1,11 @@ +case ENV['JSON'] +when 'pure' + $:.unshift 'lib' + require 'json/pure' +when 'ext' + $:.unshift 'ext', 'lib' + require 'json/ext' +else + $:.unshift 'ext', 'lib' + require 'json' +end diff --git a/tests/test_json.rb b/tests/test_json.rb index 00e52f5..2fc3c09 100755 --- a/tests/test_json.rb +++ b/tests/test_json.rb @@ -2,11 +2,7 @@ # -*- coding: utf-8 -*- require 'test/unit' -case ENV['JSON'] -when 'pure' then require 'json/pure' -when 'ext' then require 'json/ext' -else require 'json' -end +require File.join(File.dirname(__FILE__), 'setup_variant') require 'stringio' unless Array.method_defined?(:permutation) diff --git a/tests/test_json_addition.rb b/tests/test_json_addition.rb index 34f0a71..c8bfb41 100755 --- a/tests/test_json_addition.rb +++ b/tests/test_json_addition.rb @@ -2,11 +2,7 @@ # -*- coding:utf-8 -*- require 'test/unit' -case ENV['JSON'] -when 'pure' then require 'json/pure' -when 'ext' then require 'json/ext' -else require 'json' -end +require File.join(File.dirname(__FILE__), 'setup_variant') load 'json/add/core.rb' require 'date' @@ -36,6 +32,15 @@ class TC_JSONAddition < Test::Unit::TestCase end end + class A2 < A + def to_json(*args) + { + 'json_class' => self.class.name, + 'args' => [ @a ], + }.to_json(*args) + end + end + class B def self.json_creatable? false diff --git a/tests/test_json_encoding.rb b/tests/test_json_encoding.rb index cdeca58..7af5e63 100644 --- a/tests/test_json_encoding.rb +++ b/tests/test_json_encoding.rb @@ -2,11 +2,7 @@ # -*- coding: utf-8 -*- require 'test/unit' -case ENV['JSON'] -when 'pure' then require 'json/pure' -when 'ext' then require 'json/ext' -else require 'json' -end +require File.join(File.dirname(__FILE__), 'setup_variant') class TC_JSONEncoding < Test::Unit::TestCase include JSON diff --git a/tests/test_json_fixtures.rb b/tests/test_json_fixtures.rb index 378667f..e9df8f5 100755 --- a/tests/test_json_fixtures.rb +++ b/tests/test_json_fixtures.rb @@ -2,11 +2,7 @@ # -*- coding: utf-8 -*- require 'test/unit' -case ENV['JSON'] -when 'pure' then require 'json/pure' -when 'ext' then require 'json/ext' -else require 'json' -end +require File.join(File.dirname(__FILE__), 'setup_variant') class TC_JSONFixtures < Test::Unit::TestCase def setup diff --git a/tests/test_json_generate.rb b/tests/test_json_generate.rb index 5380a06..e6219df 100755 --- a/tests/test_json_generate.rb +++ b/tests/test_json_generate.rb @@ -2,11 +2,7 @@ # -*- coding: utf-8 -*- require 'test/unit' -case ENV['JSON'] -when 'pure' then require 'json/pure' -when 'ext' then require 'json/ext' -else require 'json' -end +require File.join(File.dirname(__FILE__), 'setup_variant') class TC_JSONGenerate < Test::Unit::TestCase include JSON diff --git a/tests/test_json_string_matching.rb b/tests/test_json_string_matching.rb new file mode 100644 index 0000000..6bd74e3 --- /dev/null +++ b/tests/test_json_string_matching.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby +# -*- coding: utf-8 -*- + +require 'test/unit' +require File.join(File.dirname(__FILE__), 'setup_variant') +require 'stringio' +require 'time' + +class TestJsonStringMatching < Test::Unit::TestCase + include JSON + + class TestTime < ::Time + def self.json_create(string) + Time.parse(string) + end + + def to_json(*) + %{"#{strftime('%FT%T%z')}"} + end + + def ==(other) + to_i == other.to_i + end + end + + def test_match_date + t = TestTime.new + t_json = [ t ].to_json + assert_equal [ t ], + JSON.parse(t_json, + :match => { /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{4}\Z/ => TestTime }) + assert_equal [ t.strftime('%FT%T%z') ], + JSON.parse(t_json, + :match => { /\A\d{3}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{4}\Z/ => TestTime }) + assert_equal [ t.strftime('%FT%T%z') ], + JSON.parse(t_json, + :match => { /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{4}\Z/ => TestTime }, + :create_additions => false) + end +end diff --git a/tests/test_json_unicode.rb b/tests/test_json_unicode.rb index 505f5d5..ace56ca 100755 --- a/tests/test_json_unicode.rb +++ b/tests/test_json_unicode.rb @@ -2,11 +2,7 @@ # -*- coding: utf-8 -*- require 'test/unit' -case ENV['JSON'] -when 'pure' then require 'json/pure' -when 'ext' then require 'json/ext' -else require 'json' -end +require File.join(File.dirname(__FILE__), 'setup_variant') class TC_JSONUnicode < Test::Unit::TestCase include JSON |