Added structured error reporting to Reader.

This allows applications for interactively viewing or editing JSON to do
a better job of highlighting errors. Also added offset accessors to
Value, offering the same sort of functionality even for non-errors.

Thanks to Zach Clifford (zacharyc@google.com) for the patch.

git-svn-id: http://jsoncpp.svn.sourceforge.net/svnroot/jsoncpp/trunk@281 1f120ed1-78a5-a849-adca-83f0a9e25bb6
diff --git a/jsoncpp/include/json/reader.h b/jsoncpp/include/json/reader.h
index 0771342..6271f71 100644
--- a/jsoncpp/include/json/reader.h
+++ b/jsoncpp/include/json/reader.h
@@ -33,6 +33,19 @@
       typedef char Char;
       typedef const Char *Location;
 
+      /** \brief An error tagged with where in the JSON text it was encountered.
+       *
+       * The offsets give the [start, limit) range of bytes within the text. Note
+       * that this is bytes, not codepoints.
+       *
+       */
+      struct StructuredError
+      {
+         size_t offset_start;
+         size_t offset_limit;
+         std::string message;
+      };
+
       /** \brief Constructs a Reader allowing all features
        * for parsing.
        */
@@ -95,6 +108,14 @@
        */
       std::string getFormattedErrorMessages() const;
 
+     /** \brief Returns a vector of structured erros encounted while parsing.
+      * \return A (possibly empty) vector of StructuredError objects. Currently
+      *         only one error can be returned, but the caller should tolerate multiple
+      *         errors.  This can occur if the parser recovers from a non-fatal
+      *         parse error and then encounters additional errors.
+      */
+     std::vector<StructuredError> getStructuredErrors() const;
+
    private:
       enum TokenType
       {
diff --git a/jsoncpp/include/json/value.h b/jsoncpp/include/json/value.h
index bd7f181..f18457a 100644
--- a/jsoncpp/include/json/value.h
+++ b/jsoncpp/include/json/value.h
@@ -442,6 +442,13 @@
       iterator begin();
       iterator end();
 
+      // Accessors for the [start, limit) range of bytes within the JSON text from
+      // which this value was parsed, if any.
+      void setOffsetStart( size_t start );
+      void setOffsetLimit( size_t limit );
+      size_t getOffsetStart() const;
+      size_t getOffsetLimit() const;
+
    private:
       Value &resolveReference( const char *key, 
                                bool isStatic );
@@ -509,6 +516,11 @@
       int memberNameIsStatic_ : 1;       // used by the ValueInternalMap container.
 # endif
       CommentInfo *comments_;
+
+      // [start, limit) byte offsets in the source JSON text from which this Value
+      // was extracted.
+      size_t start_;
+      size_t limit_;
    };
 
 
diff --git a/jsoncpp/src/lib_json/json_reader.cpp b/jsoncpp/src/lib_json/json_reader.cpp
index 9ba4024..4f592cd 100644
--- a/jsoncpp/src/lib_json/json_reader.cpp
+++ b/jsoncpp/src/lib_json/json_reader.cpp
@@ -215,9 +215,11 @@
    {
    case tokenObjectBegin:
       successful = readObject( token );
+      currentValue().setOffsetLimit(current_ - begin_);
       break;
    case tokenArrayBegin:
       successful = readArray( token );
+      currentValue().setOffsetLimit(current_ - begin_);
       break;
    case tokenNumber:
       successful = decodeNumber( token );
@@ -227,12 +229,18 @@
       break;
    case tokenTrue:
       currentValue() = true;
+      currentValue().setOffsetStart(token.start_ - begin_);
+      currentValue().setOffsetLimit(token.end_ - begin_);
       break;
    case tokenFalse:
       currentValue() = false;
+      currentValue().setOffsetStart(token.start_ - begin_);
+      currentValue().setOffsetLimit(token.end_ - begin_);
       break;
    case tokenNull:
       currentValue() = Value();
+      currentValue().setOffsetStart(token.start_ - begin_);
+      currentValue().setOffsetLimit(token.end_ - begin_);
       break;
    case tokenArraySeparator:
       if ( features_.allowDroppedNullPlaceholders_ )
@@ -241,10 +249,14 @@
          // token.
          current_--;
          currentValue() = Value();
+         currentValue().setOffsetStart(current_ - begin_ - 1);
+         currentValue().setOffsetLimit(current_ - begin_);
          break;
       }
       // Else, fall through...
    default:
