├── .gitignore ├── lib ├── execjs │ ├── version.rb │ ├── json.rb │ ├── disabled_runtime.rb │ ├── support │ │ ├── jsc_runner.js │ │ ├── spidermonkey_runner.js │ │ ├── node_runner.js │ │ ├── jscript_runner.js │ │ └── json2.js │ ├── module.rb │ ├── encoding.rb │ ├── runtime.rb │ ├── mustang_runtime.rb │ ├── ruby_rhino_runtime.rb │ ├── johnson_runtime.rb │ ├── ruby_racer_runtime.rb │ ├── runtimes.rb │ └── external_runtime.rb └── execjs.rb ├── .travis.yml ├── Gemfile ├── execjs.gemspec ├── LICENSE ├── README.md ├── Rakefile └── test └── test_execjs.rb /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | -------------------------------------------------------------------------------- /lib/execjs/version.rb: -------------------------------------------------------------------------------- 1 | module ExecJS 2 | VERSION = "1.4.0" 3 | end 4 | -------------------------------------------------------------------------------- /lib/execjs.rb: -------------------------------------------------------------------------------- 1 | require "execjs/module" 2 | require "execjs/runtimes" 3 | 4 | module ExecJS 5 | self.runtime ||= Runtimes.autodetect 6 | end 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.8.7 3 | - 1.9.2 4 | - 1.9.3 5 | - jruby-18mode 6 | - jruby-19mode 7 | - jruby-head 8 | - rbx-18mode 9 | - rbx-19mode 10 | - ree 11 | - ruby-head 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gemspec 4 | 5 | group :test do 6 | gem 'json' 7 | gem 'therubyracer', :platform => :mri 8 | gem 'therubyrhino', ">=1.73.3", :platform => :jruby 9 | end 10 | -------------------------------------------------------------------------------- /lib/execjs/json.rb: -------------------------------------------------------------------------------- 1 | require "multi_json" 2 | 3 | module ExecJS 4 | module JSON 5 | if MultiJson.respond_to?(:dump) 6 | def self.decode(obj) 7 | MultiJson.load(obj) 8 | end 9 | 10 | def self.encode(obj) 11 | MultiJson.dump(obj) 12 | end 13 | else 14 | def self.decode(obj) 15 | MultiJson.decode(obj) 16 | end 17 | 18 | def self.encode(obj) 19 | MultiJson.encode(obj) 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/execjs/disabled_runtime.rb: -------------------------------------------------------------------------------- 1 | require "execjs/runtime" 2 | 3 | module ExecJS 4 | class DisabledRuntime < Runtime 5 | def name 6 | "Disabled" 7 | end 8 | 9 | def exec(source) 10 | raise Error, "ExecJS disabled" 11 | end 12 | 13 | def eval(source) 14 | raise Error, "ExecJS disabled" 15 | end 16 | 17 | def compile(source) 18 | raise Error, "ExecJS disabled" 19 | end 20 | 21 | def deprecated? 22 | true 23 | end 24 | 25 | def available? 26 | true 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/execjs/support/jsc_runner.js: -------------------------------------------------------------------------------- 1 | (function(program, execJS) { execJS(program) })(function() { 2 | return eval(#{encoded_source}); 3 | }, function(program) { 4 | var output; 5 | try { 6 | result = program(); 7 | if (typeof result == 'undefined' && result !== null) { 8 | print('["ok"]'); 9 | } else { 10 | try { 11 | print(JSON.stringify(['ok', result])); 12 | } catch (err) { 13 | print('["err"]'); 14 | } 15 | } 16 | } catch (err) { 17 | print(JSON.stringify(['err', '' + err])); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /lib/execjs/support/spidermonkey_runner.js: -------------------------------------------------------------------------------- 1 | (function(program, execJS) { execJS(program) })(function() { #{source} 2 | }, function(program) { 3 | #{json2_source} 4 | var output; 5 | try { 6 | result = program(); 7 | if (typeof result == 'undefined' && result !== null) { 8 | print('["ok"]'); 9 | } else { 10 | try { 11 | print(JSON.stringify(['ok', result])); 12 | } catch (err) { 13 | print('["err"]'); 14 | } 15 | } 16 | } catch (err) { 17 | print(JSON.stringify(['err', '' + err])); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /lib/execjs/support/node_runner.js: -------------------------------------------------------------------------------- 1 | (function(program, execJS) { execJS(program) })(function(module, exports, require, console) { #{source} 2 | }, function(program) { 3 | var output, print = function(string) { 4 | process.stdout.write('' + string); 5 | }; 6 | try { 7 | result = program(); 8 | if (typeof result == 'undefined' && result !== null) { 9 | print('["ok"]'); 10 | } else { 11 | try { 12 | print(JSON.stringify(['ok', result])); 13 | } catch (err) { 14 | print('["err"]'); 15 | } 16 | } 17 | } catch (err) { 18 | print(JSON.stringify(['err', '' + err])); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /execjs.gemspec: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path('../lib', __FILE__) 2 | require 'execjs/version' 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "execjs" 6 | s.version = ExecJS::VERSION 7 | 8 | s.homepage = "https://github.com/sstephenson/execjs" 9 | s.summary = "Run JavaScript code from Ruby" 10 | s.description = "ExecJS lets you run JavaScript code from Ruby." 11 | 12 | s.files = Dir["README.md", "LICENSE", "lib/**/*"] 13 | 14 | s.add_dependency "multi_json", "~>1.0" 15 | s.add_development_dependency "rake" 16 | 17 | s.authors = ["Sam Stephenson", "Josh Peek"] 18 | s.email = ["sstephenson@gmail.com", "josh@joshpeek.com"] 19 | end 20 | -------------------------------------------------------------------------------- /lib/execjs/support/jscript_runner.js: -------------------------------------------------------------------------------- 1 | (function(program, execJS) { execJS(program) })(function() { 2 | return eval(#{encoded_source}); 3 | }, function(program) { 4 | #{json2_source} 5 | var output, print = function(string) { 6 | WScript.Echo(string); 7 | }; 8 | try { 9 | result = program(); 10 | if (typeof result == 'undefined' && result !== null) { 11 | print('["ok"]'); 12 | } else { 13 | try { 14 | print(JSON.stringify(['ok', result])); 15 | } catch (err) { 16 | print('["err"]'); 17 | } 18 | } 19 | } catch (err) { 20 | print(JSON.stringify(['err', err.name + ': ' + err.message])); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /lib/execjs/module.rb: -------------------------------------------------------------------------------- 1 | require "execjs/version" 2 | require "rbconfig" 3 | 4 | module ExecJS 5 | class Error < ::StandardError; end 6 | class RuntimeError < Error; end 7 | class ProgramError < Error; end 8 | class RuntimeUnavailable < RuntimeError; end 9 | 10 | class << self 11 | attr_reader :runtime 12 | 13 | def runtime=(runtime) 14 | raise RuntimeUnavailable, "#{runtime.name} is unavailable on this system" unless runtime.available? 15 | @runtime = runtime 16 | end 17 | 18 | def exec(source) 19 | runtime.exec(source) 20 | end 21 | 22 | def eval(source) 23 | runtime.eval(source) 24 | end 25 | 26 | def compile(source) 27 | runtime.compile(source) 28 | end 29 | 30 | def root 31 | @root ||= File.expand_path("..", __FILE__) 32 | end 33 | 34 | def windows? 35 | @windows ||= RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/execjs/encoding.rb: -------------------------------------------------------------------------------- 1 | module ExecJS 2 | # Encodes strings as UTF-8 3 | module Encoding 4 | if "".respond_to?(:encode) 5 | if RUBY_ENGINE == 'jruby' || RUBY_ENGINE == 'rbx' 6 | # workaround for jruby bug http://jira.codehaus.org/browse/JRUBY-6588 7 | # workaround for rbx bug https://github.com/rubinius/rubinius/issues/1729 8 | def encode(string) 9 | if string.encoding.name == 'ASCII-8BIT' 10 | data = string.dup 11 | data.force_encoding('UTF-8') 12 | 13 | unless data.valid_encoding? 14 | raise ::Encoding::UndefinedConversionError, "Could not encode ASCII-8BIT data #{string.dump} as UTF-8" 15 | end 16 | else 17 | data = string.encode('UTF-8') 18 | end 19 | data 20 | end 21 | else 22 | def encode(string) 23 | string.encode('UTF-8') 24 | end 25 | end 26 | else 27 | # Define no-op on 1.8 28 | def encode(string) 29 | string 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Sam Stephenson 2 | Copyright (c) 2011 Josh Peek 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/execjs/runtime.rb: -------------------------------------------------------------------------------- 1 | require "execjs/encoding" 2 | 3 | module ExecJS 4 | # Abstract base class for runtimes 5 | class Runtime 6 | class Context 7 | include Encoding 8 | 9 | def initialize(runtime, source = "") 10 | end 11 | 12 | def exec(source, options = {}) 13 | raise NotImplementedError 14 | end 15 | 16 | def eval(source, options = {}) 17 | raise NotImplementedError 18 | end 19 | 20 | def call(properties, *args) 21 | raise NotImplementedError 22 | end 23 | end 24 | 25 | def name 26 | raise NotImplementedError 27 | end 28 | 29 | def context_class 30 | self.class::Context 31 | end 32 | 33 | def exec(source) 34 | context = context_class.new(self) 35 | context.exec(source) 36 | end 37 | 38 | def eval(source) 39 | context = context_class.new(self) 40 | context.eval(source) 41 | end 42 | 43 | def compile(source) 44 | context_class.new(self, source) 45 | end 46 | 47 | def deprecated? 48 | false 49 | end 50 | 51 | def available? 52 | raise NotImplementedError 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ExecJS 2 | ====== 3 | 4 | ExecJS lets you run JavaScript code from Ruby. It automatically picks 5 | the best runtime available to evaluate your JavaScript program, then 6 | returns the result to you as a Ruby object. 7 | 8 | ExecJS supports these runtimes: 9 | 10 | * [therubyracer](https://github.com/cowboyd/therubyracer) - Google V8 11 | embedded within Ruby 12 | * [therubyrhino](https://github.com/cowboyd/therubyrhino) - Mozilla 13 | Rhino embedded within JRuby 14 | * [Node.js](http://nodejs.org/) 15 | * Apple JavaScriptCore - Included with Mac OS X 16 | * [Microsoft Windows Script Host](http://msdn.microsoft.com/en-us/library/9bbdkx3k.aspx) (JScript) 17 | 18 | A short example: 19 | 20 | require "execjs" 21 | ExecJS.eval "'red yellow blue'.split(' ')" 22 | # => ["red", "yellow", "blue"] 23 | 24 | A longer example, demonstrating how to invoke the CoffeeScript compiler: 25 | 26 | require "execjs" 27 | require "open-uri" 28 | source = open("http://jashkenas.github.com/coffee-script/extras/coffee-script.js").read 29 | 30 | context = ExecJS.compile(source) 31 | context.call("CoffeeScript.compile", "square = (x) -> x * x", :bare => true) 32 | # => "var square;\nsquare = function(x) {\n return x * x;\n};" 33 | 34 | # Installation 35 | 36 | $ gem install execjs 37 | 38 | # License 39 | 40 | Copyright (c) 2011 Sam Stephenson and Josh Peek. 41 | 42 | Released under the MIT license. See `LICENSE` for details. 43 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rake/testtask" 2 | 3 | task :default => :test 4 | 5 | $:.unshift File.expand_path("../lib", __FILE__) 6 | require "execjs/runtimes" 7 | 8 | tests = namespace :test do |tests| 9 | ExecJS::Runtimes.names.each do |name| 10 | next if ExecJS::Runtimes.const_get(name).deprecated? 11 | 12 | task(name.downcase) do 13 | ENV["EXECJS_RUNTIME"] = name.to_s 14 | end 15 | 16 | Rake::TestTask.new(name.downcase) do |t| 17 | t.libs << "test" 18 | t.warning = true 19 | end 20 | end 21 | end 22 | 23 | def banner(text) 24 | warn "" 25 | warn "=" * Rake.application.terminal_width 26 | warn text 27 | warn "=" * Rake.application.terminal_width 28 | warn "" 29 | end 30 | 31 | desc "Run tests for all installed runtimes" 32 | task :test do 33 | passed = [] 34 | failed = [] 35 | skipped = [] 36 | 37 | tests.tasks.each do |task| 38 | banner "Running #{task.name}" 39 | 40 | begin 41 | task.invoke 42 | rescue Exception => e 43 | if e.message[/Command failed with status \((\d+)\)/, 1] == "2" 44 | skipped << task.name 45 | else 46 | failed << task.name 47 | end 48 | else 49 | passed << task.name 50 | end 51 | end 52 | 53 | messages = ["PASSED: #{passed.join(", ")}"] 54 | messages << "SKIPPED: #{skipped.join(", ")}" if skipped.any? 55 | messages << "FAILED: #{failed.join(", ")}" if failed.any? 56 | banner messages.join("\n") 57 | 58 | raise "test failures" if failed.any? 59 | raise "all tests skipped" if !passed.any? 60 | end 61 | -------------------------------------------------------------------------------- /lib/execjs/mustang_runtime.rb: -------------------------------------------------------------------------------- 1 | require "execjs/runtime" 2 | 3 | module ExecJS 4 | class MustangRuntime < Runtime 5 | class Context < Runtime::Context 6 | def initialize(runtime, source = "") 7 | source = encode(source) 8 | 9 | @v8_context = ::Mustang::Context.new 10 | @v8_context.eval(source) 11 | end 12 | 13 | def exec(source, options = {}) 14 | source = encode(source) 15 | 16 | if /\S/ =~ source 17 | eval "(function(){#{source}})()", options 18 | end 19 | end 20 | 21 | def eval(source, options = {}) 22 | source = encode(source) 23 | 24 | if /\S/ =~ source 25 | unbox @v8_context.eval("(#{source})") 26 | end 27 | end 28 | 29 | def call(properties, *args) 30 | unbox @v8_context.eval(properties).call(*args) 31 | rescue NoMethodError => e 32 | raise ProgramError, e.message 33 | end 34 | 35 | def unbox(value) 36 | case value 37 | when Mustang::V8::Array 38 | value.map { |v| unbox(v) } 39 | when Mustang::V8::Boolean 40 | value.to_bool 41 | when Mustang::V8::NullClass, Mustang::V8::UndefinedClass 42 | nil 43 | when Mustang::V8::Function 44 | nil 45 | when Mustang::V8::SyntaxError 46 | raise RuntimeError, value.message 47 | when Mustang::V8::Error 48 | raise ProgramError, value.message 49 | when Mustang::V8::Object 50 | value.inject({}) { |h, (k, v)| 51 | v = unbox(v) 52 | h[k] = v if v 53 | h 54 | } 55 | else 56 | value.respond_to?(:delegate) ? value.delegate : value 57 | end 58 | end 59 | end 60 | 61 | def name 62 | "Mustang (V8)" 63 | end 64 | 65 | def available? 66 | require "mustang" 67 | true 68 | rescue LoadError 69 | false 70 | end 71 | 72 | def deprecated? 73 | true 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/execjs/ruby_rhino_runtime.rb: -------------------------------------------------------------------------------- 1 | require "execjs/runtime" 2 | 3 | module ExecJS 4 | class RubyRhinoRuntime < Runtime 5 | class Context < Runtime::Context 6 | def initialize(runtime, source = "") 7 | source = encode(source) 8 | 9 | @rhino_context = ::Rhino::Context.new 10 | fix_memory_limit! @rhino_context 11 | @rhino_context.eval(source) 12 | end 13 | 14 | def exec(source, options = {}) 15 | source = encode(source) 16 | 17 | if /\S/ =~ source 18 | eval "(function(){#{source}})()", options 19 | end 20 | end 21 | 22 | def eval(source, options = {}) 23 | source = encode(source) 24 | 25 | if /\S/ =~ source 26 | unbox @rhino_context.eval("(#{source})") 27 | end 28 | rescue ::Rhino::JSError => e 29 | if e.message =~ /^syntax error/ 30 | raise RuntimeError, e.message 31 | else 32 | raise ProgramError, e.message 33 | end 34 | end 35 | 36 | def call(properties, *args) 37 | unbox @rhino_context.eval(properties).call(*args) 38 | rescue ::Rhino::JSError => e 39 | if e.message == "syntax error" 40 | raise RuntimeError, e.message 41 | else 42 | raise ProgramError, e.message 43 | end 44 | end 45 | 46 | def unbox(value) 47 | case value = ::Rhino::to_ruby(value) 48 | when Java::OrgMozillaJavascript::NativeFunction 49 | nil 50 | when Java::OrgMozillaJavascript::NativeObject 51 | value.inject({}) do |vs, (k, v)| 52 | case v 53 | when Java::OrgMozillaJavascript::NativeFunction, ::Rhino::JS::Function 54 | nil 55 | else 56 | vs[k] = unbox(v) 57 | end 58 | vs 59 | end 60 | when Array 61 | value.map { |v| unbox(v) } 62 | else 63 | value 64 | end 65 | end 66 | 67 | private 68 | # Disables bytecode compiling which limits you to 64K scripts 69 | def fix_memory_limit!(context) 70 | if context.respond_to?(:optimization_level=) 71 | context.optimization_level = -1 72 | else 73 | context.instance_eval { @native.setOptimizationLevel(-1) } 74 | end 75 | end 76 | end 77 | 78 | def name 79 | "therubyrhino (Rhino)" 80 | end 81 | 82 | def available? 83 | require "rhino" 84 | true 85 | rescue LoadError 86 | false 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/execjs/johnson_runtime.rb: -------------------------------------------------------------------------------- 1 | require "execjs/runtime" 2 | 3 | module ExecJS 4 | class JohnsonRuntime < Runtime 5 | class Context < Runtime::Context 6 | def initialize(runtime, source = "") 7 | source = encode(source) 8 | 9 | @runtime = Johnson::Runtime.new 10 | @runtime.evaluate(source) 11 | end 12 | 13 | def exec(source, options = {}) 14 | source = encode(source) 15 | 16 | if /\S/ =~ source 17 | eval "(function(){#{source}})()", options 18 | end 19 | end 20 | 21 | def eval(source, options = {}) 22 | source = encode(source) 23 | 24 | if /\S/ =~ source 25 | unbox @runtime.evaluate("(#{source})") 26 | end 27 | rescue Johnson::Error => e 28 | if syntax_error?(e) 29 | raise RuntimeError, e.message 30 | else 31 | raise ProgramError, e.message 32 | end 33 | end 34 | 35 | def call(properties, *args) 36 | unbox @runtime.evaluate(properties).call(*args) 37 | rescue Johnson::Error => e 38 | if syntax_error?(e) 39 | raise RuntimeError, e.message 40 | else 41 | raise ProgramError, e.message 42 | end 43 | end 44 | 45 | def unbox(value) 46 | case 47 | when function?(value) 48 | nil 49 | when string?(value) 50 | value.respond_to?(:force_encoding) ? 51 | value.force_encoding('UTF-8') : 52 | value 53 | when array?(value) 54 | value.map { |v| unbox(v) } 55 | when object?(value) 56 | value.inject({}) do |vs, (k, v)| 57 | vs[k] = unbox(v) unless function?(v) 58 | vs 59 | end 60 | else 61 | value 62 | end 63 | end 64 | 65 | private 66 | def syntax_error?(error) 67 | error.message =~ /^syntax error at / 68 | end 69 | 70 | def function?(value) 71 | value.respond_to?(:function?) && value.function? 72 | end 73 | 74 | def string?(value) 75 | value.is_a?(String) 76 | end 77 | 78 | def array?(value) 79 | array_test.call(value) 80 | end 81 | 82 | def object?(value) 83 | value.respond_to?(:inject) 84 | end 85 | 86 | def array_test 87 | @array_test ||= @runtime.evaluate("(function(a) {return a instanceof [].constructor})") 88 | end 89 | end 90 | 91 | def name 92 | "Johnson (SpiderMonkey)" 93 | end 94 | 95 | def available? 96 | require "johnson" 97 | true 98 | rescue LoadError 99 | false 100 | end 101 | 102 | def deprecated? 103 | true 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /lib/execjs/ruby_racer_runtime.rb: -------------------------------------------------------------------------------- 1 | require "execjs/runtime" 2 | 3 | module ExecJS 4 | class RubyRacerRuntime < Runtime 5 | class Context < Runtime::Context 6 | def initialize(runtime, source = "") 7 | source = encode(source) 8 | 9 | lock do 10 | @v8_context = ::V8::Context.new 11 | @v8_context.eval(source) 12 | end 13 | end 14 | 15 | def exec(source, options = {}) 16 | source = encode(source) 17 | 18 | if /\S/ =~ source 19 | eval "(function(){#{source}})()", options 20 | end 21 | end 22 | 23 | def eval(source, options = {}) 24 | source = encode(source) 25 | 26 | if /\S/ =~ source 27 | lock do 28 | begin 29 | unbox @v8_context.eval("(#{source})") 30 | rescue ::V8::JSError => e 31 | if e.value["name"] == "SyntaxError" 32 | raise RuntimeError, e.value.to_s 33 | else 34 | raise ProgramError, e.value.to_s 35 | end 36 | end 37 | end 38 | end 39 | end 40 | 41 | def call(properties, *args) 42 | lock do 43 | begin 44 | unbox @v8_context.eval(properties).call(*args) 45 | rescue ::V8::JSError => e 46 | if e.value["name"] == "SyntaxError" 47 | raise RuntimeError, e.value.to_s 48 | else 49 | raise ProgramError, e.value.to_s 50 | end 51 | end 52 | end 53 | end 54 | 55 | def unbox(value) 56 | case value 57 | when ::V8::Function 58 | nil 59 | when ::V8::Array 60 | value.map { |v| unbox(v) } 61 | when ::V8::Object 62 | value.inject({}) do |vs, (k, v)| 63 | vs[k] = unbox(v) unless v.is_a?(::V8::Function) 64 | vs 65 | end 66 | when String 67 | value.respond_to?(:force_encoding) ? 68 | value.force_encoding('UTF-8') : 69 | value 70 | else 71 | value 72 | end 73 | end 74 | 75 | private 76 | def lock 77 | result, exception = nil, nil 78 | V8::C::Locker() do 79 | begin 80 | result = yield 81 | rescue Exception => e 82 | exception = e 83 | end 84 | end 85 | 86 | if exception 87 | raise exception 88 | else 89 | result 90 | end 91 | end 92 | end 93 | 94 | def name 95 | "therubyracer (V8)" 96 | end 97 | 98 | def available? 99 | require "v8" 100 | true 101 | rescue LoadError 102 | false 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/execjs/runtimes.rb: -------------------------------------------------------------------------------- 1 | require "execjs/module" 2 | require "execjs/disabled_runtime" 3 | require "execjs/external_runtime" 4 | require "execjs/johnson_runtime" 5 | require "execjs/mustang_runtime" 6 | require "execjs/ruby_racer_runtime" 7 | require "execjs/ruby_rhino_runtime" 8 | 9 | module ExecJS 10 | module Runtimes 11 | Disabled = DisabledRuntime.new 12 | 13 | RubyRacer = RubyRacerRuntime.new 14 | 15 | RubyRhino = RubyRhinoRuntime.new 16 | 17 | Johnson = JohnsonRuntime.new 18 | 19 | Mustang = MustangRuntime.new 20 | 21 | Node = ExternalRuntime.new( 22 | :name => "Node.js (V8)", 23 | :command => ["nodejs", "node"], 24 | :runner_path => ExecJS.root + "/support/node_runner.js", 25 | :encoding => 'UTF-8' 26 | ) 27 | 28 | JavaScriptCore = ExternalRuntime.new( 29 | :name => "JavaScriptCore", 30 | :command => "/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc", 31 | :runner_path => ExecJS.root + "/support/jsc_runner.js" 32 | ) 33 | 34 | SpiderMonkey = Spidermonkey = ExternalRuntime.new( 35 | :name => "SpiderMonkey", 36 | :command => "js", 37 | :runner_path => ExecJS.root + "/support/spidermonkey_runner.js", 38 | :deprecated => true 39 | ) 40 | 41 | JScript = ExternalRuntime.new( 42 | :name => "JScript", 43 | :command => "cscript //E:jscript //Nologo //U", 44 | :runner_path => ExecJS.root + "/support/jscript_runner.js", 45 | :encoding => 'UTF-16LE' # CScript with //U returns UTF-16LE 46 | ) 47 | 48 | 49 | def self.autodetect 50 | from_environment || best_available || 51 | raise(RuntimeUnavailable, "Could not find a JavaScript runtime. " + 52 | "See https://github.com/sstephenson/execjs for a list of available runtimes.") 53 | end 54 | 55 | def self.best_available 56 | runtimes.reject(&:deprecated?).find(&:available?) 57 | end 58 | 59 | def self.from_environment 60 | if name = ENV["EXECJS_RUNTIME"] 61 | if runtime = const_get(name) 62 | if runtime.available? 63 | runtime if runtime.available? 64 | else 65 | raise RuntimeUnavailable, "#{runtime.name} runtime is not available on this system" 66 | end 67 | elsif !name.empty? 68 | raise RuntimeUnavailable, "#{name} runtime is not defined" 69 | end 70 | end 71 | end 72 | 73 | def self.names 74 | @names ||= constants.inject({}) { |h, name| h.merge(const_get(name) => name) }.values 75 | end 76 | 77 | def self.runtimes 78 | @runtimes ||= [ 79 | RubyRacer, 80 | RubyRhino, 81 | Johnson, 82 | Mustang, 83 | Node, 84 | JavaScriptCore, 85 | SpiderMonkey, 86 | JScript 87 | ] 88 | end 89 | end 90 | 91 | def self.runtimes 92 | Runtimes.runtimes 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /test/test_execjs.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require "test/unit" 3 | require "execjs/module" 4 | 5 | begin 6 | require "execjs" 7 | rescue ExecJS::RuntimeUnavailable => e 8 | warn e 9 | exit 2 10 | end 11 | 12 | class TestExecJS < Test::Unit::TestCase 13 | def test_runtime_available 14 | runtime = ExecJS::ExternalRuntime.new(:command => "nonexistent") 15 | assert !runtime.available? 16 | 17 | runtime = ExecJS::ExternalRuntime.new(:command => "ruby") 18 | assert runtime.available? 19 | end 20 | 21 | def test_runtime_assignment 22 | original_runtime = ExecJS.runtime 23 | runtime = ExecJS::ExternalRuntime.new(:command => "nonexistent") 24 | assert_raises(ExecJS::RuntimeUnavailable) { ExecJS.runtime = runtime } 25 | assert_equal original_runtime, ExecJS.runtime 26 | 27 | runtime = ExecJS::ExternalRuntime.new(:command => "ruby") 28 | ExecJS.runtime = runtime 29 | assert_equal runtime, ExecJS.runtime 30 | ensure 31 | ExecJS.runtime = original_runtime 32 | end 33 | 34 | def test_context_call 35 | context = ExecJS.compile("id = function(v) { return v; }") 36 | assert_equal "bar", context.call("id", "bar") 37 | end 38 | 39 | def test_nested_context_call 40 | context = ExecJS.compile("a = {}; a.b = {}; a.b.id = function(v) { return v; }") 41 | assert_equal "bar", context.call("a.b.id", "bar") 42 | end 43 | 44 | def test_context_call_missing_function 45 | context = ExecJS.compile("") 46 | assert_raises ExecJS::ProgramError do 47 | context.call("missing") 48 | end 49 | end 50 | 51 | def test_exec 52 | assert_nil ExecJS.exec("1") 53 | assert_nil ExecJS.exec("return") 54 | assert_nil ExecJS.exec("return null") 55 | assert_nil ExecJS.exec("return function() {}") 56 | assert_equal 0, ExecJS.exec("return 0") 57 | assert_equal true, ExecJS.exec("return true") 58 | assert_equal [1, 2], ExecJS.exec("return [1, 2]") 59 | assert_equal "hello", ExecJS.exec("return 'hello'") 60 | assert_equal({"a"=>1,"b"=>2}, ExecJS.exec("return {a:1,b:2}")) 61 | assert_equal "café", ExecJS.exec("return 'café'") 62 | assert_equal "☃", ExecJS.exec('return "☃"') 63 | assert_equal "☃", ExecJS.exec('return "\u2603"') 64 | assert_equal "\\", ExecJS.exec('return "\\\\"') 65 | end 66 | 67 | def test_eval 68 | assert_nil ExecJS.eval("") 69 | assert_nil ExecJS.eval(" ") 70 | assert_nil ExecJS.eval("null") 71 | assert_nil ExecJS.eval("function() {}") 72 | assert_equal 0, ExecJS.eval("0") 73 | assert_equal true, ExecJS.eval("true") 74 | assert_equal [1, 2], ExecJS.eval("[1, 2]") 75 | assert_equal [1, nil], ExecJS.eval("[1, function() {}]") 76 | assert_equal "hello", ExecJS.eval("'hello'") 77 | assert_equal ["red", "yellow", "blue"], ExecJS.eval("'red yellow blue'.split(' ')") 78 | assert_equal({"a"=>1,"b"=>2}, ExecJS.eval("{a:1,b:2}")) 79 | assert_equal({"a"=>true}, ExecJS.eval("{a:true,b:function (){}}")) 80 | assert_equal "café", ExecJS.eval("'café'") 81 | assert_equal "☃", ExecJS.eval('"☃"') 82 | assert_equal "☃", ExecJS.eval('"\u2603"') 83 | assert_equal "\\", ExecJS.eval('"\\\\"') 84 | end 85 | 86 | if defined? Encoding 87 | def test_encoding 88 | utf8 = Encoding.find('UTF-8') 89 | 90 | assert_equal utf8, ExecJS.exec("return 'hello'").encoding 91 | assert_equal utf8, ExecJS.eval("'☃'").encoding 92 | 93 | ascii = "'hello'".encode('US-ASCII') 94 | result = ExecJS.eval(ascii) 95 | assert_equal "hello", result 96 | assert_equal utf8, result.encoding 97 | 98 | assert_raise Encoding::UndefinedConversionError do 99 | binary = "\xde\xad\xbe\xef".force_encoding("BINARY") 100 | ExecJS.eval(binary) 101 | end 102 | end 103 | 104 | def test_encoding_compile 105 | utf8 = Encoding.find('UTF-8') 106 | 107 | context = ExecJS.compile("foo = function(v) { return '¶' + v; }".encode("ISO8859-15")) 108 | 109 | assert_equal utf8, context.exec("return foo('hello')").encoding 110 | assert_equal utf8, context.eval("foo('☃')").encoding 111 | 112 | ascii = "foo('hello')".encode('US-ASCII') 113 | result = context.eval(ascii) 114 | assert_equal "¶hello", result 115 | assert_equal utf8, result.encoding 116 | 117 | assert_raise Encoding::UndefinedConversionError do 118 | binary = "\xde\xad\xbe\xef".force_encoding("BINARY") 119 | context.eval(binary) 120 | end 121 | end 122 | end 123 | 124 | def test_compile 125 | context = ExecJS.compile("foo = function() { return \"bar\"; }") 126 | assert_equal "bar", context.exec("return foo()") 127 | assert_equal "bar", context.eval("foo()") 128 | assert_equal "bar", context.call("foo") 129 | end 130 | 131 | def test_this_is_global_scope 132 | assert_equal true, ExecJS.eval("this === (function() {return this})()") 133 | assert_equal true, ExecJS.exec("return this === (function() {return this})()") 134 | end 135 | 136 | def test_commonjs_vars_are_undefined 137 | assert ExecJS.eval("typeof module == 'undefined'") 138 | assert ExecJS.eval("typeof exports == 'undefined'") 139 | assert ExecJS.eval("typeof require == 'undefined'") 140 | end 141 | 142 | def test_console_is_undefined 143 | assert ExecJS.eval("typeof console == 'undefined'") 144 | end 145 | 146 | def test_compile_large_scripts 147 | body = "var foo = 'bar';\n" * 100_000 148 | assert ExecJS.exec("function foo() {\n#{body}\n};\nreturn true") 149 | end 150 | 151 | def test_syntax_error 152 | assert_raise ExecJS::RuntimeError do 153 | ExecJS.exec(")") 154 | end 155 | end 156 | 157 | def test_thrown_exception 158 | assert_raise ExecJS::ProgramError do 159 | ExecJS.exec("throw 'hello'") 160 | end 161 | end 162 | 163 | def test_coffeescript 164 | require "open-uri" 165 | assert source = open("http://jashkenas.github.com/coffee-script/extras/coffee-script.js").read 166 | context = ExecJS.compile(source) 167 | assert_equal 64, context.call("CoffeeScript.eval", "((x) -> x * x)(8)") 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /lib/execjs/external_runtime.rb: -------------------------------------------------------------------------------- 1 | require "shellwords" 2 | require "tempfile" 3 | require "execjs/runtime" 4 | 5 | module ExecJS 6 | class ExternalRuntime < Runtime 7 | class Context < Runtime::Context 8 | def initialize(runtime, source = "") 9 | source = encode(source) 10 | 11 | @runtime = runtime 12 | @source = source 13 | end 14 | 15 | def eval(source, options = {}) 16 | source = encode(source) 17 | 18 | if /\S/ =~ source 19 | exec("return eval(#{JSON.encode("(#{source})")})") 20 | end 21 | end 22 | 23 | def exec(source, options = {}) 24 | source = encode(source) 25 | source = "#{@source}\n#{source}" if @source 26 | 27 | compile_to_tempfile(source) do |file| 28 | extract_result(@runtime.send(:exec_runtime, file.path)) 29 | end 30 | end 31 | 32 | def call(identifier, *args) 33 | eval "#{identifier}.apply(this, #{JSON.encode(args)})" 34 | end 35 | 36 | protected 37 | def compile_to_tempfile(source) 38 | tempfile = Tempfile.open(['execjs', '.js']) 39 | tempfile.write compile(source) 40 | tempfile.close 41 | yield tempfile 42 | ensure 43 | tempfile.close! 44 | end 45 | 46 | def compile(source) 47 | @runtime.send(:runner_source).dup.tap do |output| 48 | output.sub!('#{source}') do 49 | source 50 | end 51 | output.sub!('#{encoded_source}') do 52 | encoded_source = encode_unicode_codepoints(source) 53 | JSON.encode("(function(){ #{encoded_source} })()") 54 | end 55 | output.sub!('#{json2_source}') do 56 | IO.read(ExecJS.root + "/support/json2.js") 57 | end 58 | end 59 | end 60 | 61 | def extract_result(output) 62 | status, value = output.empty? ? [] : JSON.decode(output) 63 | if status == "ok" 64 | value 65 | elsif value =~ /SyntaxError:/ 66 | raise RuntimeError, value 67 | else 68 | raise ProgramError, value 69 | end 70 | end 71 | 72 | if "".respond_to?(:codepoints) 73 | def encode_unicode_codepoints(str) 74 | str.gsub(/[\u0080-\uffff]/) do |ch| 75 | "\\u%04x" % ch.codepoints.to_a 76 | end 77 | end 78 | else 79 | def encode_unicode_codepoints(str) 80 | str.gsub(/([\xC0-\xDF][\x80-\xBF]| 81 | [\xE0-\xEF][\x80-\xBF]{2}| 82 | [\xF0-\xF7][\x80-\xBF]{3})+/nx) do |ch| 83 | "\\u%04x" % ch.unpack("U*") 84 | end 85 | end 86 | end 87 | end 88 | 89 | attr_reader :name 90 | 91 | def initialize(options) 92 | @name = options[:name] 93 | @command = options[:command] 94 | @runner_path = options[:runner_path] 95 | @test_args = options[:test_args] 96 | @test_match = options[:test_match] 97 | @encoding = options[:encoding] 98 | @deprecated = !!options[:deprecated] 99 | @binary = nil 100 | end 101 | 102 | def available? 103 | require "execjs/json" 104 | binary ? true : false 105 | end 106 | 107 | def deprecated? 108 | @deprecated 109 | end 110 | 111 | private 112 | def binary 113 | @binary ||= locate_binary 114 | end 115 | 116 | def locate_executable(cmd) 117 | if ExecJS.windows? && File.extname(cmd) == "" 118 | cmd << ".exe" 119 | end 120 | 121 | if File.executable? cmd 122 | cmd 123 | else 124 | path = ENV['PATH'].split(File::PATH_SEPARATOR).find { |p| 125 | full_path = File.join(p, cmd) 126 | File.executable?(full_path) && File.file?(full_path) 127 | } 128 | path && File.expand_path(cmd, path) 129 | end 130 | end 131 | 132 | protected 133 | def runner_source 134 | @runner_source ||= IO.read(@runner_path) 135 | end 136 | 137 | def exec_runtime(filename) 138 | output = sh("#{shell_escape(*(binary.split(' ') << filename))} 2>&1") 139 | if $?.success? 140 | output 141 | else 142 | raise RuntimeError, output 143 | end 144 | end 145 | 146 | def locate_binary 147 | if binary = which(@command) 148 | if @test_args 149 | output = `#{shell_escape(binary, @test_args)} 2>&1` 150 | binary if output.match(@test_match) 151 | else 152 | binary 153 | end 154 | end 155 | end 156 | 157 | def which(command) 158 | Array(command).find do |name| 159 | name, args = name.split(/\s+/, 2) 160 | path = locate_executable(name) 161 | 162 | next unless path 163 | 164 | args ? "#{path} #{args}" : path 165 | end 166 | end 167 | 168 | if "".respond_to?(:force_encoding) 169 | def sh(command) 170 | output, options = nil, {} 171 | options[:external_encoding] = @encoding if @encoding 172 | options[:internal_encoding] = ::Encoding.default_internal || 'UTF-8' 173 | IO.popen(command, options) { |f| output = f.read } 174 | output 175 | end 176 | else 177 | require "iconv" 178 | 179 | def sh(command) 180 | output = nil 181 | IO.popen(command) { |f| output = f.read } 182 | 183 | if @encoding 184 | Iconv.new('UTF-8', @encoding).iconv(output) 185 | else 186 | output 187 | end 188 | end 189 | end 190 | 191 | if ExecJS.windows? 192 | def shell_escape(*args) 193 | # see http://technet.microsoft.com/en-us/library/cc723564.aspx#XSLTsection123121120120 194 | args.map { |arg| 195 | arg = %Q("#{arg.gsub('"','""')}") if arg.match(/[&|()<>^ "]/) 196 | arg 197 | }.join(" ") 198 | end 199 | else 200 | def shell_escape(*args) 201 | Shellwords.join(args) 202 | end 203 | end 204 | end 205 | end 206 | -------------------------------------------------------------------------------- /lib/execjs/support/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2011-01-18 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, strict: false, regexp: false */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | (function (global) { 163 | if (!global.JSON) { 164 | global.JSON = {}; 165 | } 166 | 167 | var JSON = global.JSON; 168 | 169 | "use strict"; 170 | 171 | function f(n) { 172 | // Format integers to have at least two digits. 173 | return n < 10 ? '0' + n : n; 174 | } 175 | 176 | if (typeof Date.prototype.toJSON !== 'function') { 177 | 178 | Date.prototype.toJSON = function (key) { 179 | 180 | return isFinite(this.valueOf()) ? 181 | this.getUTCFullYear() + '-' + 182 | f(this.getUTCMonth() + 1) + '-' + 183 | f(this.getUTCDate()) + 'T' + 184 | f(this.getUTCHours()) + ':' + 185 | f(this.getUTCMinutes()) + ':' + 186 | f(this.getUTCSeconds()) + 'Z' : null; 187 | }; 188 | 189 | String.prototype.toJSON = 190 | Number.prototype.toJSON = 191 | Boolean.prototype.toJSON = function (key) { 192 | return this.valueOf(); 193 | }; 194 | } 195 | 196 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 197 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 198 | gap, 199 | indent, 200 | meta = { // table of character substitutions 201 | '\b': '\\b', 202 | '\t': '\\t', 203 | '\n': '\\n', 204 | '\f': '\\f', 205 | '\r': '\\r', 206 | '"' : '\\"', 207 | '\\': '\\\\' 208 | }, 209 | rep; 210 | 211 | 212 | function quote(string) { 213 | 214 | // If the string contains no control characters, no quote characters, and no 215 | // backslash characters, then we can safely slap some quotes around it. 216 | // Otherwise we must also replace the offending characters with safe escape 217 | // sequences. 218 | 219 | escapable.lastIndex = 0; 220 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 221 | var c = meta[a]; 222 | return typeof c === 'string' ? c : 223 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 224 | }) + '"' : '"' + string + '"'; 225 | } 226 | 227 | 228 | function str(key, holder) { 229 | 230 | // Produce a string from holder[key]. 231 | 232 | var i, // The loop counter. 233 | k, // The member key. 234 | v, // The member value. 235 | length, 236 | mind = gap, 237 | partial, 238 | value = holder[key]; 239 | 240 | // If the value has a toJSON method, call it to obtain a replacement value. 241 | 242 | if (value && typeof value === 'object' && 243 | typeof value.toJSON === 'function') { 244 | value = value.toJSON(key); 245 | } 246 | 247 | // If we were called with a replacer function, then call the replacer to 248 | // obtain a replacement value. 249 | 250 | if (typeof rep === 'function') { 251 | value = rep.call(holder, key, value); 252 | } 253 | 254 | // What happens next depends on the value's type. 255 | 256 | switch (typeof value) { 257 | case 'string': 258 | return quote(value); 259 | 260 | case 'number': 261 | 262 | // JSON numbers must be finite. Encode non-finite numbers as null. 263 | 264 | return isFinite(value) ? String(value) : 'null'; 265 | 266 | case 'boolean': 267 | case 'null': 268 | 269 | // If the value is a boolean or null, convert it to a string. Note: 270 | // typeof null does not produce 'null'. The case is included here in 271 | // the remote chance that this gets fixed someday. 272 | 273 | return String(value); 274 | 275 | // If the type is 'object', we might be dealing with an object or an array or 276 | // null. 277 | 278 | case 'object': 279 | 280 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 281 | // so watch out for that case. 282 | 283 | if (!value) { 284 | return 'null'; 285 | } 286 | 287 | // Make an array to hold the partial results of stringifying this object value. 288 | 289 | gap += indent; 290 | partial = []; 291 | 292 | // Is the value an array? 293 | 294 | if (Object.prototype.toString.apply(value) === '[object Array]') { 295 | 296 | // The value is an array. Stringify every element. Use null as a placeholder 297 | // for non-JSON values. 298 | 299 | length = value.length; 300 | for (i = 0; i < length; i += 1) { 301 | partial[i] = str(i, value) || 'null'; 302 | } 303 | 304 | // Join all of the elements together, separated with commas, and wrap them in 305 | // brackets. 306 | 307 | v = partial.length === 0 ? '[]' : gap ? 308 | '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : 309 | '[' + partial.join(',') + ']'; 310 | gap = mind; 311 | return v; 312 | } 313 | 314 | // If the replacer is an array, use it to select the members to be stringified. 315 | 316 | if (rep && typeof rep === 'object') { 317 | length = rep.length; 318 | for (i = 0; i < length; i += 1) { 319 | k = rep[i]; 320 | if (typeof k === 'string') { 321 | v = str(k, value); 322 | if (v) { 323 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 324 | } 325 | } 326 | } 327 | } else { 328 | 329 | // Otherwise, iterate through all of the keys in the object. 330 | 331 | for (k in value) { 332 | if (Object.hasOwnProperty.call(value, k)) { 333 | v = str(k, value); 334 | if (v) { 335 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 336 | } 337 | } 338 | } 339 | } 340 | 341 | // Join all of the member texts together, separated with commas, 342 | // and wrap them in braces. 343 | 344 | v = partial.length === 0 ? '{}' : gap ? 345 | '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : 346 | '{' + partial.join(',') + '}'; 347 | gap = mind; 348 | return v; 349 | } 350 | } 351 | 352 | // If the JSON object does not yet have a stringify method, give it one. 353 | 354 | if (typeof JSON.stringify !== 'function') { 355 | JSON.stringify = function (value, replacer, space) { 356 | 357 | // The stringify method takes a value and an optional replacer, and an optional 358 | // space parameter, and returns a JSON text. The replacer can be a function 359 | // that can replace values, or an array of strings that will select the keys. 360 | // A default replacer method can be provided. Use of the space parameter can 361 | // produce text that is more easily readable. 362 | 363 | var i; 364 | gap = ''; 365 | indent = ''; 366 | 367 | // If the space parameter is a number, make an indent string containing that 368 | // many spaces. 369 | 370 | if (typeof space === 'number') { 371 | for (i = 0; i < space; i += 1) { 372 | indent += ' '; 373 | } 374 | 375 | // If the space parameter is a string, it will be used as the indent string. 376 | 377 | } else if (typeof space === 'string') { 378 | indent = space; 379 | } 380 | 381 | // If there is a replacer, it must be a function or an array. 382 | // Otherwise, throw an error. 383 | 384 | rep = replacer; 385 | if (replacer && typeof replacer !== 'function' && 386 | (typeof replacer !== 'object' || 387 | typeof replacer.length !== 'number')) { 388 | throw new Error('JSON.stringify'); 389 | } 390 | 391 | // Make a fake root object containing our value under the key of ''. 392 | // Return the result of stringifying the value. 393 | 394 | return str('', {'': value}); 395 | }; 396 | } 397 | 398 | 399 | // If the JSON object does not yet have a parse method, give it one. 400 | 401 | if (typeof JSON.parse !== 'function') { 402 | JSON.parse = function (text, reviver) { 403 | 404 | // The parse method takes a text and an optional reviver function, and returns 405 | // a JavaScript value if the text is a valid JSON text. 406 | 407 | var j; 408 | 409 | function walk(holder, key) { 410 | 411 | // The walk method is used to recursively walk the resulting structure so 412 | // that modifications can be made. 413 | 414 | var k, v, value = holder[key]; 415 | if (value && typeof value === 'object') { 416 | for (k in value) { 417 | if (Object.hasOwnProperty.call(value, k)) { 418 | v = walk(value, k); 419 | if (v !== undefined) { 420 | value[k] = v; 421 | } else { 422 | delete value[k]; 423 | } 424 | } 425 | } 426 | } 427 | return reviver.call(holder, key, value); 428 | } 429 | 430 | 431 | // Parsing happens in four stages. In the first stage, we replace certain 432 | // Unicode characters with escape sequences. JavaScript handles many characters 433 | // incorrectly, either silently deleting them, or treating them as line endings. 434 | 435 | text = String(text); 436 | cx.lastIndex = 0; 437 | if (cx.test(text)) { 438 | text = text.replace(cx, function (a) { 439 | return '\\u' + 440 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 441 | }); 442 | } 443 | 444 | // In the second stage, we run the text against regular expressions that look 445 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 446 | // because they can cause invocation, and '=' because it can cause mutation. 447 | // But just to be safe, we want to reject all unexpected forms. 448 | 449 | // We split the second stage into 4 regexp operations in order to work around 450 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 451 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 452 | // replace all simple value tokens with ']' characters. Third, we delete all 453 | // open brackets that follow a colon or comma or that begin the text. Finally, 454 | // we look to see that the remaining characters are only whitespace or ']' or 455 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 456 | 457 | if (/^[\],:{}\s]*$/ 458 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 459 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 460 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 461 | 462 | // In the third stage we use the eval function to compile the text into a 463 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 464 | // in JavaScript: it can begin a block or an object literal. We wrap the text 465 | // in parens to eliminate the ambiguity. 466 | 467 | j = eval('(' + text + ')'); 468 | 469 | // In the optional fourth stage, we recursively walk the new structure, passing 470 | // each name/value pair to a reviver function for possible transformation. 471 | 472 | return typeof reviver === 'function' ? 473 | walk({'': j}, '') : j; 474 | } 475 | 476 | // If the text is not JSON parseable, then a SyntaxError is thrown. 477 | 478 | throw new SyntaxError('JSON.parse'); 479 | }; 480 | } 481 | }(this)); 482 | --------------------------------------------------------------------------------