/* * Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team * Rights to this code are as documented in doc/pod/license.pod. * * JSON-RPC for Ermyth * * $Id: reader.C,v 1.3 2007/09/05 11:23:15 pippijn Exp $ */ //>>>>>>>>>>> reader.C <<<<<<<<<< #include #include #include #include namespace json { static bool in (char c, char c1, char c2, char c3, char c4) { return c == c1 || c == c2 || c == c3 || c == c4; } static bool in (char c, char c1, char c2, char c3, char c4, char c5) { return c == c1 || c == c2 || c == c3 || c == c4 || c == c5; } static bool containsNewLine (Reader::Location begin, Reader::Location end) { for (;begin < end; ++begin) if (*begin == '\012' || *begin == '\015') return true; return false; } // Class Reader Reader::Reader () { } bool Reader::parse (const std::string &document, Value &root, bool collectComments) { document_ = document; char const *begin = document_.c_str (); char const *end = begin + document_.length (); return parse (begin, end, root, collectComments); } bool Reader::parse (std::istream& sin, Value &root, bool collectComments) { #if 0 std::istream_iterator begin (sin); std::istream_iterator end; #endif // Those would allow streamed input from a file, if parse () were a // template function. // Since std::string is reference-counted, this at least does not // create an extra copy. std::string doc; std::getline (sin, doc, (char)EOF); return parse (doc, root, collectComments); } bool Reader::parse (char const *beginDoc, char const *endDOc, Value &root, bool collectComments) { begin_ = beginDoc; end_ = endDOc; collectComments_ = collectComments; current_ = begin_; lastValueEnd_ = 0; lastValue_ = 0; commentsBefore_ = ""; errors_.clear (); while (!nodes_.empty ()) nodes_.pop (); nodes_.push (&root); bool successful = readValue (); Token token; skipCommentTokens (token); if (collectComments_ && !commentsBefore_.empty ()) root.setComment (commentsBefore_, commentAfter); return successful; } bool Reader::readValue () { Token token; skipCommentTokens (token); bool successful = true; if (collectComments_ && !commentsBefore_.empty ()) { currentValue ().setComment (commentsBefore_, commentBefore); commentsBefore_ = ""; } switch (token.type_) { case tokenObjectBegin: successful = readObject (); break; case tokenArrayBegin: successful = readArray (); break; case tokenNumber: successful = decodeNumber (token); break; case tokenString: successful = decodeString (token); break; case tokenTrue: currentValue () = true; break; case tokenFalse: currentValue () = false; break; case tokenNull: currentValue () = Value (); break; default: return addError ("Syntax error: value, object or array expected.", token); } if (collectComments_) { lastValueEnd_ = current_; lastValue_ = ¤tValue (); } return successful; } void Reader::skipCommentTokens (Token &token) { do { readToken (token); } while (token.type_ == tokenComment); } bool Reader::expectToken (TokenType type, Token &token, char const *message) { readToken (token); if (token.type_ != type) return addError (message, token); return true; } bool Reader::readToken (Token &token) { skipSpaces (); token.start_ = current_; char c = getNextChar (); bool ok = true; switch (c) { case '{': token.type_ = tokenObjectBegin; break; case '}': token.type_ = tokenObjectEnd; break; case '[': token.type_ = tokenArrayBegin; break; case ']': token.type_ = tokenArrayEnd; break; case '"': token.type_ = tokenString; ok = readString (); break; case '/': token.type_ = tokenComment; ok = readComment (); break; #if 0 #ifdef __GNUC__ case '0'...'9': #endif #else case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': #endif case '-': token.type_ = tokenNumber; readNumber (); break; case 't': token.type_ = tokenTrue; ok = match ("rue", 3); break; case 'f': token.type_ = tokenFalse; ok = match ("alse", 4); break; case 'n': token.type_ = tokenNull; ok = match ("ull", 3); break; case ',': token.type_ = tokenArraySeparator; break; case ':': token.type_ = tokenMemberSeparator; break; case 0: token.type_ = tokenEndOfStream; break; default: ok = false; break; } if (!ok) token.type_ = tokenError; token.end_ = current_; return true; } void Reader::skipSpaces () { while (current_ != end_) { char c = *current_; if (c == ' ' || c == '\t' || c == '\r' || c == '\n') ++current_; else break; } } bool Reader::match (Location pattern, int patternLength) { if (end_ - current_ < patternLength) return false; int index = patternLength; while (index--) if (current_[index] != pattern[index]) return false; current_ += patternLength; return true; } bool Reader::readComment () { Location commentBegin = current_ - 1; char c = getNextChar (); bool successful = false; if (c == '*') successful = readCStyleComment (); else if (c == '/') successful = readCppStyleComment (); if (!successful) return false; if (collectComments_) { CommentPlacement placement = commentBefore; if (lastValueEnd_ && !containsNewLine (lastValueEnd_, commentBegin)) { if (c != '*' || !containsNewLine (commentBegin, current_)) placement = commentAfterOnSameLine; } addComment (commentBegin, current_, placement); } return true; } void Reader::addComment (Location begin, Location end, CommentPlacement placement) { assert (collectComments_); if (placement == commentAfterOnSameLine) { assert (lastValue_ != 0); lastValue_->setComment (std::string (begin, end), placement); } else { if (!commentsBefore_.empty ()) commentsBefore_ += "\n"; commentsBefore_ += std::string (begin, end); } } bool Reader::readCStyleComment () { while (current_ != end_) { char c = getNextChar (); if (c == '*' && *current_ == '/') break; } return getNextChar () == '/'; } bool Reader::readCppStyleComment () { while (current_ != end_) { char c = getNextChar (); if (c == '\r' || c == '\n') break; } return true; } void Reader::readNumber () { while (current_ != end_) { if (!(*current_ >= '0' && *current_ <= '9') && !in (*current_, '.', 'e', 'E', '+', '-')) break; ++current_; } } bool Reader::readString () { char c = 0; while (current_ != end_) { c = getNextChar (); if (c == '\\') getNextChar (); else if (c == '"') break; } return c == '"'; } bool Reader::readObject () { Token tokenName; std::string name; currentValue () = Value (objectValue); while (readToken (tokenName)) { bool initialTokenOk = true; while (tokenName.type_ == tokenComment && initialTokenOk) initialTokenOk = readToken (tokenName); if (!initialTokenOk) break; if (tokenName.type_ == tokenObjectEnd && name.empty ()) // empty object return true; if (tokenName.type_ != tokenString) break; name = ""; if (!decodeString (tokenName, name)) return recoverFromError (tokenObjectEnd); Token colon; if (!readToken (colon) || colon.type_ != tokenMemberSeparator) { return addErrorAndRecover ("Missing ':' after object member name", colon, tokenObjectEnd); } Value &value = currentValue ()[ name ]; nodes_.push (&value); bool ok = readValue (); nodes_.pop (); if (!ok) // error already set return recoverFromError (tokenObjectEnd); Token comma; if (!readToken (comma) || (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator && comma.type_ != tokenComment)) { return addErrorAndRecover ("Missing ',' or '}' in object declaration", comma, tokenObjectEnd); } bool finalizeTokenOk = true; while (comma.type_ == tokenComment && finalizeTokenOk) finalizeTokenOk = readToken (comma); if (comma.type_ == tokenObjectEnd) return true; } return addErrorAndRecover ("Missing '}' or object member name", tokenName, tokenObjectEnd); } bool Reader::readArray () { currentValue () = Value (arrayValue); skipSpaces (); if (*current_ == ']') // empty array { Token endArray; readToken (endArray); return true; } int index = 0; while (true) { Value &value = currentValue ()[ index++ ]; nodes_.push (&value); bool ok = readValue (); nodes_.pop (); if (!ok) // error already set return recoverFromError (tokenArrayEnd); Token token; if (!readToken (token) || (token.type_ != tokenArraySeparator && token.type_ != tokenArrayEnd)) { return addErrorAndRecover ("Missing ',' or ']' in array declaration", token, tokenArrayEnd); } if (token.type_ == tokenArrayEnd) break; } return true; } bool Reader::decodeNumber (Token &token) { bool isDouble = false; for (Location inspect = token.start_; inspect != token.end_; ++inspect) { isDouble = isDouble || in (*inspect, '.', 'e', 'E', '+') || (*inspect == '-' && inspect != token.start_); } if (isDouble) return decodeDouble (token); Location current = token.start_; bool isNegative = *current == '-'; if (isNegative) ++current; unsigned threshold = (isNegative ? unsigned (-Value::minInt) : Value::maxUInt) / 10; unsigned value = 0; while (current < token.end_) { char c = *current++; if (c < '0' || c > '9') return addError ("'" + std::string (token.start_, token.end_) + "' is not a number.", token); if (value >= threshold) return decodeDouble (token); value = value * 10 + unsigned (c - '0'); } if (isNegative) currentValue () = -int (value); else if (value <= unsigned (Value::maxInt)) currentValue () = int (value); else currentValue () = value; return true; } bool Reader::decodeDouble (Token &token) { double value = 0; const int bufferSize = 32; int count; int length = int (token.end_ - token.start_); if (length <= bufferSize) { char buffer[bufferSize]; memcpy (buffer, token.start_, length); buffer[length] = 0; count = sscanf (buffer, "%lf", &value); } else { std::string buffer (token.start_, token.end_); count = sscanf (buffer.c_str (), "%lf", &value); } if (count != 1) return addError ("'" + std::string (token.start_, token.end_) + "' is not a number.", token); currentValue () = value; return true; } bool Reader::decodeString (Token &token) { std::string decoded; if (!decodeString (token, decoded)) return false; currentValue () = decoded; return true; } bool Reader::decodeString (Token &token, std::string &decoded) { Location current = token.start_ + 1; // skip '"' Location end = token.end_ - 1; // do not include '"' decoded.reserve (long (end - current)); while (current != end) { char c = *current++; if (expect_false (c == '"')) break; else if (expect_false (c == '\\')) { if (expect_false (current == end)) return addError ("Empty escape sequence in string", token, current); char escape = *current++; switch (escape) { case '"': case '/': case '\\': decoded += escape; break; case 'b': decoded += '\010'; break; case 't': decoded += '\011'; break; case 'n': decoded += '\012'; break; case 'f': decoded += '\014'; break; case 'r': decoded += '\015'; break; case 'u': { unsigned unicode; if (!decodeUnicodeEscapeSequence (token, current, end, unicode)) return false; // @todo encode unicode as utf8. // @todo remember to alter the writer too. } break; default: return addError ("Bad escape sequence in string", token, current); } } else { decoded += c; } } return true; } bool Reader::decodeUnicodeEscapeSequence (Token &token, Location ¤t, Location end, unsigned &unicode) { if (end - current < 4) return addError ("Bad unicode escape sequence in string: four digits expected.", token, current); unicode = 0; for (int index = 0; index < 4; ++index) { char c = *current++; unicode *= 16; if (c >= '0' && c <= '9') unicode += c - '0'; else if (c >= 'a' && c <= 'f') unicode += c - 'a' + 10; else if (c >= 'A' && c <= 'F') unicode += c - 'A' + 10; else return addError ("Bad unicode escape sequence in string: hexadecimal digit expected.", token, current); } return true; } bool Reader::addError (const std::string &message, Token &token, Location extra) { ErrorInfo info; info.token_ = token; info.message_ = message; info.extra_ = extra; errors_.push_back (info); return false; } bool Reader::recoverFromError (TokenType skipUntilToken) { int errorCount = int (errors_.size ()); Token skip; while (true) { if (!readToken (skip)) errors_.resize (errorCount); // discard errors caused by recovery if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) break; } errors_.resize (errorCount); return false; } bool Reader::addErrorAndRecover (const std::string &message, Token &token, TokenType skipUntilToken) { addError (message, token); return recoverFromError (skipUntilToken); } Value & Reader::currentValue () { return *(nodes_.top ()); } char Reader::getNextChar () { if (current_ == end_) return 0; return *current_++; } void Reader::getLocationLineAndColumn (Location location, int &line, int &column) const { Location current = begin_; Location lastLineStart = current; line = 0; while (current < location && current != end_) { char c = *current++; if (c == '\r') { if (*current == '\n') ++current; lastLineStart = current; ++line; } else if (c == '\n') { lastLineStart = current; ++line; } } // column & line start at 1 column = int (location - lastLineStart) + 1; ++line; } std::string Reader::getLocationLineAndColumn (Location location) const { int line, column; getLocationLineAndColumn (location, line, column); char buffer[18+16+16+1]; sprintf (buffer, "Line %d, Column %d", line, column); return buffer; } std::string Reader::error_msgs () const { std::string formattedMessage; for (Errors::const_iterator itError = errors_.begin (); itError != errors_.end (); ++itError) { const ErrorInfo &error = *itError; formattedMessage += "* " + getLocationLineAndColumn (error.token_.start_) + "\n"; formattedMessage += " " + error.message_ + "\n"; if (error.extra_) formattedMessage += "See " + getLocationLineAndColumn (error.extra_) + " for detail.\n"; } return formattedMessage; } std::istream& operator >> (std::istream &sin, Value &root) { Reader reader; bool ok = reader.parse (sin, root, true); #if 0 throw_unless (ok); #endif if (!ok) throw std::runtime_error (reader.error_msgs ()); return sin; } } // namespace json