[
  {
    "path": ".github/workflows/ruby.yml",
    "content": "on: [push, pull_request]\nname: Build\njobs:\n  test:\n    name: rake test\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        ruby-version:\n          - head\n          - \"3.3\"\n          - \"3.2\"\n          - \"3.1\"\n          - \"3.0\"\n          - \"2.7\"\n          - \"2.6\"\n    steps:\n      - uses: actions/checkout@v4\n      - uses: ruby/setup-ruby@v1\n        with:\n          ruby-version: ${{ matrix.ruby-version }}\n          bundler-cache: true\n      - run: |\n          bundle exec rake test\n"
  },
  {
    "path": ".gitignore",
    "content": "/.bundle/\n/.yardoc\n/Gemfile.lock\n/_yardoc/\n/coverage/\n/doc/\n/pkg/\n/spec/reports/\n/tmp/\n*.gem\n"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\ngemspec\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2010-2024 David Graham\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# JSON::Stream\n\nJSON::Stream is a JSON parser, based on a finite state machine, that generates\nevents for each state change. This allows streaming both the JSON document into\nmemory and the parsed object graph out of memory to some other process.\n\nThis is much like an XML SAX parser that generates events during parsing. There\nis no requirement for the document, or the object graph, to be fully buffered in\nmemory. This is best suited for huge JSON documents that won't fit in memory.\nFor example, streaming and processing large map/reduce views from Apache\nCouchDB.\n\n## Usage\n\nThe simplest way to parse is to read the full JSON document into memory\nand then parse it into a full object graph. This is fine for small documents\nbecause we have room for both the document and parsed object in memory.\n\n```ruby\nrequire 'json/stream'\njson = File.read('/tmp/test.json')\nobj = JSON::Stream::Parser.parse(json)\n```\n\nWhile it's possible to do this with JSON::Stream, we really want to use the json\ngem for documents like this. JSON.parse() is much faster than this parser,\nbecause it can rely on having the entire document in memory to analyze.\n\nFor larger documents we can use an IO object to stream it into the parser.\nWe still need room for the parsed object, but the document itself is never\nfully read into memory.\n\n```ruby\nrequire 'json/stream'\nstream = File.open('/tmp/test.json')\nobj = JSON::Stream::Parser.parse(stream)\n```\n\nAgain, while JSON::Stream can be used this way, if we just need to stream the\ndocument from disk or the network, we're better off using the yajl-ruby gem.\n\nHuge documents arriving over the network in small chunks to an EventMachine\n`receive_data` loop is where JSON::Stream is really useful. Inside an\nEventMachine::Connection subclass we might have:\n\n```ruby\ndef post_init\n  @parser = JSON::Stream::Parser.new do\n    start_document { puts \"start document\" }\n    end_document   { puts \"end document\" }\n    start_object   { puts \"start object\" }\n    end_object     { puts \"end object\" }\n    start_array    { puts \"start array\" }\n    end_array      { puts \"end array\" }\n    key            { |k| puts \"key: #{k}\" }\n    value          { |v| puts \"value: #{v}\" }\n  end\nend\n\ndef receive_data(data)\n  begin\n    @parser << data\n  rescue JSON::Stream::ParserError => e\n    close_connection\n  end\nend\n```\n\nThe parser accepts chunks of the JSON document and parses up to the end of the\navailable buffer. Passing in more data resumes the parse from the prior state.\nWhen an interesting state change happens, the parser notifies all registered\ncallback procs of the event.\n\nThe event callback is where we can do interesting data filtering and passing\nto other processes. The above example simply prints state changes, but\nimagine the callbacks looking for an array named `rows` and processing sets\nof these row objects in small batches. Millions of rows, streaming over the\nnetwork, can be processed in constant memory space this way.\n\n## Alternatives\n\n* [json](https://github.com/flori/json)\n* [yajl-ruby](https://github.com/brianmario/yajl-ruby)\n* [yajl-ffi](https://github.com/dgraham/yajl-ffi)\n* [application/json-seq](http://www.rfc-editor.org/rfc/rfc7464.txt)\n\n## Development\n\n```\n$ bin/setup\n$ bin/rake test\n```\n\n## License\n\nJSON::Stream is released under the MIT license. Check the LICENSE file for details.\n"
  },
  {
    "path": "Rakefile",
    "content": "require 'rake'\nrequire 'rake/clean'\nrequire 'rake/testtask'\n\nCLOBBER.include('pkg')\n\ndirectory 'pkg'\n\ndesc 'Build distributable packages'\ntask :build => [:pkg] do\n  system 'gem build json-stream.gemspec && mv json-*.gem pkg/'\nend\n\nRake::TestTask.new(:test) do |test|\n  test.libs << 'spec'\n  test.pattern = 'spec/**/*_spec.rb'\n  test.warning = true\nend\n\ntask :default => [:clobber, :test, :build]\n"
  },
  {
    "path": "bin/bundler",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n#\n# This file was generated by Bundler.\n#\n# The application 'bundler' is installed as part of a gem, and\n# this file is here to facilitate running it.\n#\n\nrequire \"pathname\"\nENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../../Gemfile\",\n  Pathname.new(__FILE__).realpath)\n\nrequire \"rubygems\"\nrequire \"bundler/setup\"\n\nload Gem.bin_path(\"bundler\", \"bundler\")\n"
  },
  {
    "path": "bin/console",
    "content": "#!/usr/bin/env ruby\n\nrequire \"bundler/setup\"\nrequire \"json/stream\"\n\n# You can add fixtures and/or initialization code here to make experimenting\n# with your gem easier. You can also use a different console, if you like.\n\n# (If you use this, don't forget to add pry to your Gemfile!)\n# require \"pry\"\n# Pry.start\n\nrequire \"irb\"\nIRB.start(__FILE__)\n"
  },
  {
    "path": "bin/rake",
    "content": "#!/usr/bin/env ruby\n# frozen_string_literal: true\n#\n# This file was generated by Bundler.\n#\n# The application 'rake' is installed as part of a gem, and\n# this file is here to facilitate running it.\n#\n\nrequire \"pathname\"\nENV[\"BUNDLE_GEMFILE\"] ||= File.expand_path(\"../../Gemfile\",\n  Pathname.new(__FILE__).realpath)\n\nrequire \"rubygems\"\nrequire \"bundler/setup\"\n\nload Gem.bin_path(\"rake\", \"rake\")\n"
  },
  {
    "path": "bin/setup",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\nIFS=$'\\n\\t'\nset -vx\n\nbundle install\n"
  },
  {
    "path": "json-stream.gemspec",
    "content": "require './lib/json/stream/version'\n\nGem::Specification.new do |s|\n  s.name        = 'json-stream'\n  s.version     = JSON::Stream::VERSION\n  s.summary     = %q[A streaming JSON parser that generates SAX-like events.]\n  s.description = %q[A parser best suited for huge JSON documents that don't fit in memory.]\n\n  s.authors      = ['David Graham']\n  s.email        = %w[david.malcom.graham@gmail.com]\n  s.homepage     = 'http://dgraham.github.io/json-stream/'\n  s.license      = 'MIT'\n\n  s.files        = Dir['[A-Z]*', 'json-stream.gemspec', '{lib}/**/*'] - ['Gemfile.lock']\n  s.require_path = 'lib'\n\n  s.add_development_dependency 'bundler', '~> 2.2'\n  s.add_development_dependency 'minitest', '~> 5.22'\n  s.add_development_dependency 'rake', '~> 13.2'\n\n  s.required_ruby_version = '>= 2.6.0'\nend\n"
  },
  {
    "path": "lib/json/stream/buffer.rb",
    "content": "module JSON\n  module Stream\n    # A character buffer that expects a UTF-8 encoded stream of bytes.\n    # This handles truncated multi-byte characters properly so we can just\n    # feed it binary data and receive a properly formatted UTF-8 String as\n    # output.\n    #\n    # More UTF-8 parsing details are available at:\n    #\n    #   http://en.wikipedia.org/wiki/UTF-8\n    #   http://tools.ietf.org/html/rfc3629#section-3\n    class Buffer\n      def initialize\n        @state = :start\n        @buffer = []\n        @need = 0\n      end\n\n      # Fill the buffer with a String of binary UTF-8 encoded bytes. Returns\n      # as much of the data in a UTF-8 String as we have. Truncated multi-byte\n      # characters are saved in the buffer until the next call to this method\n      # where we expect to receive the rest of the multi-byte character.\n      #\n      # data - The partial binary encoded String data.\n      #\n      # Raises JSON::Stream::ParserError if the UTF-8 byte sequence is malformed.\n      #\n      # Returns a UTF-8 encoded String.\n      def <<(data)\n        # Avoid state machine for complete UTF-8.\n        if @buffer.empty?\n          data.force_encoding(Encoding::UTF_8)\n          return data if data.valid_encoding?\n        end\n\n        bytes = []\n        data.each_byte do |byte|\n          case @state\n          when :start\n            if byte < 128\n              bytes << byte\n            elsif byte >= 192\n              @state = :multi_byte\n              @buffer << byte\n              @need =\n                case\n                when byte >= 240 then 4\n                when byte >= 224 then 3\n                when byte >= 192 then 2\n                end\n            else\n              error('Expected start of multi-byte or single byte char')\n            end\n          when :multi_byte\n            if byte > 127 && byte < 192\n              @buffer << byte\n              if @buffer.size == @need\n                bytes += @buffer.slice!(0, @buffer.size)\n                @state = :start\n              end\n            else\n              error('Expected continuation byte')\n            end\n          end\n        end\n\n        # Build UTF-8 encoded string from completed codepoints.\n        bytes.pack('C*').force_encoding(Encoding::UTF_8).tap do |text|\n          error('Invalid UTF-8 byte sequence') unless text.valid_encoding?\n        end\n      end\n\n      # Determine if the buffer contains partial UTF-8 continuation bytes that\n      # are waiting on subsequent completion bytes before a full codepoint is\n      # formed.\n      #\n      # Examples\n      #\n      #   bytes = \"é\".bytes\n      #\n      #   buffer << bytes[0]\n      #   buffer.empty?\n      #   # => false\n      #\n      #   buffer << bytes[1]\n      #   buffer.empty?\n      #   # => true\n      #\n      # Returns true if the buffer is empty.\n      def empty?\n        @buffer.empty?\n      end\n\n      private\n\n      def error(message)\n        raise ParserError, message\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/json/stream/builder.rb",
    "content": "module JSON\n  module Stream\n    # A parser listener that builds a full, in memory, object from a JSON\n    # document. This is similar to using the json gem's `JSON.parse` method.\n    #\n    # Examples\n    #\n    #   parser = JSON::Stream::Parser.new\n    #   builder = JSON::Stream::Builder.new(parser)\n    #   parser << '{\"answer\": 42, \"question\": false}'\n    #   obj = builder.result\n    class Builder\n      METHODS = %w[start_document end_document start_object end_object start_array end_array key value]\n\n      attr_reader :result\n\n      def initialize(parser)\n        METHODS.each do |name|\n          parser.send(name, &method(name))\n        end\n      end\n\n      def start_document\n        @stack = []\n        @keys = []\n        @result = nil\n      end\n\n      def end_document\n        @result = @stack.pop\n      end\n\n      def start_object\n        @stack.push({})\n      end\n\n      def end_object\n        return if @stack.size == 1\n\n        node = @stack.pop\n        top = @stack[-1]\n\n        case top\n        when Hash\n          top[@keys.pop] = node\n        when Array\n          top << node\n        end\n      end\n      alias :end_array :end_object\n\n      def start_array\n        @stack.push([])\n      end\n\n      def key(key)\n        @keys << key\n      end\n\n      def value(value)\n        top = @stack[-1]\n        case top\n        when Hash\n          top[@keys.pop] = value\n        when Array\n          top << value\n        else\n          @stack << value\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/json/stream/parser.rb",
    "content": "module JSON\n  module Stream\n    # Raised on any invalid JSON text.\n    ParserError = Class.new(RuntimeError)\n\n    # A streaming JSON parser that generates SAX-like events for state changes.\n    # Use the json gem for small documents. Use this for huge documents that\n    # won't fit in memory.\n    #\n    # Examples\n    #\n    #   parser = JSON::Stream::Parser.new\n    #   parser.key { |key| puts key }\n    #   parser.value { |value| puts value }\n    #   parser << '{\"answer\":'\n    #   parser << ' 42}'\n    class Parser\n      BUF_SIZE      = 4096\n      CONTROL       = /[\\x00-\\x1F]/\n      WS            = /[ \\n\\t\\r]/\n      HEX           = /[0-9a-fA-F]/\n      DIGIT         = /[0-9]/\n      DIGIT_1_9     = /[1-9]/\n      DIGIT_END     = /\\d$/\n      TRUE_RE       = /[rue]/\n      FALSE_RE      = /[alse]/\n      NULL_RE       = /[ul]/\n      TRUE_KEYWORD  = 'true'\n      FALSE_KEYWORD = 'false'\n      NULL_KEYWORD  = 'null'\n      LEFT_BRACE    = '{'\n      RIGHT_BRACE   = '}'\n      LEFT_BRACKET  = '['\n      RIGHT_BRACKET = ']'\n      BACKSLASH     = '\\\\'\n      SLASH         = '/'\n      QUOTE         = '\"'\n      COMMA         = ','\n      COLON         = ':'\n      ZERO          = '0'\n      MINUS         = '-'\n      PLUS          = '+'\n      POINT         = '.'\n      EXPONENT      = /[eE]/\n      B,F,N,R,T,U   = %w[b f n r t u]\n\n      # Parses a full JSON document from a String or an IO stream and returns\n      # the parsed object graph. For parsing small JSON documents with small\n      # memory requirements, use the json gem's faster JSON.parse method instead.\n      #\n      # json - The String or IO containing JSON data.\n      #\n      # Examples\n      #\n      #   JSON::Stream::Parser.parse('{\"hello\": \"world\"}')\n      #   # => {\"hello\": \"world\"}\n      #\n      # Raises a JSON::Stream::ParserError if the JSON data is malformed.\n      #\n      # Returns a Hash.\n      def self.parse(json)\n        stream = json.is_a?(String) ? StringIO.new(json) : json\n        parser = Parser.new\n        builder = Builder.new(parser)\n        while (buf = stream.read(BUF_SIZE)) != nil\n          parser << buf\n        end\n        parser.finish\n        builder.result\n      ensure\n        stream.close\n      end\n\n      # Create a new parser with an optional initialization block where\n      # we can register event callbacks.\n      #\n      # Examples\n      #\n      #   parser = JSON::Stream::Parser.new do\n      #     start_document { puts \"start document\" }\n      #     end_document   { puts \"end document\" }\n      #     start_object   { puts \"start object\" }\n      #     end_object     { puts \"end object\" }\n      #     start_array    { puts \"start array\" }\n      #     end_array      { puts \"end array\" }\n      #     key            { |k| puts \"key: #{k}\" }\n      #     value          { |v| puts \"value: #{v}\" }\n      #   end\n      def initialize(&block)\n        @state = :start_document\n        @utf8 = Buffer.new\n        @listeners = {\n          start_document: [],\n          end_document: [],\n          start_object: [],\n          end_object: [],\n          start_array: [],\n          end_array: [],\n          key: [],\n          value: []\n        }\n\n        # Track parse stack.\n        @stack = []\n        @unicode = \"\"\n        @buf = \"\"\n        @pos = -1\n\n        # Register any observers in the block.\n        instance_eval(&block) if block_given?\n      end\n\n      def start_document(&block)\n        @listeners[:start_document] << block\n      end\n\n      def end_document(&block)\n        @listeners[:end_document] << block\n      end\n\n      def start_object(&block)\n        @listeners[:start_object] << block\n      end\n\n      def end_object(&block)\n        @listeners[:end_object] << block\n      end\n\n      def start_array(&block)\n        @listeners[:start_array] << block\n      end\n\n      def end_array(&block)\n        @listeners[:end_array] << block\n      end\n\n      def key(&block)\n        @listeners[:key] << block\n      end\n\n      def value(&block)\n        @listeners[:value] << block\n      end\n\n      # Pass data into the parser to advance the state machine and\n      # generate callback events. This is well suited for an EventMachine\n      # receive_data loop.\n      #\n      # data - The String of partial JSON data to parse.\n      #\n      # Raises a JSON::Stream::ParserError if the JSON data is malformed.\n      #\n      # Returns nothing.\n      def <<(data)\n        (@utf8 << data).each_char do |ch|\n          @pos += 1\n          case @state\n          when :start_document\n            start_value(ch)\n          when :start_object\n            case ch\n            when QUOTE\n              @state = :start_string\n              @stack.push(:key)\n            when RIGHT_BRACE\n              end_container(:object)\n            when WS\n              # ignore\n            else\n              error('Expected object key start')\n            end\n          when :start_string\n            case ch\n            when QUOTE\n              if @stack.pop == :string\n                end_value(@buf)\n              else # :key\n                @state = :end_key\n                notify(:key, @buf)\n              end\n              @buf = \"\"\n            when BACKSLASH\n              @state = :start_escape\n            when CONTROL\n              error('Control characters must be escaped')\n            else\n              @buf << ch\n            end\n          when :start_escape\n            case ch\n            when QUOTE, BACKSLASH, SLASH\n              @buf << ch\n              @state = :start_string\n            when B\n              @buf << \"\\b\"\n              @state = :start_string\n            when F\n              @buf << \"\\f\"\n              @state = :start_string\n            when N\n              @buf << \"\\n\"\n              @state = :start_string\n            when R\n              @buf << \"\\r\"\n              @state = :start_string\n            when T\n              @buf << \"\\t\"\n              @state = :start_string\n            when U\n              @state = :unicode_escape\n            else\n              error('Expected escaped character')\n            end\n          when :unicode_escape\n            case ch\n            when HEX\n              @unicode << ch\n              if @unicode.size == 4\n                codepoint = @unicode.slice!(0, 4).hex\n                if codepoint >= 0xD800 && codepoint <= 0xDBFF\n                  error('Expected low surrogate pair half') if @stack[-1].is_a?(Integer)\n                  @state = :start_surrogate_pair\n                  @stack.push(codepoint)\n                elsif codepoint >= 0xDC00 && codepoint <= 0xDFFF\n                  high = @stack.pop\n                  error('Expected high surrogate pair half') unless high.is_a?(Integer)\n                  pair = ((high - 0xD800) * 0x400) + (codepoint - 0xDC00) + 0x10000\n                  @buf << pair\n                  @state = :start_string\n                else\n                  @buf << codepoint\n                  @state = :start_string\n                end\n              end\n            else\n              error('Expected unicode escape hex digit')\n            end\n          when :start_surrogate_pair\n            case ch\n            when BACKSLASH\n              @state = :start_surrogate_pair_u\n            else\n              error('Expected low surrogate pair half')\n            end\n          when :start_surrogate_pair_u\n            case ch\n            when U\n              @state = :unicode_escape\n            else\n              error('Expected low surrogate pair half')\n            end\n          when :start_negative_number\n            case ch\n            when ZERO\n              @state = :start_zero\n              @buf << ch\n            when DIGIT_1_9\n              @state = :start_int\n              @buf << ch\n            else\n              error('Expected 0-9 digit')\n            end\n          when :start_zero\n            case ch\n            when POINT\n              @state = :start_float\n              @buf << ch\n            when EXPONENT\n              @state = :start_exponent\n              @buf << ch\n            else\n              end_value(@buf.to_i)\n              @buf = \"\"\n              @pos -= 1\n              redo\n            end\n          when :start_float\n            case ch\n            when DIGIT\n              @state = :in_float\n              @buf << ch\n            else\n              error('Expected 0-9 digit')\n            end\n          when :in_float\n            case ch\n            when DIGIT\n              @buf << ch\n            when EXPONENT\n              @state = :start_exponent\n              @buf << ch\n            else\n              end_value(@buf.to_f)\n              @buf = \"\"\n              @pos -= 1\n              redo\n            end\n          when :start_exponent\n            case ch\n            when MINUS, PLUS, DIGIT\n              @state = :in_exponent\n              @buf << ch\n            else\n              error('Expected +, -, or 0-9 digit')\n            end\n          when :in_exponent\n            case ch\n            when DIGIT\n              @buf << ch\n            else\n              error('Expected 0-9 digit') unless @buf =~ DIGIT_END\n              end_value(@buf.to_f)\n              @buf = \"\"\n              @pos -= 1\n              redo\n            end\n          when :start_int\n            case ch\n            when DIGIT\n              @buf << ch\n            when POINT\n              @state = :start_float\n              @buf << ch\n            when EXPONENT\n              @state = :start_exponent\n              @buf << ch\n            else\n              end_value(@buf.to_i)\n              @buf = \"\"\n              @pos -= 1\n              redo\n            end\n          when :start_true\n            keyword(TRUE_KEYWORD, true, TRUE_RE, ch)\n          when :start_false\n            keyword(FALSE_KEYWORD, false, FALSE_RE, ch)\n          when :start_null\n            keyword(NULL_KEYWORD, nil, NULL_RE, ch)\n          when :end_key\n            case ch\n            when COLON\n              @state = :key_sep\n            when WS\n              # ignore\n            else\n              error('Expected colon key separator')\n            end\n          when :key_sep\n            start_value(ch)\n          when :start_array\n            case ch\n            when RIGHT_BRACKET\n              end_container(:array)\n            when WS\n              # ignore\n            else\n              start_value(ch)\n            end\n          when :end_value\n            case ch\n            when COMMA\n              @state = :value_sep\n            when RIGHT_BRACE\n              end_container(:object)\n            when RIGHT_BRACKET\n              end_container(:array)\n            when WS\n              # ignore\n            else\n              error('Expected comma or object or array close')\n            end\n          when :value_sep\n            if @stack[-1] == :object\n              case ch\n              when QUOTE\n                @state = :start_string\n                @stack.push(:key)\n              when WS\n                # ignore\n              else\n                error('Expected object key start')\n              end\n            else\n              start_value(ch)\n            end\n          when :end_document\n            error('Unexpected data') unless ch =~ WS\n          end\n        end\n      end\n\n      # Drain any remaining buffered characters into the parser to complete\n      # the parsing of the document.\n      #\n      # This is only required when parsing a document containing a single\n      # numeric value, integer or float. The parser has no other way to\n      # detect when it should no longer expect additional characters with\n      # which to complete the parse, so it must be signaled by a call to\n      # this method.\n      #\n      # If you're parsing more typical object or array documents, there's no\n      # need to call `finish` because the parse will complete when the final\n      # closing `]` or `}` character is scanned.\n      #\n      # Raises a JSON::Stream::ParserError if the JSON data is malformed.\n      #\n      # Returns nothing.\n      def finish\n        # Partial multi-byte character waiting for completion bytes.\n        error('Unexpected end-of-file') unless @utf8.empty?\n\n        # Partial array, object, or string.\n        error('Unexpected end-of-file') unless @stack.empty?\n\n        case @state\n        when :end_document\n          # done, do nothing\n        when :in_float\n          end_value(@buf.to_f)\n        when :in_exponent\n          error('Unexpected end-of-file') unless @buf =~ DIGIT_END\n          end_value(@buf.to_f)\n        when :start_zero\n          end_value(@buf.to_i)\n        when :start_int\n          end_value(@buf.to_i)\n        else\n          error('Unexpected end-of-file')\n        end\n      end\n\n      private\n\n      # Invoke all registered observer procs for the event type.\n      #\n      # type - The Symbol listener name.\n      # args - The argument list to pass into the observer procs.\n      #\n      # Examples\n      #\n      #    # broadcast events for {\"answer\": 42}\n      #    notify(:start_object)\n      #    notify(:key, \"answer\")\n      #    notify(:value, 42)\n      #    notify(:end_object)\n      #\n      # Returns nothing.\n      def notify(type, *args)\n        @listeners[type].each do |block|\n          block.call(*args)\n        end\n      end\n\n      # Complete an object or array container value type.\n      #\n      # type - The Symbol, :object or :array, of the expected type.\n      #\n      # Raises a JSON::Stream::ParserError if the expected container type\n      #   was not completed.\n      #\n      # Returns nothing.\n      def end_container(type)\n        @state = :end_value\n        if @stack.pop == type\n          case type\n          when :object then notify(:end_object)\n          when :array  then notify(:end_array)\n          end\n        else\n          error(\"Expected end of #{type}\")\n        end\n        notify_end_document if @stack.empty?\n      end\n\n      # Broadcast an `end_document` event to observers after a complete JSON\n      # value document (object, array, number, string, true, false, null) has\n      # been parsed from the text. This is the final event sent to observers\n      # and signals the parse has finished.\n      #\n      # Returns nothing.\n      def notify_end_document\n        @state = :end_document\n        notify(:end_document)\n      end\n\n      # Parse one of the three allowed keywords: true, false, null.\n      #\n      # word  - The String keyword ('true', 'false', 'null').\n      # value - The Ruby value (true, false, nil).\n      # re    - The Regexp of allowed keyword characters.\n      # ch    - The current String character being parsed.\n      #\n      # Raises a JSON::Stream::ParserError if the character does not belong\n      #   in the expected keyword.\n      #\n      # Returns nothing.\n      def keyword(word, value, re, ch)\n        if ch =~ re\n          @buf << ch\n        else\n          error(\"Expected #{word} keyword\")\n        end\n\n        if @buf.size == word.size\n          if @buf == word\n            @buf = \"\"\n            end_value(value)\n          else\n            error(\"Expected #{word} keyword\")\n          end\n        end\n      end\n\n      # Process the first character of one of the seven possible JSON\n      # values: object, array, string, true, false, null, number.\n      #\n      # ch - The current character String.\n      #\n      # Raises a JSON::Stream::ParserError if the character does not signal\n      #   the start of a value.\n      #\n      # Returns nothing.\n      def start_value(ch)\n        case ch\n        when LEFT_BRACE\n          notify(:start_document) if @stack.empty?\n          @state = :start_object\n          @stack.push(:object)\n          notify(:start_object)\n        when LEFT_BRACKET\n          notify(:start_document) if @stack.empty?\n          @state = :start_array\n          @stack.push(:array)\n          notify(:start_array)\n        when QUOTE\n          @state = :start_string\n          @stack.push(:string)\n        when T\n          @state = :start_true\n          @buf << ch\n        when F\n          @state = :start_false\n          @buf << ch\n        when N\n          @state = :start_null\n          @buf << ch\n        when MINUS\n          @state = :start_negative_number\n          @buf << ch\n        when ZERO\n          @state = :start_zero\n          @buf << ch\n        when DIGIT_1_9\n          @state = :start_int\n          @buf << ch\n        when WS\n          # ignore\n        else\n          error('Expected value')\n        end\n      end\n\n      # Advance the state machine and notify `value` observers that a\n      # string, number or keyword (true, false, null) value was parsed.\n      #\n      # value - The object to broadcast to observers.\n      #\n      # Returns nothing.\n      def end_value(value)\n        @state = :end_value\n        notify(:start_document) if @stack.empty?\n        notify(:value, value)\n        notify_end_document if @stack.empty?\n      end\n\n      def error(message)\n        raise ParserError, \"#{message}: char #{@pos}\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/json/stream/version.rb",
    "content": "module JSON\n  module Stream\n    VERSION = '1.0.0'\n  end\nend\n"
  },
  {
    "path": "lib/json/stream.rb",
    "content": "# encoding: UTF-8\n\nrequire 'stringio'\nrequire 'json/stream/buffer'\nrequire 'json/stream/builder'\nrequire 'json/stream/parser'\nrequire 'json/stream/version'\n"
  },
  {
    "path": "spec/buffer_spec.rb",
    "content": "require 'json/stream'\nrequire 'minitest/autorun'\n\ndescribe JSON::Stream::Buffer do\n  subject { JSON::Stream::Buffer.new }\n\n  it 'accepts single byte characters' do\n    assert_equal \"\", subject << \"\"\n    assert_equal \"abc\", subject << \"abc\"\n    assert_equal \"\\u0000abc\", subject << \"\\u0000abc\"\n  end\n\n  # The é character can be a single codepoint \\u00e9 or two codepoints\n  # \\u0065\\u0301. The first is encoded in 2 bytes, the second in 3 bytes.\n  # The json and yajl-ruby gems and CouchDB do not normalize unicode text\n  # so neither will we. Although, a good way to normalize is by calling\n  # ActiveSupport::Multibyte::Chars.new(\"é\").normalize(:c).\n  it 'accepts combined characters' do\n    assert_equal \"\\u0065\\u0301\", subject << \"\\u0065\\u0301\"\n    assert_equal 3, (subject << \"\\u0065\\u0301\").bytesize\n    assert_equal 2, (subject << \"\\u0065\\u0301\").size\n\n    assert_equal \"\\u00e9\", subject << \"\\u00e9\"\n    assert_equal 2, (subject << \"\\u00e9\").bytesize\n    assert_equal 1, (subject << \"\\u00e9\").size\n  end\n\n  it 'accepts valid two byte characters' do\n    assert_equal \"abcé\", subject << \"abcé\"\n    assert_equal \"a\", subject << \"a\\xC3\"\n    assert_equal \"é\", subject << \"\\xA9\"\n    assert_equal \"\", subject << \"\\xC3\"\n    assert_equal \"é\", subject << \"\\xA9\"\n    assert_equal \"é\", subject << \"\\xC3\\xA9\"\n  end\n\n  it 'accepts valid three byte characters' do\n    assert_equal \"abcé\\u2603\", subject << \"abcé\\u2603\"\n    assert_equal \"a\", subject << \"a\\xE2\"\n    assert_equal \"\", subject << \"\\x98\"\n    assert_equal \"\\u2603\", subject << \"\\x83\"\n  end\n\n  it 'accepts valid four byte characters' do\n    assert_equal \"abcé\\u2603\\u{10102}é\", subject << \"abcé\\u2603\\u{10102}é\"\n    assert_equal \"a\", subject << \"a\\xF0\"\n    assert_equal \"\", subject << \"\\x90\"\n    assert_equal \"\", subject << \"\\x84\"\n    assert_equal \"\\u{10102}\", subject << \"\\x82\"\n  end\n\n  it 'rejects valid utf-8 followed by partial two byte sequence' do\n    assert_equal '[', subject << '['\n    assert_equal '\"', subject << '\"'\n    assert_equal '', subject << \"\\xC3\"\n    assert_raises(JSON::Stream::ParserError) { subject << '\"' }\n  end\n\n  it 'rejects invalid two byte start characters' do\n    assert_raises(JSON::Stream::ParserError) { subject << \"\\xC3\\xC3\" }\n  end\n\n  it 'rejects invalid three byte start characters' do\n    assert_raises(JSON::Stream::ParserError) { subject << \"\\xE2\\xE2\" }\n  end\n\n  it 'rejects invalid four byte start characters' do\n    assert_raises(JSON::Stream::ParserError) { subject << \"\\xF0\\xF0\" }\n  end\n\n  it 'rejects a two byte start with single byte continuation character' do\n    assert_raises(JSON::Stream::ParserError) { subject << \"\\xC3\\u0000\" }\n  end\n\n  it 'rejects a three byte start with single byte continuation character' do\n    assert_raises(JSON::Stream::ParserError) { subject << \"\\xE2\\u0010\" }\n  end\n\n  it 'rejects a four byte start with single byte continuation character' do\n    assert_raises(JSON::Stream::ParserError) { subject << \"\\xF0a\" }\n  end\n\n  it 'rejects an invalid continuation character' do\n    assert_raises(JSON::Stream::ParserError) { subject << \"\\xA9\" }\n  end\n\n  it 'rejects an overlong form' do\n    assert_raises(JSON::Stream::ParserError) { subject << \"\\xC0\\x80\" }\n  end\n\n  describe 'checking for empty buffers' do\n    it 'is initially empty' do\n      assert subject.empty?\n    end\n\n    it 'is empty after processing complete characters' do\n      subject << 'test'\n      assert subject.empty?\n    end\n\n    it 'is not empty after processing partial multi-byte characters' do\n      subject << \"\\xC3\"\n      refute subject.empty?\n      subject << \"\\xA9\"\n      assert subject.empty?\n    end\n  end\nend\n"
  },
  {
    "path": "spec/builder_spec.rb",
    "content": "require 'json/stream'\nrequire 'minitest/autorun'\n\ndescribe JSON::Stream::Builder do\n  let(:parser) { JSON::Stream::Parser.new }\n  subject { JSON::Stream::Builder.new(parser) }\n\n  it 'builds a false value' do\n    assert_nil subject.result\n    subject.start_document\n    subject.value(false)\n    assert_nil subject.result\n    subject.end_document\n    assert_equal false, subject.result\n  end\n\n  it 'builds a string value' do\n    assert_nil subject.result\n    subject.start_document\n    subject.value(\"test\")\n    assert_nil subject.result\n    subject.end_document\n    assert_equal \"test\", subject.result\n  end\n\n  it 'builds an empty array' do\n    assert_nil subject.result\n    subject.start_document\n    subject.start_array\n    subject.end_array\n    assert_nil subject.result\n    subject.end_document\n    assert_equal [], subject.result\n  end\n\n  it 'builds an array of numbers' do\n    subject.start_document\n    subject.start_array\n    subject.value(1)\n    subject.value(2)\n    subject.value(3)\n    subject.end_array\n    subject.end_document\n    assert_equal [1, 2, 3], subject.result\n  end\n\n  it 'builds nested empty arrays' do\n    subject.start_document\n    subject.start_array\n    subject.start_array\n    subject.end_array\n    subject.end_array\n    subject.end_document\n    assert_equal [[]], subject.result\n  end\n\n  it 'builds nested arrays of numbers' do\n    subject.start_document\n    subject.start_array\n    subject.value(1)\n    subject.start_array\n    subject.value(2)\n    subject.end_array\n    subject.value(3)\n    subject.end_array\n    subject.end_document\n    assert_equal [1, [2], 3], subject.result\n  end\n\n  it 'builds an empty object' do\n    subject.start_document\n    subject.start_object\n    subject.end_object\n    subject.end_document\n    assert_equal({}, subject.result)\n  end\n\n  it 'builds a complex object' do\n    subject.start_document\n    subject.start_object\n    subject.key(\"k1\")\n    subject.value(1)\n    subject.key(\"k2\")\n    subject.value(nil)\n    subject.key(\"k3\")\n    subject.value(true)\n    subject.key(\"k4\")\n    subject.value(false)\n    subject.key(\"k5\")\n    subject.value(\"string value\")\n    subject.end_object\n    subject.end_document\n    expected = {\n      \"k1\" => 1,\n      \"k2\" => nil,\n      \"k3\" => true,\n      \"k4\" => false,\n      \"k5\" => \"string value\"\n    }\n    assert_equal expected, subject.result\n  end\n\n  it 'builds a nested object' do\n    subject.start_document\n    subject.start_object\n    subject.key(\"k1\")\n    subject.value(1)\n\n    subject.key(\"k2\")\n    subject.start_object\n    subject.end_object\n\n    subject.key(\"k3\")\n    subject.start_object\n      subject.key(\"sub1\")\n      subject.start_array\n        subject.value(12)\n      subject.end_array\n    subject.end_object\n\n    subject.key(\"k4\")\n    subject.start_array\n      subject.value(1)\n      subject.start_object\n        subject.key(\"sub2\")\n        subject.start_array\n        subject.value(nil)\n        subject.end_array\n      subject.end_object\n    subject.end_array\n\n    subject.key(\"k5\")\n    subject.value(\"string value\")\n    subject.end_object\n    subject.end_document\n    expected = {\n      \"k1\" => 1,\n      \"k2\" => {},\n      \"k3\" => {\"sub1\" => [12]},\n      \"k4\" => [1, {\"sub2\" => [nil]}],\n      \"k5\" => \"string value\"\n    }\n    assert_equal expected, subject.result\n  end\n\n  it 'builds a real document' do\n    refute_nil subject\n    parser << File.read('spec/fixtures/repository.json')\n    refute_nil subject.result\n    assert_equal 'rails', subject.result['name']\n    assert_equal 4223, subject.result['owner']['id']\n    assert_equal false, subject.result['fork']\n    assert_nil subject.result['mirror_url']\n  end\nend\n"
  },
  {
    "path": "spec/fixtures/repository.json",
    "content": "{\n  \"id\": 8514,\n  \"name\": \"rails\",\n  \"full_name\": \"rails/rails\",\n  \"owner\": {\n    \"login\": \"rails\",\n    \"id\": 4223,\n    \"avatar_url\": \"https://avatars.githubusercontent.com/u/4223?\",\n    \"gravatar_id\": \"30f39a09e233e8369dddf6feb4be0308\",\n    \"url\": \"https://api.github.com/users/rails\",\n    \"html_url\": \"https://github.com/rails\",\n    \"followers_url\": \"https://api.github.com/users/rails/followers\",\n    \"following_url\": \"https://api.github.com/users/rails/following{/other_user}\",\n    \"gists_url\": \"https://api.github.com/users/rails/gists{/gist_id}\",\n    \"starred_url\": \"https://api.github.com/users/rails/starred{/owner}{/repo}\",\n    \"subscriptions_url\": \"https://api.github.com/users/rails/subscriptions\",\n    \"organizations_url\": \"https://api.github.com/users/rails/orgs\",\n    \"repos_url\": \"https://api.github.com/users/rails/repos\",\n    \"events_url\": \"https://api.github.com/users/rails/events{/privacy}\",\n    \"received_events_url\": \"https://api.github.com/users/rails/received_events\",\n    \"type\": \"Organization\",\n    \"site_admin\": false\n  },\n  \"private\": false,\n  \"html_url\": \"https://github.com/rails/rails\",\n  \"description\": \"Ruby on Rails\",\n  \"fork\": false,\n  \"url\": \"https://api.github.com/repos/rails/rails\",\n  \"forks_url\": \"https://api.github.com/repos/rails/rails/forks\",\n  \"keys_url\": \"https://api.github.com/repos/rails/rails/keys{/key_id}\",\n  \"collaborators_url\": \"https://api.github.com/repos/rails/rails/collaborators{/collaborator}\",\n  \"teams_url\": \"https://api.github.com/repos/rails/rails/teams\",\n  \"hooks_url\": \"https://api.github.com/repos/rails/rails/hooks\",\n  \"issue_events_url\": \"https://api.github.com/repos/rails/rails/issues/events{/number}\",\n  \"events_url\": \"https://api.github.com/repos/rails/rails/events\",\n  \"assignees_url\": \"https://api.github.com/repos/rails/rails/assignees{/user}\",\n  \"branches_url\": \"https://api.github.com/repos/rails/rails/branches{/branch}\",\n  \"tags_url\": \"https://api.github.com/repos/rails/rails/tags\",\n  \"blobs_url\": \"https://api.github.com/repos/rails/rails/git/blobs{/sha}\",\n  \"git_tags_url\": \"https://api.github.com/repos/rails/rails/git/tags{/sha}\",\n  \"git_refs_url\": \"https://api.github.com/repos/rails/rails/git/refs{/sha}\",\n  \"trees_url\": \"https://api.github.com/repos/rails/rails/git/trees{/sha}\",\n  \"statuses_url\": \"https://api.github.com/repos/rails/rails/statuses/{sha}\",\n  \"languages_url\": \"https://api.github.com/repos/rails/rails/languages\",\n  \"stargazers_url\": \"https://api.github.com/repos/rails/rails/stargazers\",\n  \"contributors_url\": \"https://api.github.com/repos/rails/rails/contributors\",\n  \"subscribers_url\": \"https://api.github.com/repos/rails/rails/subscribers\",\n  \"subscription_url\": \"https://api.github.com/repos/rails/rails/subscription\",\n  \"commits_url\": \"https://api.github.com/repos/rails/rails/commits{/sha}\",\n  \"git_commits_url\": \"https://api.github.com/repos/rails/rails/git/commits{/sha}\",\n  \"comments_url\": \"https://api.github.com/repos/rails/rails/comments{/number}\",\n  \"issue_comment_url\": \"https://api.github.com/repos/rails/rails/issues/comments/{number}\",\n  \"contents_url\": \"https://api.github.com/repos/rails/rails/contents/{+path}\",\n  \"compare_url\": \"https://api.github.com/repos/rails/rails/compare/{base}...{head}\",\n  \"merges_url\": \"https://api.github.com/repos/rails/rails/merges\",\n  \"archive_url\": \"https://api.github.com/repos/rails/rails/{archive_format}{/ref}\",\n  \"downloads_url\": \"https://api.github.com/repos/rails/rails/downloads\",\n  \"issues_url\": \"https://api.github.com/repos/rails/rails/issues{/number}\",\n  \"pulls_url\": \"https://api.github.com/repos/rails/rails/pulls{/number}\",\n  \"milestones_url\": \"https://api.github.com/repos/rails/rails/milestones{/number}\",\n  \"notifications_url\": \"https://api.github.com/repos/rails/rails/notifications{?since,all,participating}\",\n  \"labels_url\": \"https://api.github.com/repos/rails/rails/labels{/name}\",\n  \"releases_url\": \"https://api.github.com/repos/rails/rails/releases{/id}\",\n  \"created_at\": \"2008-04-11T02:19:47Z\",\n  \"updated_at\": \"2014-06-25T21:08:45Z\",\n  \"pushed_at\": \"2014-06-25T17:47:52Z\",\n  \"git_url\": \"git://github.com/rails/rails.git\",\n  \"ssh_url\": \"git@github.com:rails/rails.git\",\n  \"clone_url\": \"https://github.com/rails/rails.git\",\n  \"svn_url\": \"https://github.com/rails/rails\",\n  \"homepage\": \"http://rubyonrails.org\",\n  \"size\": 331047,\n  \"stargazers_count\": 22248,\n  \"watchers_count\": 22248,\n  \"language\": \"Ruby\",\n  \"has_issues\": true,\n  \"has_downloads\": true,\n  \"has_wiki\": false,\n  \"forks_count\": 8278,\n  \"mirror_url\": null,\n  \"open_issues_count\": 625,\n  \"forks\": 8278,\n  \"open_issues\": 625,\n  \"watchers\": 22248,\n  \"default_branch\": \"master\",\n  \"organization\": {\n    \"login\": \"rails\",\n    \"id\": 4223,\n    \"avatar_url\": \"https://avatars.githubusercontent.com/u/4223?\",\n    \"gravatar_id\": \"30f39a09e233e8369dddf6feb4be0308\",\n    \"url\": \"https://api.github.com/users/rails\",\n    \"html_url\": \"https://github.com/rails\",\n    \"followers_url\": \"https://api.github.com/users/rails/followers\",\n    \"following_url\": \"https://api.github.com/users/rails/following{/other_user}\",\n    \"gists_url\": \"https://api.github.com/users/rails/gists{/gist_id}\",\n    \"starred_url\": \"https://api.github.com/users/rails/starred{/owner}{/repo}\",\n    \"subscriptions_url\": \"https://api.github.com/users/rails/subscriptions\",\n    \"organizations_url\": \"https://api.github.com/users/rails/orgs\",\n    \"repos_url\": \"https://api.github.com/users/rails/repos\",\n    \"events_url\": \"https://api.github.com/users/rails/events{/privacy}\",\n    \"received_events_url\": \"https://api.github.com/users/rails/received_events\",\n    \"type\": \"Organization\",\n    \"site_admin\": false\n  },\n  \"network_count\": 8278,\n  \"subscribers_count\": 1521\n}\n"
  },
  {
    "path": "spec/parser_spec.rb",
    "content": "require 'json/stream'\nrequire 'minitest/autorun'\n\ndescribe JSON::Stream::Parser do\n  subject { JSON::Stream::Parser.new }\n\n  describe 'parsing a document' do\n    it 'rejects documents containing bad start character' do\n      expected = [:error]\n      assert_equal expected, events('a')\n    end\n\n    it 'rejects documents starting with period' do\n      expected = [:error]\n      assert_equal expected, events('.')\n    end\n\n    it 'parses a null value document' do\n      expected = [:start_document, [:value, nil], :end_document]\n      assert_equal expected, events('null')\n    end\n\n    it 'parses a false value document' do\n      expected = [:start_document, [:value, false], :end_document]\n      assert_equal expected, events('false')\n    end\n\n    it 'parses a true value document' do\n      expected = [:start_document, [:value, true], :end_document]\n      assert_equal expected, events('true')\n    end\n\n    it 'parses a string document' do\n      expected = [:start_document, [:value, \"test\"], :end_document]\n      assert_equal expected, events('\"test\"')\n    end\n\n    it 'parses a single digit integer value document' do\n      expected = [:start_document, [:value, 2], :end_document]\n      events = events('2', subject)\n      assert events.empty?\n      subject.finish\n      assert_equal expected, events\n    end\n\n    it 'parses a multiple digit integer value document' do\n      expected = [:start_document, [:value, 12], :end_document]\n      events = events('12', subject)\n      assert events.empty?\n      subject.finish\n      assert_equal expected, events\n    end\n\n    it 'parses a zero literal document' do\n      expected = [:start_document, [:value, 0], :end_document]\n      events = events('0', subject)\n      assert events.empty?\n      subject.finish\n      assert_equal expected, events\n    end\n\n    it 'parses a negative integer document' do\n      expected = [:start_document, [:value, -1], :end_document]\n      events = events('-1', subject)\n      assert events.empty?\n      subject.finish\n      assert_equal expected, events\n    end\n\n    it 'parses an exponent literal document' do\n      expected = [:start_document, [:value, 200.0], :end_document]\n      events = events('2e2', subject)\n      assert events.empty?\n      subject.finish\n      assert_equal expected, events\n    end\n\n    it 'parses a float value document' do\n      expected = [:start_document, [:value, 12.1], :end_document]\n      events = events('12.1', subject)\n      assert events.empty?\n      subject.finish\n      assert_equal expected, events\n    end\n\n    it 'parses a value document with leading whitespace' do\n      expected = [:start_document, [:value, false], :end_document]\n      assert_equal expected, events('  false  ')\n    end\n\n    it 'parses array documents' do\n      expected = [:start_document, :start_array, :end_array, :end_document]\n      assert_equal expected, events('[]')\n      assert_equal expected, events('[ ]')\n      assert_equal expected, events(' [] ')\n      assert_equal expected, events(' [ ] ')\n    end\n\n    it 'parses object documents' do\n      expected = [:start_document, :start_object, :end_object, :end_document]\n      assert_equal expected, events('{}')\n      assert_equal expected, events('{ }')\n      assert_equal expected, events(' {} ')\n      assert_equal expected, events(' { } ')\n    end\n\n    it 'rejects documents with trailing characters' do\n      expected = [:start_document, :start_object, :end_object, :end_document, :error]\n      assert_equal expected, events('{}a')\n      assert_equal expected, events('{ } 12')\n      assert_equal expected, events(' {} false')\n      assert_equal expected, events(' { }, {}')\n    end\n\n    it 'ignores whitespace around tokens, preserves it within strings' do\n      json = %Q{\n        { \" key 1 \" : \\t [\n          1, 2, \" my string \",\\r\n          false, true, null ]\n        }\n      }\n      expected = [\n        :start_document,\n          :start_object,\n            [:key, \" key 1 \"],\n            :start_array,\n              [:value, 1],\n              [:value, 2],\n              [:value, \" my string \"],\n              [:value, false],\n              [:value, true],\n              [:value, nil],\n            :end_array,\n          :end_object,\n        :end_document\n      ]\n      assert_equal expected, events(json)\n    end\n\n    it 'rejects form feed whitespace' do\n      json = \"[1,\\f 2]\"\n      expected = [:start_document, :start_array, [:value, 1], :error]\n      assert_equal expected, events(json)\n    end\n\n    it 'rejects vertical tab whitespace' do\n      json = \"[1,\\v 2]\"\n      expected = [:start_document, :start_array, [:value, 1], :error]\n      assert_equal expected, events(json)\n    end\n\n    it 'rejects partial keyword tokens' do\n      expected = [:start_document, :start_array, :error]\n      assert_equal expected, events('[tru]')\n      assert_equal expected, events('[fal]')\n      assert_equal expected, events('[nul,true]')\n      assert_equal expected, events('[fals1]')\n    end\n\n    it 'rejects scrambled keyword tokens' do\n      expected = [:start_document, :start_array, :error]\n      assert_equal expected, events('[ture]')\n      assert_equal expected, events('[fales]')\n      assert_equal expected, events('[nlul]')\n    end\n\n    it 'parses single keyword tokens' do\n      expected = [:start_document, :start_array, [:value, true], :end_array, :end_document]\n      assert_equal expected, events('[true]')\n    end\n\n    it 'parses keywords in series' do\n      expected = [:start_document, :start_array, [:value, true], [:value, nil], :end_array, :end_document]\n      assert_equal expected, events('[true, null]')\n    end\n  end\n\n  describe 'finishing the parse' do\n    it 'rejects finish with no json data provided' do\n      assert_raises(JSON::Stream::ParserError) { subject.finish }\n    end\n\n    it 'rejects partial null keyword' do\n      subject << 'nul'\n      assert_raises(JSON::Stream::ParserError) { subject.finish }\n    end\n\n    it 'rejects partial true keyword' do\n      subject << 'tru'\n      assert_raises(JSON::Stream::ParserError) { subject.finish }\n    end\n\n    it 'rejects partial false keyword' do\n      subject << 'fals'\n      assert_raises(JSON::Stream::ParserError) { subject.finish }\n    end\n\n    it 'rejects partial float literal' do\n      subject << '42.'\n      assert_raises(JSON::Stream::ParserError) { subject.finish }\n    end\n\n    it 'rejects partial exponent' do\n      subject << '42e'\n      assert_raises(JSON::Stream::ParserError) { subject.finish }\n    end\n\n    it 'rejects malformed exponent' do\n      subject << '42e+'\n      assert_raises(JSON::Stream::ParserError) { subject.finish }\n    end\n\n    it 'rejects partial negative number' do\n      subject << '-'\n      assert_raises(JSON::Stream::ParserError) { subject.finish }\n    end\n\n    it 'rejects partial string literal' do\n      subject << '\"test'\n      assert_raises(JSON::Stream::ParserError) { subject.finish }\n    end\n\n    it 'rejects partial object ending in literal value' do\n      subject << '{\"test\": 42'\n      assert_raises(JSON::Stream::ParserError) { subject.finish }\n    end\n\n    it 'rejects partial array ending in literal value' do\n      subject << '[42'\n      assert_raises(JSON::Stream::ParserError) { subject.finish }\n    end\n\n    it 'does nothing on subsequent finish' do\n      begin\n        subject << 'false'\n        subject.finish\n        subject.finish\n      rescue\n        fail 'raised unexpected error'\n      end\n    end\n  end\n\n  describe 'parsing number tokens' do\n    it 'rejects invalid negative numbers' do\n      expected = [:start_document, :start_array, :error]\n      assert_equal expected, events('[-]')\n\n      expected = [:start_document, :start_array, [:value, 1], :error]\n      assert_equal expected, events('[1-0]')\n    end\n\n    it 'parses integer zero' do\n      expected = [:start_document, :start_array, [:value, 0], :end_array, :end_document]\n      assert_equal expected, events('[0]')\n      assert_equal expected, events('[-0]')\n    end\n\n    it 'parses float zero' do\n      expected = [:start_document, :start_array, [:value, 0.0], :end_array, :end_document]\n      assert_equal expected, events('[0.0]')\n      assert_equal expected, events('[-0.0]')\n    end\n\n    it 'rejects multi zero' do\n      expected = [:start_document, :start_array, [:value, 0], :error]\n      assert_equal expected, events('[00]')\n      assert_equal expected, events('[-00]')\n    end\n\n    it 'rejects integers that start with zero' do\n      expected = [:start_document, :start_array, [:value, 0], :error]\n      assert_equal expected, events('[01]')\n      assert_equal expected, events('[-01]')\n    end\n\n    it 'parses integer tokens' do\n      expected = [:start_document, :start_array, [:value, 1], :end_array, :end_document]\n      assert_equal expected, events('[1]')\n\n      expected = [:start_document, :start_array, [:value, -1], :end_array, :end_document]\n      assert_equal expected, events('[-1]')\n\n      expected = [:start_document, :start_array, [:value, 123], :end_array, :end_document]\n      assert_equal expected, events('[123]')\n\n      expected = [:start_document, :start_array, [:value, -123], :end_array, :end_document]\n      assert_equal expected, events('[-123]')\n    end\n\n    it 'parses float tokens' do\n      expected = [:start_document, :start_array, [:value, 1.0], :end_array, :end_document]\n      assert_equal expected, events('[1.0]')\n      assert_equal expected, events('[1.00]')\n    end\n\n    it 'parses negative floats' do\n      expected = [:start_document, :start_array, [:value, -1.0], :end_array, :end_document]\n      assert_equal expected, events('[-1.0]')\n      assert_equal expected, events('[-1.00]')\n    end\n\n    it 'parses multi-digit floats' do\n      expected = [:start_document, :start_array, [:value, 123.012], :end_array, :end_document]\n      assert_equal expected, events('[123.012]')\n      assert_equal expected, events('[123.0120]')\n    end\n\n    it 'parses negative multi-digit floats' do\n      expected = [:start_document, :start_array, [:value, -123.012], :end_array, :end_document]\n      assert_equal expected, events('[-123.012]')\n      assert_equal expected, events('[-123.0120]')\n    end\n\n    it 'rejects floats missing leading zero' do\n      expected = [:start_document, :start_array, :error]\n      assert_equal expected, events('[.1]')\n      assert_equal expected, events('[-.1]')\n      assert_equal expected, events('[.01]')\n      assert_equal expected, events('[-.01]')\n    end\n\n    it 'rejects float missing fraction' do\n      expected = [:start_document, :start_array, :error]\n      assert_equal expected, events('[.]')\n      assert_equal expected, events('[..]')\n      assert_equal expected, events('[0.]')\n      assert_equal expected, events('[12.]')\n    end\n\n    it 'parses zero with implicit positive exponent as float' do\n      expected = [:start_document, :start_array, [:value, 0.0], :end_array, :end_document]\n      events = events('[0e2]')\n      assert_equal expected, events\n      assert_kind_of Float, events[2][1]\n    end\n\n    it 'parses zero with explicit positive exponent as float' do\n      expected = [:start_document, :start_array, [:value, 0.0], :end_array, :end_document]\n      events = events('[0e+2]')\n      assert_equal expected, events\n      assert_kind_of Float, events[2][1]\n    end\n\n    it 'parses zero with negative exponent as float' do\n      expected = [:start_document, :start_array, [:value, 0.0], :end_array, :end_document]\n      events = events('[0e-2]')\n      assert_equal expected, events\n      assert_kind_of Float, events[2][1]\n    end\n\n    it 'parses positive exponent integers as floats' do\n      expected = [:start_document, :start_array, [:value, 212.0], :end_array, :end_document]\n\n      events = events('[2.12e2]')\n      assert_equal expected, events('[2.12e2]')\n      assert_kind_of Float, events[2][1]\n\n      assert_equal expected, events('[2.12e02]')\n      assert_equal expected, events('[2.12e+2]')\n      assert_equal expected, events('[2.12e+02]')\n    end\n\n    it 'parses positive exponent floats' do\n      expected = [:start_document, :start_array, [:value, 21.2], :end_array, :end_document]\n      assert_equal expected, events('[2.12e1]')\n      assert_equal expected, events('[2.12e01]')\n      assert_equal expected, events('[2.12e+1]')\n      assert_equal expected, events('[2.12e+01]')\n    end\n\n    it 'parses negative exponent' do\n      expected = [:start_document, :start_array, [:value, 0.0212], :end_array, :end_document]\n      assert_equal expected, events('[2.12e-2]')\n      assert_equal expected, events('[2.12e-02]')\n      assert_equal expected, events('[2.12e-2]')\n      assert_equal expected, events('[2.12e-02]')\n    end\n\n    it 'parses zero exponent floats' do\n      expected = [:start_document, :start_array, [:value, 2.12], :end_array, :end_document]\n      assert_equal expected, events('[2.12e0]')\n      assert_equal expected, events('[2.12e00]')\n      assert_equal expected, events('[2.12e-0]')\n      assert_equal expected, events('[2.12e-00]')\n    end\n\n    it 'parses zero exponent integers' do\n      expected = [:start_document, :start_array, [:value, 2.0], :end_array, :end_document]\n      assert_equal expected, events('[2e0]')\n      assert_equal expected, events('[2e00]')\n      assert_equal expected, events('[2e-0]')\n      assert_equal expected, events('[2e-00]')\n    end\n\n    it 'rejects missing exponent' do\n      expected = [:start_document, :start_array, :error]\n      assert_equal expected, events('[e]')\n      assert_equal expected, events('[1e]')\n      assert_equal expected, events('[1e-]')\n      assert_equal expected, events('[1e--]')\n      assert_equal expected, events('[1e+]')\n      assert_equal expected, events('[1e++]')\n      assert_equal expected, events('[0.e]')\n      assert_equal expected, events('[10.e]')\n    end\n\n    it 'rejects float with trailing character' do\n      expected = [:start_document, :start_array, [:value, 0.0], :error]\n      assert_equal expected, events('[0.0q]')\n    end\n\n    it 'rejects integer with trailing character' do\n      expected = [:start_document, :start_array, [:value, 1], :error]\n      assert_equal expected, events('[1q]')\n    end\n  end\n\n  describe 'parsing string tokens' do\n    describe 'parsing two-character escapes' do\n      it 'rejects invalid escape characters' do\n        expected = [:start_document, :start_array, :error]\n        assert_equal expected, events('[\"\\\\a\"]')\n      end\n\n      it 'parses quotation mark' do\n        expected = [:start_document, :start_array, [:value, \"\\\"\"], :end_array, :end_document]\n        assert_equal expected, events('[\"\\\"\"]')\n      end\n\n      it 'parses reverse solidus' do\n        expected = [:start_document, :start_array, [:value, \"\\\\\"], :end_array, :end_document]\n        assert_equal expected, events('[\"\\\\\\\"]')\n      end\n\n      it 'parses solidus' do\n        expected = [:start_document, :start_array, [:value, \"/\"], :end_array, :end_document]\n        assert_equal expected, events('[\"\\/\"]')\n      end\n\n      it 'parses backspace' do\n        expected = [:start_document, :start_array, [:value, \"\\b\"], :end_array, :end_document]\n        assert_equal expected, events('[\"\\b\"]')\n      end\n\n      it 'parses form feed' do\n        expected = [:start_document, :start_array, [:value, \"\\f\"], :end_array, :end_document]\n        assert_equal expected, events('[\"\\f\"]')\n      end\n\n      it 'parses line feed' do\n        expected = [:start_document, :start_array, [:value, \"\\n\"], :end_array, :end_document]\n        assert_equal expected, events('[\"\\n\"]')\n      end\n\n      it 'parses carriage return' do\n        expected = [:start_document, :start_array, [:value, \"\\r\"], :end_array, :end_document]\n        assert_equal expected, events('[\"\\r\"]')\n      end\n\n      it 'parses tab' do\n        expected = [:start_document, :start_array, [:value, \"\\t\"], :end_array, :end_document]\n        assert_equal expected, events('[\"\\t\"]')\n      end\n\n      it 'parses a series of escapes with whitespace' do\n        expected = [:start_document, :start_array, [:value, \"\\\" \\\\ / \\b \\f \\n \\r \\t\"], :end_array, :end_document]\n        assert_equal expected, events('[\"\\\" \\\\\\ \\/ \\b \\f \\n \\r \\t\"]')\n      end\n\n      it 'parses a series of escapes without whitespace' do\n        expected = [:start_document, :start_array, [:value, \"\\\"\\\\/\\b\\f\\n\\r\\t\"], :end_array, :end_document]\n        assert_equal expected, events('[\"\\\"\\\\\\\\/\\b\\f\\n\\r\\t\"]')\n      end\n\n      it 'parses a series of escapes with duplicate characters between them' do\n        expected = [:start_document, :start_array, [:value, \"\\\"t\\\\b/f\\bn\\f/\\nn\\rr\\t\"], :end_array, :end_document]\n        assert_equal expected, events('[\"\\\"t\\\\\\b\\/f\\bn\\f/\\nn\\rr\\t\"]')\n      end\n    end\n\n    describe 'parsing control characters' do\n      it 'rejects control character in array' do\n        expected = [:start_document, :start_array, :error]\n        assert_equal expected, events(\"[\\\" \\u0000 \\\"]\")\n      end\n\n      it 'rejects control character in object' do\n        expected = [:start_document, :start_object, :error]\n        assert_equal expected, events(\"{\\\" \\u0000 \\\":12}\")\n      end\n\n      it 'parses escaped control character' do\n        expected = [:start_document, :start_array, [:value, \"\\u0000\"], :end_array, :end_document]\n        assert_equal expected, events('[\"\\\\u0000\"]')\n      end\n\n      it 'parses escaped control character in object key' do\n        expected = [:start_document, :start_object, [:key, \"\\u0000\"], [:value, 12], :end_object, :end_document]\n        assert_equal expected, events('{\"\\\\u0000\": 12}')\n      end\n\n      it 'parses non-control character' do\n        # del ascii 127 is allowed unescaped in json\n        expected = [:start_document, :start_array, [:value, \" \\u007F \"], :end_array, :end_document]\n        assert_equal expected, events(\"[\\\" \\u007f \\\"]\")\n      end\n    end\n\n    describe 'parsing unicode escape sequences' do\n      it 'parses escaped ascii character' do\n        a = \"\\x61\"\n        escaped = '\\u0061'\n        expected = [:start_document, :start_array, [:value, a], :end_array, :end_document]\n        assert_equal expected, events('[\"' + escaped + '\"]')\n      end\n\n      it 'parses un-escaped raw unicode' do\n        # U+1F602 face with tears of joy\n        face = \"\\xf0\\x9f\\x98\\x82\"\n        expected = [:start_document, :start_array, [:value, face], :end_array, :end_document]\n        assert_equal expected, events('[\"' + face + '\"]')\n      end\n\n      it 'parses escaped unicode surrogate pairs' do\n        # U+1F602 face with tears of joy\n        face = \"\\xf0\\x9f\\x98\\x82\"\n        escaped = '\\uD83D\\uDE02'\n        expected = [:start_document, :start_array, [:value, face], :end_array, :end_document]\n        assert_equal expected, events('[\"' + escaped + '\"]')\n      end\n\n      it 'rejects partial unicode escapes' do\n        expected = [:start_document, :start_array, :error]\n        assert_equal expected, events('[\" \\\\u \"]')\n        assert_equal expected, events('[\" \\\\u2 \"]')\n        assert_equal expected, events('[\" \\\\u26 \"]')\n        assert_equal expected, events('[\" \\\\u260 \"]')\n      end\n\n      it 'parses unicode escapes' do\n        # U+2603 snowman\n        snowman = \"\\xe2\\x98\\x83\"\n        escaped = '\\u2603'\n\n        expected = [:start_document, :start_array, [:value, snowman], :end_array, :end_document]\n        assert_equal expected, events('[\"' + escaped + '\"]')\n\n        expected = [:start_document, :start_array, [:value, 'snow' + snowman + ' man'], :end_array, :end_document]\n        assert_equal expected, events('[\"snow' + escaped + ' man\"]')\n\n        expected = [:start_document, :start_array, [:value, 'snow' + snowman + '3 man'], :end_array, :end_document]\n        assert_equal expected, events('[\"snow' + escaped + '3 man\"]')\n\n        expected = [:start_document, :start_object, [:key, 'snow' + snowman + '3 man'], [:value, 1], :end_object, :end_document]\n        assert_equal expected, events('{\"snow\\\\u26033 man\": 1}')\n      end\n    end\n\n    describe 'parsing unicode escapes with surrogate pairs' do\n      it 'rejects missing second pair' do\n        expected = [:start_document, :start_array, :error]\n        assert_equal expected, events('[\"\\uD834\"]')\n      end\n\n      it 'rejects missing first pair' do\n        expected = [:start_document, :start_array, :error]\n        assert_equal expected, events('[\"\\uDD1E\"]')\n      end\n\n      it 'rejects double first pair' do\n        expected = [:start_document, :start_array, :error]\n        assert_equal expected, events('[\"\\uD834\\uD834\"]')\n      end\n\n      it 'rejects double second pair' do\n        expected = [:start_document, :start_array, :error]\n        assert_equal expected, events('[\"\\uDD1E\\uDD1E\"]')\n      end\n\n      it 'rejects reversed pair' do\n        expected = [:start_document, :start_array, :error]\n        assert_equal expected, events('[\"\\uDD1E\\uD834\"]')\n      end\n\n      it 'parses correct pairs in object keys and values' do\n        # U+1D11E G-Clef\n        clef = \"\\xf0\\x9d\\x84\\x9e\"\n        expected = [\n          :start_document,\n            :start_object,\n              [:key, clef],\n              [:value, \"g\\u{1D11E}clef\"],\n            :end_object,\n          :end_document\n        ]\n        assert_equal expected, events(%q{ {\"\\uD834\\uDD1E\": \"g\\uD834\\uDD1Eclef\"} })\n      end\n    end\n  end\n\n  describe 'parsing arrays' do\n    it 'rejects trailing comma' do\n      expected = [:start_document, :start_array, [:value, 12], :error]\n      assert_equal expected, events('[12, ]')\n    end\n\n    it 'parses nested empty array' do\n      expected = [:start_document, :start_array, :start_array, :end_array, :end_array, :end_document]\n      assert_equal expected, events('[[]]')\n    end\n\n    it 'parses nested array with value' do\n      expected = [:start_document, :start_array, :start_array, [:value, 2.1], :end_array, :end_array, :end_document]\n      assert_equal expected, events('[[ 2.10 ]]')\n    end\n\n    it 'rejects malformed arrays' do\n      expected = [:start_document, :start_array, :error]\n      assert_equal expected, events('[}')\n      assert_equal expected, events('[,]')\n      assert_equal expected, events('[, 12]')\n    end\n\n    it 'rejects malformed nested arrays' do\n      expected = [:start_document, :start_array, :start_array, :error]\n      assert_equal(expected, events('[[}]'))\n      assert_equal expected, events('[[}]')\n      assert_equal expected, events('[[,]]')\n    end\n\n    it 'rejects malformed array value lists' do\n      expected = [:start_document, :start_array, [:value, \"test\"], :error]\n      assert_equal expected, events('[\"test\"}')\n      assert_equal expected, events('[\"test\",]')\n      assert_equal expected, events('[\"test\" \"test\"]')\n      assert_equal expected, events('[\"test\" 12]')\n    end\n\n    it 'parses array with value' do\n      expected = [:start_document, :start_array, [:value, \"test\"], :end_array, :end_document]\n      assert_equal expected, events('[\"test\"]')\n    end\n\n    it 'parses array with value list' do\n      expected = [\n        :start_document,\n          :start_array,\n            [:value, 1],\n            [:value, 2],\n            [:value, nil],\n            [:value, 12.1],\n            [:value, \"test\"],\n          :end_array,\n        :end_document\n      ]\n      assert_equal expected, events('[1,2, null, 12.1,\"test\"]')\n    end\n  end\n\n  describe 'parsing objects' do\n    it 'rejects malformed objects' do\n      expected = [:start_document, :start_object, :error]\n      assert_equal expected, events('{]')\n      assert_equal expected, events('{:}')\n    end\n\n    it 'parses single key object' do\n      expected = [:start_document, :start_object, [:key, \"key 1\"], [:value, 12], :end_object, :end_document]\n      assert_equal expected, events('{\"key 1\" : 12}')\n    end\n\n    it 'parses object key value list' do\n      expected = [\n        :start_document,\n          :start_object,\n            [:key, \"key 1\"], [:value, 12],\n            [:key, \"key 2\"], [:value, \"two\"],\n          :end_object,\n        :end_document\n      ]\n      assert_equal expected, events('{\"key 1\" : 12, \"key 2\":\"two\"}')\n    end\n\n    it 'rejects object key with no value' do\n      expected = [\n        :start_document,\n          :start_object,\n            [:key, \"key\"],\n            :start_array,\n              [:value, nil],\n              [:value, false],\n              [:value, true],\n            :end_array,\n            [:key, \"key 2\"],\n          :error\n      ]\n      assert_equal expected, events('{\"key\": [ null , false , true ] ,\"key 2\"}')\n    end\n\n    it 'rejects object with trailing comma' do\n      expected = [:start_document, :start_object, [:key, \"key 1\"], [:value, 12], :error]\n      assert_equal expected, events('{\"key 1\" : 12,}')\n    end\n  end\n\n  describe 'parsing unicode bytes' do\n    it 'parses single byte utf-8' do\n      expected = [:start_document, :start_array, [:value, \"test\"], :end_array, :end_document]\n      assert_equal expected, events('[\"test\"]')\n    end\n\n    it 'parses full two byte utf-8' do\n      expected = [\n        :start_document,\n          :start_array,\n            [:value, \"résumé\"],\n            [:value, \"éé\"],\n          :end_array,\n        :end_document\n      ]\n      assert_equal expected, events(\"[\\\"résumé\\\", \\\"é\\xC3\\xA9\\\"]\")\n    end\n\n    # Parser should throw an error when only one byte of a two byte character\n    # is available. The \\xC3 byte is the first byte of the é character.\n    it 'rejects a partial two byte utf-8 string' do\n      expected = [:start_document, :start_array, :error]\n      assert_equal expected, events(\"[\\\"\\xC3\\\"]\")\n    end\n\n    it 'parses valid two byte utf-8 string' do\n      expected = [:start_document, :start_array, [:value, 'é'], :end_array, :end_document]\n      assert_equal expected, events(\"[\\\"\\xC3\\xA9\\\"]\")\n    end\n\n    it 'parses full three byte utf-8 string' do\n      expected = [\n        :start_document,\n          :start_array,\n            [:value, \"snow\\u2603man\"],\n            [:value, \"\\u2603\\u2603\"],\n          :end_array,\n        :end_document\n      ]\n      assert_equal expected, events(\"[\\\"snow\\u2603man\\\", \\\"\\u2603\\u2603\\\"]\")\n    end\n\n    it 'rejects one byte of three byte utf-8 string' do\n      expected = [:start_document, :start_array, :error]\n      assert_equal expected, events(\"[\\\"\\xE2\\\"]\")\n    end\n\n    it 'rejects two bytes of three byte utf-8 string' do\n      expected = [:start_document, :start_array, :error]\n      assert_equal expected, events(\"[\\\"\\xE2\\x98\\\"]\")\n    end\n\n    it 'parses full three byte utf-8 string' do\n      expected = [:start_document, :start_array, [:value, \"\\u2603\"], :end_array, :end_document]\n      assert_equal expected, events(\"[\\\"\\xE2\\x98\\x83\\\"]\")\n    end\n\n    it 'parses full four byte utf-8 string' do\n      expected = [\n        :start_document,\n          :start_array,\n            [:value, \"\\u{10102} check mark\"],\n          :end_array,\n        :end_document\n      ]\n      assert_equal expected, events(\"[\\\"\\u{10102} check mark\\\"]\")\n    end\n\n    it 'rejects one byte of four byte utf-8 string' do\n      expected = [:start_document, :start_array, :error]\n      assert_equal expected, events(\"[\\\"\\xF0\\\"]\")\n    end\n\n    it 'rejects two bytes of four byte utf-8 string' do\n      expected = [:start_document, :start_array, :error]\n      assert_equal expected, events(\"[\\\"\\xF0\\x90\\\"]\")\n    end\n\n    it 'rejects three bytes of four byte utf-8 string' do\n      expected = [:start_document, :start_array, :error]\n      assert_equal expected, events(\"[\\\"\\xF0\\x90\\x84\\\"]\")\n    end\n\n    it 'parses full four byte utf-8 string' do\n      expected = [:start_document, :start_array, [:value, \"\\u{10102}\"], :end_array, :end_document]\n      assert_equal expected, events(\"[\\\"\\xF0\\x90\\x84\\x82\\\"]\")\n    end\n  end\n\n  describe 'parsing json text from the module' do\n    it 'parses an array document' do\n      result = JSON::Stream::Parser.parse('[1,2,3]')\n      assert_equal [1, 2, 3], result\n    end\n\n    it 'parses a true keyword literal document' do\n      result = JSON::Stream::Parser.parse('true')\n      assert_equal true, result\n    end\n\n    it 'parses a false keyword literal document' do\n      result = JSON::Stream::Parser.parse('false')\n      assert_equal false, result\n    end\n\n    it 'parses a null keyword literal document' do\n      result = JSON::Stream::Parser.parse('null')\n      assert_nil result\n    end\n\n    it 'parses a string literal document' do\n      result = JSON::Stream::Parser.parse('\"hello\"')\n      assert_equal 'hello', result\n    end\n\n    it 'parses an integer literal document' do\n      result = JSON::Stream::Parser.parse('42')\n      assert_equal 42, result\n    end\n\n    it 'parses a float literal document' do\n      result = JSON::Stream::Parser.parse('42.12')\n      assert_equal 42.12, result\n    end\n\n    it 'rejects a partial float literal document' do\n      assert_raises(JSON::Stream::ParserError) do\n        JSON::Stream::Parser.parse('42.')\n      end\n    end\n\n    it 'rejects a partial document' do\n      assert_raises(JSON::Stream::ParserError) do\n        JSON::Stream::Parser.parse('{')\n      end\n    end\n\n    it 'rejects an empty document' do\n      assert_raises(JSON::Stream::ParserError) do\n        JSON::Stream::Parser.parse('')\n      end\n    end\n  end\n\n  it 'registers observers in initializer block' do\n    events = []\n    parser = JSON::Stream::Parser.new do\n      start_document { events << :start_document }\n      end_document   { events << :end_document }\n      start_object   { events << :start_object }\n      end_object     { events << :end_object }\n      key            { |k| events << [:key, k] }\n      value          { |v| events << [:value, v] }\n    end\n    parser << '{\"key\":12}'\n    expected = [:start_document, :start_object, [:key, \"key\"], [:value, 12], :end_object, :end_document]\n    assert_equal expected, events\n  end\n\n  private\n\n  # Run a worst case, one byte at a time, parse against the JSON string and\n  # return a list of events generated by the parser. A special :error event is\n  # included if the parser threw an exception.\n  #\n  # json   - The String to parse.\n  # parser - The optional Parser instance to use.\n  #\n  # Returns an Events instance.\n  def events(json, parser = nil)\n    parser ||= JSON::Stream::Parser.new\n    collector = Events.new(parser)\n    begin\n      json.each_byte { |byte| parser << [byte].pack('C') }\n    rescue JSON::Stream::ParserError\n      collector.error\n    end\n    collector.events\n  end\n\n  # Dynamically map methods in this class to parser callback methods\n  # so we can collect parser events for inspection by test cases.\n  class Events\n    METHODS = %w[start_document end_document start_object end_object start_array end_array key value]\n\n    attr_reader :events\n\n    def initialize(parser)\n      @events = []\n      METHODS.each do |name|\n        parser.send(name, &method(name))\n      end\n    end\n\n    METHODS.each do |name|\n      define_method(name) do |*args|\n        @events << (args.empty? ? name.to_sym : [name.to_sym, *args])\n      end\n    end\n\n    def error\n      @events << :error\n    end\n  end\nend\n"
  }
]