+      currentValue().setOffsetStart(token.start_ - begin_);
+      currentValue().setOffsetLimit(token.end_ - begin_);
       return addError( "Syntax error: value, object or array expected.", token );
    }
 
@@ -493,11 +505,12 @@
 
 
 bool 
-Reader::readObject( Token &/*tokenStart*/ )
+Reader::readObject( Token &tokenStart )
 {
    Token tokenName;
    std::string name;
    currentValue() = Value( objectValue );
+   currentValue().setOffsetStart(tokenStart.start_ - begin_);
    while ( readToken( tokenName ) )
    {
       bool initialTokenOk = true;
@@ -564,9 +577,10 @@
 
 
 bool 
-Reader::readArray( Token &/*tokenStart*/ )
+Reader::readArray( Token &tokenStart )
 {
    currentValue() = Value( arrayValue );
+   currentValue().setOffsetStart(tokenStart.start_ - begin_);
    skipSpaces();
    if ( *current_ == ']' ) // empty array
    {
@@ -613,6 +627,8 @@
   if ( !decodeNumber( token, decoded ) )
      return false;
   currentValue() = decoded;
+  currentValue().setOffsetStart(token.start_ - begin_);
+  currentValue().setOffsetLimit(token.end_ - begin_);
   return true;
 }
 
@@ -678,6 +694,8 @@
   if ( !decodeDouble( token, decoded ) )
      return false;
   currentValue() = decoded;
+  currentValue().setOffsetStart(token.start_ - begin_);
+  currentValue().setOffsetLimit(token.end_ - begin_);
   return true;
 }
 
@@ -729,6 +747,8 @@
    if ( !decodeString( token, decoded ) )
       return false;
    currentValue() = decoded;
+   currentValue().setOffsetStart(token.start_ - begin_);
+   currentValue().setOffsetLimit(token.end_ - begin_);
    return true;
 }
 
@@ -963,6 +983,25 @@
 }
 
 
