# = json - JSON library for Ruby
#
# == Description
#
# == Author
#
# Florian Frank <mailto:flori@ping.de>
#
# == License
#
# This is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License Version 2 as published by the Free
# Software Foundation: www.gnu.org/copyleft/gpl.html
#
# == Download
#
# The latest version of this library can be downloaded at
#
# * http://rubyforge.org/frs?group_id=953
#
# Online Documentation should be located at
#
# * http://json.rubyforge.org
#
# == Examples
#
# To create a JSON string from a ruby data structure, you
# can call JSON.unparse like that:
#
#  json = JSON.unparse [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
#  # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]"
#
# It's also possible to call the #to_json method directly.
#
#  json = [1, 2, {"a"=>3.141}, false, true, nil, 4..10].to_json
#  # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]"
#
# To get back a ruby data structure, you have to call
# JSON.parse on the JSON string:
#
#  JSON.parse json
#  # => [1, 2, {"a"=>3.141}, false, true, nil, "4..10"]
# 
# Note, that the range from the original data structure is a simple
# string now. The reason for this is, that JSON doesn't support ranges
# or arbitrary classes. In this case the json library falls back to call
# Object#to_json, which is the same as #to_s.to_json.
#
# It's possible to extend JSON to support serialization of arbitray classes by
# simply implementing a more specialized version of the #to_json method, that
# should return a JSON object (a hash converted to JSON with #to_json)
# like this (don't forget the *a for all the arguments):
#
#  class Range
#    def to_json(*a)
#      {
#        'json_class'   => self.class.name,
#        'data'         => [ first, last, exclude_end? ]
#      }.to_json(*a)
#    end
#  end
#
# The hash key 'json_class' is the class, that will be asked to deserialize the
# JSON representation later. In this case it's 'Range', but any namespace of
# the form 'A::B' or '::A::B' will do. All other keys are arbitrary and can be
# used to store the necessary data to configure the object to be deserialized.
#
# If a the key 'json_class' is found in a JSON object, the JSON parser checks
# if the given class responds to the json_create class method. If so, it is
# called with the JSON object converted to a Ruby hash. So a range can
# be deserialized by implementing Range.json_create like this:
# 
#  class Range
#    def self.json_create(o)
#      new(*o['data'])
#    end
#  end
#
# Now it possible to serialize/deserialize ranges as well:
#
#  json = JSON.unparse [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
#  # => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]"
#  JSON.parse json
#  # => [1, 2, {"a"=>3.141}, false, true, nil, 4..10]
#
# JSON.unparse always creates the shortes possible string representation of a
# ruby data structure in one line. This good for data storage or network
# protocols, but not so good for humans to read. Fortunately there's
# also JSON.pretty_unparse that creates a more readable output:
#
#  puts JSON.pretty_unparse([1, 2, {"a"=>3.141}, false, true, nil, 4..10])
#  [
#    1,
#    2,
#    {
#      "a": 3.141
#    },
#    false,
#    true,
#    null,
#    {
#      "json_class": "Range",
#      "data": [
#        4,
#        10,
#        false
#      ]
#    }
#  ]
#
# There are also the methods Kernel#j for unparse, and Kernel#jj for
# pretty_unparse output to the console, that work analogous to Kernel#p and
# Kernel#pp.
#

require 'strscan'

