module JSON # 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(%r(\\(?:[\\bfnrt"/]|u([A-Fa-f\d]{4})))) do case $~[0] when '\/' then '/' 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 end