+std::vector<Reader::StructuredError>
+Reader::getStructuredErrors() const
+{
+   std::vector<Reader::StructuredError> allErrors;
+   for ( Errors::const_iterator itError = errors_.begin();
+         itError != errors_.end();
+         ++itError )
+   {
+      const ErrorInfo &error = *itError;
+      Reader::StructuredError structured;
+      structured.offset_start = error.token_.start_ - begin_;
+      structured.offset_limit = error.token_.end_ - begin_;
+      structured.message = error.message_;
+      allErrors.push_back(structured);
+   }
+   return allErrors;
+}
+
+
 std::istream& operator>>( std::istream &sin, Value &root )
 {
     Json::Reader reader;
diff --git a/jsoncpp/src/lib_json/json_value.cpp b/jsoncpp/src/lib_json/json_value.cpp
index dd8dda0..abc59c8 100644
--- a/jsoncpp/src/lib_json/json_value.cpp
+++ b/jsoncpp/src/lib_json/json_value.cpp
@@ -274,6 +274,8 @@
    , itemIsUsed_( 0 )
 #endif
    , comments_( 0 )
+   , start_( 0 )
+   , limit_( 0 )
 {
    switch ( type )
    {
@@ -318,6 +320,8 @@
    , itemIsUsed_( 0 )
 #endif
    , comments_( 0 )
+   , start_( 0 )
+   , limit_( 0 )
 {
    value_.uint_ = value;
 }
@@ -329,6 +333,8 @@
    , itemIsUsed_( 0 )
 #endif
    , comments_( 0 )
+   , start_( 0 )
+   , limit_( 0 )
 {
    value_.int_ = value;
 }
@@ -342,6 +348,8 @@
    , itemIsUsed_( 0 )
 #endif
    , comments_( 0 )
+   , start_( 0 )
+   , limit_( 0 )
 {
    value_.int_ = value;
 }
@@ -354,6 +362,8 @@
    , itemIsUsed_( 0 )
 #endif
    , comments_( 0 )
+   , start_( 0 )
+   , limit_( 0 )
 {
    value_.uint_ = value;
 }
@@ -366,6 +376,8 @@
    , itemIsUsed_( 0 )
 #endif
    , comments_( 0 )
+   , start_( 0 )
+   , limit_( 0 )
 {
    value_.real_ = value;
 }
@@ -377,6 +389,8 @@
    , itemIsUsed_( 0 )
 #endif
    , comments_( 0 )
+   , start_( 0 )
+   , limit_( 0 )
 {
    value_.string_ = duplicateStringValue( value );
 }
@@ -390,6 +404,8 @@
    , itemIsUsed_( 0 )
 #endif
    , comments_( 0 )
+   , start_( 0 )
+   , limit_( 0 )
 {
    value_.string_ = duplicateStringValue( beginValue, 
                                           (unsigned int)(endValue - beginValue) );
@@ -403,6 +419,8 @@
    , itemIsUsed_( 0 )
 #endif
    , comments_( 0 )
+   , start_( 0 )
+   , limit_( 0 )
 {
    value_.string_ = duplicateStringValue( value.c_str(), 
                                           (unsigned int)value.length() );
@@ -416,6 +434,8 @@
    , itemIsUsed_( 0 )
 #endif
    , comments_( 0 )
+   , start_( 0 )
+   , limit_( 0 )
 {
    value_.string_ = const_cast<char *>( value.c_str() );
 }
@@ -429,6 +449,8 @@
    , itemIsUsed_( 0 )
 #endif
    , comments_( 0 )
+   , start_( 0 )
+   , limit_( 0 )
 {
    value_.string_ = duplicateStringValue( value, value.length() );
 }
@@ -441,6 +463,8 @@
    , itemIsUsed_( 0 )
 #endif
    , comments_( 0 )
+   , start_( 0 )
+   , limit_( 0 )
 {
    value_.bool_ = value;
 }
@@ -453,6 +477,8 @@
    , itemIsUsed_( 0 )
 #endif
    , comments_( 0 )
+   , start_( other.start_ )
+   , limit_( other.limit_ )
 {
    switch ( type_ )
    {
@@ -557,6 +583,8 @@
    int temp2 = allocated_;
    allocated_ = other.allocated_;
    other.allocated_ = temp2;
+   std::swap( start_, other.start_ );
+   std::swap( limit_, other.limit_ );
 }
 
 ValueType 
@@ -1027,7 +1055,8 @@
 Value::clear()
 {
    JSON_ASSERT_MESSAGE( type_ == nullValue  ||  type_ == arrayValue  || type_ == objectValue, "in Json::Value::clear(): requires complex value" );
-
+   start_ = 0;
+   limit_ = 0;
    switch ( type_ )
    {
 #ifndef JSON_VALUE_USE_INTERNAL_MAP
@@ -1556,6 +1585,34 @@
 }
 
 
+void
+Value::setOffsetStart( size_t start )
+{
+   start_ = start;
+}
+
+
+void
+Value::setOffsetLimit( size_t limit )
+{
+   limit_ = limit;
+}
+
+
+size_t
+Value::getOffsetStart() const
+{
+   return start_;
+}
+
+
+size_t
+Value::getOffsetLimit() const
+{
+   return limit_;
+}
+
+
 std::string 
 Value::toStyledString() const
 {
diff --git a/jsoncpp/src/test_lib_json/main.cpp b/jsoncpp/src/test_lib_json/main.cpp
index dbcbd78..f82a384 100644
--- a/jsoncpp/src/test_lib_json/main.cpp
+++ b/jsoncpp/src/test_lib_json/main.cpp
@@ -1474,6 +1474,26 @@
 }
 
 
+JSONTEST_FIXTURE( ValueTest, offsetAccessors )
+{
+    Json::Value x;
+    JSONTEST_ASSERT( x.getOffsetStart() == 0 );
+    JSONTEST_ASSERT( x.getOffsetLimit() == 0 );
+    x.setOffsetStart(10);
+    x.setOffsetLimit(20);
+    JSONTEST_ASSERT( x.getOffsetStart() == 10 );
+    JSONTEST_ASSERT( x.getOffsetLimit() == 20 );
+    Json::Value y(x);
+    JSONTEST_ASSERT( y.getOffsetStart() == 10 );
+    JSONTEST_ASSERT( y.getOffsetLimit() == 20 );
+    Json::Value z;
+    z.swap(y);
+    JSONTEST_ASSERT( z.getOffsetStart() == 10 );
+    JSONTEST_ASSERT( z.getOffsetLimit() == 20 );
+    JSONTEST_ASSERT( y.getOffsetStart() == 0 );
+    JSONTEST_ASSERT( y.getOffsetLimit() == 0 );
+}
+
 struct WriterTest : JsonTest::TestCase
 {
 };
@@ -1490,6 +1510,115 @@
 }
 
 
+struct ReaderTest : JsonTest::TestCase
+{
+};
+
+
+JSONTEST_FIXTURE( ReaderTest, parseWithNoErrors )
+{
+    Json::Reader reader;
+    Json::Value root;
+    bool ok = reader.parse(
+        "{ \"property\" : \"value\" }",
+        root);
+    JSONTEST_ASSERT( ok );
+    JSONTEST_ASSERT( reader.getFormattedErrorMessages().size() == 0 );
+    JSONTEST_ASSERT( reader.getStructuredErrors().size() == 0 );
+}
+
+
+JSONTEST_FIXTURE( ReaderTest, parseWithNoErrorsTestingOffsets )
+{
+    Json::Reader reader;
+    Json::Value root;
+    bool ok = reader.parse(
+        "{ \"property\" : [\"value\", \"value2\"], \"obj\" : { \"nested\" : 123, \"bool\" : true}, \"null\" : null, \"false\" : false }",
+        root);
+    JSONTEST_ASSERT( ok );
+    JSONTEST_ASSERT( reader.getFormattedErrorMessages().size() == 0 );
+    JSONTEST_ASSERT( reader.getStructuredErrors().size() == 0 );
+    JSONTEST_ASSERT( root["property"].getOffsetStart() == 15 );
+    JSONTEST_ASSERT( root["property"].getOffsetLimit() == 34 );
+    JSONTEST_ASSERT( root["property"][0].getOffsetStart() == 16 );
+    JSONTEST_ASSERT( root["property"][0].getOffsetLimit() == 23 );
+    JSONTEST_ASSERT( root["property"][1].getOffsetStart() == 25 );
+    JSONTEST_ASSERT( root["property"][1].getOffsetLimit() == 33 );
+    JSONTEST_ASSERT( root["obj"].getOffsetStart() == 44 );
+    JSONTEST_ASSERT( root["obj"].getOffsetLimit() == 76 );
+    JSONTEST_ASSERT( root["obj"]["nested"].getOffsetStart() == 57 );
+    JSONTEST_ASSERT( root["obj"]["nested"].getOffsetLimit() == 60 );
+    JSONTEST_ASSERT( root["obj"]["bool"].getOffsetStart() == 71 );
+    JSONTEST_ASSERT( root["obj"]["bool"].getOffsetLimit() == 75 );
+    JSONTEST_ASSERT( root["null"].getOffsetStart() == 87 );
+    JSONTEST_ASSERT( root["null"].getOffsetLimit() == 91 );
+    JSONTEST_ASSERT( root["false"].getOffsetStart() == 103 );
+    JSONTEST_ASSERT( root["false"].getOffsetLimit() == 108 );
+    JSONTEST_ASSERT( root.getOffsetStart() == 0 );
+    JSONTEST_ASSERT( root.getOffsetLimit() == 110 );
+}
+
+
+JSONTEST_FIXTURE( ReaderTest, parseWithOneError )
+{
+    Json::Reader reader;
+    Json::Value root;
+    bool ok = reader.parse(
+        "{ \"property\" :: \"value\" }",
+        root);
+    JSONTEST_ASSERT( !ok );
+    JSONTEST_ASSERT( reader.getFormattedErrorMessages() ==
+        "* Line 1, Column 15\n  Syntax error: value, object or array expected.\n" );
+    std::vector<Json::Reader::StructuredError> errors =
+        reader.getStructuredErrors();
+    JSONTEST_ASSERT( errors.size() == 1 );
+    JSONTEST_ASSERT( errors.at(0).offset_start == 14 );
+    JSONTEST_ASSERT( errors.at(0).offset_limit == 15 );
+    JSONTEST_ASSERT( errors.at(0).message ==
+        "Syntax error: value, object or array expected." );
+}
+
+
+JSONTEST_FIXTURE( ReaderTest, parseChineseWithOneError )
+{
+    Json::Reader reader;
+    Json::Value root;
+    bool ok = reader.parse(
+        "{ \"pr佐藤erty\" :: \"value\" }",
+        root);
+    JSONTEST_ASSERT( !ok );
+    JSONTEST_ASSERT( reader.getFormattedErrorMessages() ==
+        "* Line 1, Column 19\n  Syntax error: value, object or array expected.\n" );
+    std::vector<Json::Reader::StructuredError> errors =
+        reader.getStructuredErrors();
+    JSONTEST_ASSERT( errors.size() == 1 );
+    JSONTEST_ASSERT( errors.at(0).offset_start == 18 );
+    JSONTEST_ASSERT( errors.at(0).offset_limit == 19 );
+    JSONTEST_ASSERT( errors.at(0).message ==
+        "Syntax error: value, object or array expected." );
+}
+
+
+JSONTEST_FIXTURE( ReaderTest, parseWithDetailError )
+{
+    Json::Reader reader;
+    Json::Value root;
+    bool ok = reader.parse(
+        "{ \"property\" : \"v\\alue\" }",
+        root);
+    JSONTEST_ASSERT( !ok );
+    JSONTEST_ASSERT( reader.getFormattedErrorMessages() ==
+        "* Line 1, Column 16\n  Bad escape sequence in string\nSee Line 1, Column 20 for detail.\n" );
+    std::vector<Json::Reader::StructuredError> errors =
+        reader.getStructuredErrors();
+    JSONTEST_ASSERT( errors.size() == 1 );
+    JSONTEST_ASSERT( errors.at(0).offset_start == 15 );
+    JSONTEST_ASSERT( errors.at(0).offset_limit == 23 );
+    JSONTEST_ASSERT( errors.at(0).message ==
+        "Bad escape sequence in string" );
+}
+
+
 int main( int argc, const char *argv[] )
 {
    JsonTest::Runner runner;
@@ -1512,6 +1641,14 @@
    JSONTEST_REGISTER_FIXTURE( runner, ValueTest, compareObject );
    JSONTEST_REGISTER_FIXTURE( runner, ValueTest, compareType );
    JSONTEST_REGISTER_FIXTURE( runner, ValueTest, checkInteger );
+   JSONTEST_REGISTER_FIXTURE( runner, ValueTest, offsetAccessors );
+
+   JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseWithNoErrors );
+   JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseWithNoErrorsTestingOffsets );
+   JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseWithOneError );
+   JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseChineseWithOneError );
+   JSONTEST_REGISTER_FIXTURE( runner, ReaderTest, parseWithDetailError );
+
    JSONTEST_REGISTER_FIXTURE( runner, WriterTest, dropNullPlaceholders );
    return runner.runCommandLine( argc, argv );
 }