# This module is the namespace for all the JSON related classes. It also 
# defines some module functions to expose a nicer API to users, instead
# of using the parser and other classes directly.
module JSON
  # The base exception for JSON errors.
  JSONError             = Class.new StandardError

  # This exception is raise, if a parser error occurs.
  ParserError           = Class.new JSONError

  # This exception is raise, if a unparser error occurs.
  UnparserError         = Class.new JSONError

  # If a circular data structure is encountered while unparsing
  # this exception is raised.
  CircularDatastructure = Class.new UnparserError

  class << self
    # Switches on Unicode support, if _enable_ is _true_. Otherwise switches
    # Unicode support off.
    def support_unicode=(enable)
      @support_unicode = enable
    end

    # Returns _true_ if JSON supports unicode, otherwise _false_ is returned.
    def support_unicode?
      !!@support_unicode
    end
  end
  JSON.support_unicode = true # default, hower it's possible to switch off full
                              # unicode support, if non-ascii bytes should be
                              # just passed through.

  begin
    require 'iconv'
    # An iconv instance to convert from UTF8 to UTF16 Big Endian.
    UTF16toUTF8 = Iconv.new('utf-8', 'utf-16be')
    # An iconv instance to convert from UTF16 Big Endian to UTF8.
    UTF8toUTF16 = Iconv.new('utf-16be', 'utf-8'); UTF8toUTF16.iconv('no bom')
  rescue LoadError
    JSON.support_unicode = false # enforce disabling of unicode support
  end

  # This class implements the JSON parser that is used to parse a JSON string
  # into a Ruby data structure.
  class Parser < StringScanner
    STRING                = /"((?:[^"\\]|\\.)*)"/
    INTEGER               = /-?\d+/
    FLOAT                 = /-?\d+\.(\d*)(?i:e[+-]?\d+)?/
    OBJECT_OPEN           = /\{/
    OBJECT_CLOSE          = /\}/
    ARRAY_OPEN            = /\[/
    ARRAY_CLOSE           = /\]/
    PAIR_DELIMITER        = /:/
    COLLECTION_DELIMITER  = /,/
    TRUE                  = /true/
    FALSE                 = /false/
    NULL                  = /null/
    IGNORE                = %r(
      (?:
        //[^\n\r]*[\n\r]| # line comments
        /\*               # c-style comments
          (?:
            [^*/]|        # normal chars
            /[^*]|        # slashes that do not start a nested comment
            \*[^/]|       # asterisks that do not end this comment
            /(?=\*/)      # single slash before this comment's end 
          )*
        \*/               # the end of this comment
        |\s+              # whitespaces
      )+
    )mx

    UNPARSED = Object.new

    # Parses the current JSON string and returns the complete data structure
    # as a result.
    def parse
      reset
      until eos?
        case
        when scan(ARRAY_OPEN)
          return parse_array
        when scan(OBJECT_OPEN)
          return parse_object
        when skip(IGNORE)
          ;
        when !((value = parse_value).equal? UNPARSED)
          return value
        else
          raise ParserError, "source '#{peek(20)}' not in JSON!"
        end
      end
    end

    private

    def parse_string
      if scan(STRING)
        return '' if self[1].empty?
        self[1].gsub(/\\(?:[\\bfnrt"]|u([A-Fa-f\d]{4}))/) do
          case $~[0]
          when '\\\\' then '\\'
          when '\\b'  then "\b"
          when '\\f'  then "\f"
          when '\\n'  then "\n"
          when '\\r'  then "\r"
          when '\\t'  then "\t"
          when '\\"'  then '"'
          else
            if JSON.support_unicode? and $KCODE == 'UTF8'
              JSON.utf16_to_utf8($~[1])
            else
              # if utf8 mode is switched off or unicode not supported, try to
              # transform unicode \u-notation to bytes directly:
              $~[1].to_i(16).chr
            end
          end
        end
      else
        UNPARSED
      end
    end

    def parse_value
      case
      when scan(FLOAT)
        Float(self[0])
      when scan(INTEGER)
        Integer(self[0])
      when scan(TRUE)
        true
      when scan(FALSE)
        false
      when scan(NULL)
        nil
      when (string = parse_string) != UNPARSED
        string
      when scan(ARRAY_OPEN)
        parse_array
      when scan(OBJECT_OPEN)
        parse_object
      else
        UNPARSED
      end
    end

    def parse_array
      result = []
      until eos?
        case
        when (value = parse_value) != UNPARSED
          result << value
          skip(IGNORE)
          unless scan(COLLECTION_DELIMITER) or match?(ARRAY_CLOSE)
            raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!"
          end
        when scan(ARRAY_CLOSE)
          break
        when skip(IGNORE)
          ;
        else
          raise ParserError, "unexpected token in array at '#{peek(20)}'!"
        end
      end
      result
    end

    def parse_object
      result = {}
      until eos?
        case
        when (string = parse_string) != UNPARSED
          skip(IGNORE)
          unless scan(PAIR_DELIMITER)
            raise ParserError, "expected ':' in object at '#{peek(20)}'!"
          end
          skip(IGNORE)
          unless (value = parse_value).equal? UNPARSED
            result[string] = value
            skip(IGNORE)
            unless scan(COLLECTION_DELIMITER) or match?(OBJECT_CLOSE)
              raise ParserError,
                "expected ',' or '}' in object at '#{peek(20)}'!"
            end
          else
            raise ParserError, "expected value in object at '#{peek(20)}'!"
          end
        when scan(OBJECT_CLOSE)
          if klassname = result['json_class']
            klass = klassname.sub(/^:+/, '').split(/::/).inject(Object) do |p,k|
              p.const_get(k) rescue nil
            end
            break unless klass and klass.json_creatable?
            result = klass.json_create(result)
          end
          break
        when skip(IGNORE)
          ;
        else
          raise ParserError, "unexpected token in object at '#{peek(20)}'!"
        end
      end
      result
    end
  end

  # This class is used to create State instances, that are use to hold data
  # while unparsing a Ruby data structure into a JSON string.
  class State
    # Creates a State object from _opts_, which ought to be Hash to create a
    # new State instance configured by opts, something else to create an
    # unconfigured instance. If _opts_ is a State object, it is just returned.
    def self.from_state(opts)
      case opts
      when self
        opts
      when Hash
        new(opts)
      else
        new
      end
    end

    # Instantiates a new State object, configured by _opts_.
    def initialize(opts = {})
      @indent     = opts[:indent]     || ''
      @space      = opts[:space]      || ''
      @object_nl  = opts[:object_nl]  || ''
      @array_nl   = opts[:array_nl]   || ''
      @seen       = {}
    end

    # This string is used to indent levels in the JSON string.
    attr_accessor :indent

    # This string is used to include a space between the tokens in a JSON
    # string.
    attr_accessor :space

    # This string is put at the end of a line that holds a JSON object (or
    # Hash).
    attr_accessor :object_nl

    # This string is put at the end of a line that holds a JSON array.
    attr_accessor :array_nl

    # Returns _true_, if _object_ was already seen during this Unparsing run. 
    def seen?(object)
      @seen.key?(object.__id__)
    end

    # Remember _object_, to find out if it was already encountered (to find out
    # if a cyclic data structure is unparsed). 
    def remember(object)
      @seen[object.__id__] = true
    end

    # Forget _object_ for this Unparsing run.
    def forget(object)
      @seen.delete object.__id__
    end
  end

  module_function

  # Convert _string_ from UTF8 encoding to UTF16 (big endian) encoding and
  # return it.
  def utf8_to_utf16(string)
    JSON::UTF8toUTF16.iconv(string).unpack('H*')[0]
  end

  # Convert _string_ from UTF16 (big endian) encoding to UTF8 encoding and
  # return it.
  def utf16_to_utf8(string)
    bytes = '' << string[0, 2].to_i(16) << string[2, 2].to_i(16)
    JSON::UTF16toUTF8.iconv(bytes)
  end

  # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
  # UTF16 big endian characters as \u????, and return it.
  def utf8_to_json(string)
    i, n, result = 0, string.size, ''
    while i < n
      char = string[i]
      case
      when char == ?\b then result << '\b'
      when char == ?\t then result << '\t'
      when char == ?\n then result << '\n'
      when char == ?\f then result << '\f'
      when char == ?\r then result << '\r'
      when char == ?"  then result << '\"'
      when char == ?\\ then result << '\\\\'
      when char.between?(0x0, 0x1f) then result << "\\u%04x" % char
      when char.between?(0x20, 0x7f) then result << char
      when !(JSON.support_unicode? && $KCODE == 'UTF8')
        # if utf8 mode is switched off or unicode not supported, just pass
        # bytes through:
        result << char
      when char & 0xe0 == 0xc0
        result << '\u' << utf8_to_utf16(string[i, 2])
        i += 1
      when char & 0xf0 == 0xe0
        result << '\u' << utf8_to_utf16(string[i, 3])
        i += 2
      when char & 0xf8 == 0xf0
        result << '\u' << utf8_to_utf16(string[i, 4])
        i += 3
      when char & 0xfc == 0xf8
        result << '\u' << utf8_to_utf16(string[i, 5])
        i += 4
      when char & 0xfe == 0xfc
        result << '\u' << utf8_to_utf16(string[i, 6])
        i += 5
      else
        raise JSON::UnparserError, "Encountered unknown UTF-8 byte: %x!" % char
      end
      i += 1
    end
    result
  end

  # Parse the JSON string _source_ into a Ruby data structure and return it.
  def parse(source)
    Parser.new(source).parse
  end

  # Unparse the Ruby data structure _obj_ into a single line JSON string and
  # return it. _state_ is a JSON::State object, that can be used to configure
  # the output further.
  def unparse(obj, state = nil)
    obj.to_json(JSON::State.from_state(state))
  end

  # Unparse the Ruby data structure _obj_ into a JSON string and return it.
  # The returned string is a prettier form of the string returned by #unparse.
  def pretty_unparse(obj)
    state = JSON::State.new(
      :indent     => '  ',
      :space      => ' ',
      :object_nl  => "\n",
      :array_nl   => "\n"
    )
    obj.to_json(state)
  end
end

class Object
  # Converts this object to a string (calling #to_s), converts
  # it to a JSON string, and returns the result. This is a fallback, if no
  # special method #to_json was defined for some object.
  # _state_ is a JSON::State object, that can also be used
  # to configure the produced JSON string output further.

  def to_json(*) to_s.to_json end
end

class Hash
  # Returns a JSON string containing a JSON object, that is unparsed from
  # this Hash instance.
  # _state_ is a JSON::State object, that can also be used to configure the
  # produced JSON string output further.
  # _depth_ is used to find out nesting depth, to indent accordingly.
  def to_json(state = nil, depth = 0)
    state = JSON::State.from_state(state)
    json_check_circular(state) { json_transform(state, depth) }
  end

  private

  def json_check_circular(state)
    if state
      state.seen?(self) and raise JSON::CircularDatastructure,
          "circular data structures not supported!"
      state.remember self
    end
    yield
  ensure
    state and state.forget self
  end

  def json_shift(state, depth)
    state and not state.object_nl.empty? or return ''
    state.indent * depth
  end

  def json_transform(state, depth)
    delim = ','
    delim << state.object_nl if state
    result = '{'
    result << state.object_nl if state
    result << map { |key,value|
      json_shift(state, depth + 1) <<
        key.to_s.to_json(state, depth + 1) <<
        ':' << state.space << value.to_json(state, depth + 1)
    }.join(delim)
    result << state.object_nl if state
    result << json_shift(state, depth)
    result << '}'
    result
  end
end

class Array
  # Returns a JSON string containing a JSON array, that is unparsed from
  # this Array instance.
  # _state_ is a JSON::State object, that can also be used to configure the
  # produced JSON string output further.
  # _depth_ is used to find out nesting depth, to indent accordingly.
  def to_json(state = nil, depth = 0)
    state = JSON::State.from_state(state)
    json_check_circular(state) { json_transform(state, depth) }
  end

  private

  def json_check_circular(state)
    if state
      state.seen?(self) and raise JSON::CircularDatastructure,
        "circular data structures not supported!"
      state.remember self
    end
    yield
  ensure
    state and state.forget self
  end

  def json_shift(state, depth)
    state and not state.array_nl.empty? or return ''
    state.indent * depth
  end

  def json_transform(state, depth)
    delim = ','
    delim << state.array_nl if state
    result = '['
    result << state.array_nl if state
    result << map { |value|
      json_shift(state, depth + 1) << value.to_json(state, depth + 1)
    }.join(delim)
    result << state.array_nl if state
    result << json_shift(state, depth) 
    result << ']'
    result
  end
end

class Integer
  # Returns a JSON string representation for this Integer number.
  def to_json(*) to_s end
end

class Float
  # Returns a JSON string representation for this Float number.
  def to_json(*) to_s end
end

class String
  # This string should be encoded with UTF-8 (if JSON unicode support is
  # enabled). A call to this method returns a JSON string
  # encoded with UTF16 big endian characters as \u????. If
  # JSON.support_unicode? is false only control characters are encoded this
  # way, all 8-bit bytes are just passed through.
  def to_json(*)
    '"' << JSON::utf8_to_json(self) << '"'
  end

  # Raw Strings are JSON Objects (the raw bytes are stored in an array for the
  # key "raw"). The Ruby String can be created by this class method.
  def self.json_create(o)
    o['raw'].pack('C*')
  end

  # This method creates a raw object, that can be nested into other data
  # structures and will be unparsed as a raw string.
  def to_json_raw_object
    {
      'json_class'  => self.class.name,
      'raw'         => self.unpack('C*'),
    }
  end

  # This method should be used, if you want to convert raw strings to JSON
  # instead of UTF-8 strings, e. g. binary data (and JSON Unicode support is
  # enabled).
  def to_json_raw(*args)
    to_json_raw_object.to_json(*args)
  end
end

class TrueClass
  # Returns a JSON string for true: 'true'.
  def to_json(*) to_s end
end

class FalseClass
  # Returns a JSON string for false: 'false'.
  def to_json(*) to_s end
end

class NilClass
  # Returns a JSON string for nil: 'null'.
  def to_json(*) 'null' end
end

module Kernel
  # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in
  # one line.
  def j(*objs)
    objs.each do |obj|
      puts JSON::unparse(obj)
    end
    nil
  end

  # Ouputs _objs_ to STDOUT as JSON strings in a pretty format, with
  # indentation and over many lines.
  def jj(*objs)
    objs.each do |obj|
      puts JSON::pretty_unparse(obj)
    end
    nil
  end
end

class Class
  # Returns true, if this class can be used to create an instance
  # from a serialised JSON string. The class has to implement a class
  # method _json_create_ that expects a hash as first parameter, which includes
  # the required data.
  def json_creatable?
    respond_to?(:json_create)
  end
end
  # vim: set et sw=2 ts=2:
