Merge remote-tracking branch 'miloyip/master'
This commit is contained in:
commit
26e089b9a2
@ -18,6 +18,7 @@ set(EXAMPLES
|
|||||||
serialize
|
serialize
|
||||||
simpledom
|
simpledom
|
||||||
simplereader
|
simplereader
|
||||||
|
simplepullreader
|
||||||
simplewriter
|
simplewriter
|
||||||
tutorial)
|
tutorial)
|
||||||
|
|
||||||
|
48
example/simplepullreader/simplepullreader.cpp
Normal file
48
example/simplepullreader/simplepullreader.cpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#include "rapidjson/reader.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using namespace rapidjson;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// If you can require C++11, you could use std::to_string here
|
||||||
|
template <typename T> std::string stringify(T x) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << x;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MyHandler {
|
||||||
|
const char* type;
|
||||||
|
std::string data;
|
||||||
|
|
||||||
|
bool Null() { type = "Null"; data.clear(); return true; }
|
||||||
|
bool Bool(bool b) { type = "Bool:"; data = b? "true": "false"; return true; }
|
||||||
|
bool Int(int i) { type = "Int:"; data = stringify(i); return true; }
|
||||||
|
bool Uint(unsigned u) { type = "Uint:"; data = stringify(u); return true; }
|
||||||
|
bool Int64(int64_t i) { type = "Int64:"; data = stringify(i); return true; }
|
||||||
|
bool Uint64(uint64_t u) { type = "Uint64:"; data = stringify(u); return true; }
|
||||||
|
bool Double(double d) { type = "Double:"; data = stringify(d); return true; }
|
||||||
|
bool RawNumber(const char* str, SizeType length, bool) { type = "Number:"; data = std::string(str, length); return true; }
|
||||||
|
bool String(const char* str, SizeType length, bool) { type = "String:"; data = std::string(str, length); return true; }
|
||||||
|
bool StartObject() { type = "StartObject"; data.clear(); return true; }
|
||||||
|
bool Key(const char* str, SizeType length, bool) { type = "Key:"; data = std::string(str, length); return true; }
|
||||||
|
bool EndObject(SizeType memberCount) { type = "EndObject:"; data = stringify(memberCount); return true; }
|
||||||
|
bool StartArray() { type = "StartArray"; data.clear(); return true; }
|
||||||
|
bool EndArray(SizeType elementCount) { type = "EndArray:"; data = stringify(elementCount); return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
const char json[] = " { \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3, 4] } ";
|
||||||
|
|
||||||
|
MyHandler handler;
|
||||||
|
Reader reader;
|
||||||
|
StringStream ss(json);
|
||||||
|
reader.IterativeParseInit();
|
||||||
|
while (!reader.IterativeParseComplete()) {
|
||||||
|
reader.IterativeParseNext<kParseDefaultFlags>(ss, handler);
|
||||||
|
cout << handler.type << handler.data << endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -240,7 +240,7 @@ public:
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr<internal::IsSame<typename internal::RemoveConst<T>::Type, Ch> >), (GenericPointer))
|
RAPIDJSON_DISABLEIF_RETURN((internal::NotExpr<internal::IsSame<typename internal::RemoveConst<T>::Type, Ch> >), (GenericPointer))
|
||||||
Append(T* name, Allocator* allocator = 0) const {
|
Append(T* name, Allocator* allocator = 0) const {
|
||||||
return Append(name, StrLen(name), allocator);
|
return Append(name, internal::StrLen(name), allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if RAPIDJSON_HAS_STDSTRING
|
#if RAPIDJSON_HAS_STDSTRING
|
||||||
|
@ -513,6 +513,83 @@ public:
|
|||||||
return Parse<kParseDefaultFlags>(is, handler);
|
return Parse<kParseDefaultFlags>(is, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Initialize JSON text token-by-token parsing
|
||||||
|
/*!
|
||||||
|
*/
|
||||||
|
void IterativeParseInit() {
|
||||||
|
parseResult_.Clear();
|
||||||
|
state_ = IterativeParsingStartState;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Parse one token from JSON text
|
||||||
|
/*! \tparam InputStream Type of input stream, implementing Stream concept
|
||||||
|
\tparam Handler Type of handler, implementing Handler concept.
|
||||||
|
\param is Input stream to be parsed.
|
||||||
|
\param handler The handler to receive events.
|
||||||
|
\return Whether the parsing is successful.
|
||||||
|
*/
|
||||||
|
template <unsigned parseFlags, typename InputStream, typename Handler>
|
||||||
|
bool IterativeParseNext(InputStream& is, Handler& handler) {
|
||||||
|
while (RAPIDJSON_LIKELY(is.Peek() != '\0')) {
|
||||||
|
SkipWhitespaceAndComments<parseFlags>(is);
|
||||||
|
|
||||||
|
Token t = Tokenize(is.Peek());
|
||||||
|
IterativeParsingState n = Predict(state_, t);
|
||||||
|
IterativeParsingState d = Transit<parseFlags>(state_, t, n, is, handler);
|
||||||
|
|
||||||
|
// If we've finished or hit an error...
|
||||||
|
if (RAPIDJSON_UNLIKELY(IsIterativeParsingCompleteState(d))) {
|
||||||
|
// Report errors.
|
||||||
|
if (d == IterativeParsingErrorState) {
|
||||||
|
HandleError(state_, is);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transition to the finish state.
|
||||||
|
RAPIDJSON_ASSERT(d == IterativeParsingFinishState);
|
||||||
|
state_ = d;
|
||||||
|
|
||||||
|
// If StopWhenDone is not set...
|
||||||
|
if (!(parseFlags & kParseStopWhenDoneFlag)) {
|
||||||
|
// ... and extra non-whitespace data is found...
|
||||||
|
SkipWhitespaceAndComments<parseFlags>(is);
|
||||||
|
if (is.Peek() != '\0') {
|
||||||
|
// ... this is considered an error.
|
||||||
|
HandleError(state_, is);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success! We are done!
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transition to the new state.
|
||||||
|
state_ = d;
|
||||||
|
|
||||||
|
// If we parsed anything other than a delimiter, we invoked the handler, so we can return true now.
|
||||||
|
if (!IsIterativeParsingDelimiterState(n))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We reached the end of file.
|
||||||
|
stack_.Clear();
|
||||||
|
|
||||||
|
if (state_ != IterativeParsingFinishState) {
|
||||||
|
HandleError(state_, is);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Check if token-by-token parsing JSON text is complete
|
||||||
|
/*! \return Whether the JSON has been fully decoded.
|
||||||
|
*/
|
||||||
|
RAPIDJSON_FORCEINLINE bool IterativeParseComplete() {
|
||||||
|
return IsIterativeParsingCompleteState(state_);
|
||||||
|
}
|
||||||
|
|
||||||
//! Whether a parse error has occured in the last parsing.
|
//! Whether a parse error has occured in the last parsing.
|
||||||
bool HasParseError() const { return parseResult_.IsError(); }
|
bool HasParseError() const { return parseResult_.IsError(); }
|
||||||
|
|
||||||
@ -1170,18 +1247,27 @@ private:
|
|||||||
}
|
}
|
||||||
// Parse NaN or Infinity here
|
// Parse NaN or Infinity here
|
||||||
else if ((parseFlags & kParseNanAndInfFlag) && RAPIDJSON_LIKELY((s.Peek() == 'I' || s.Peek() == 'N'))) {
|
else if ((parseFlags & kParseNanAndInfFlag) && RAPIDJSON_LIKELY((s.Peek() == 'I' || s.Peek() == 'N'))) {
|
||||||
useNanOrInf = true;
|
if (Consume(s, 'N')) {
|
||||||
if (RAPIDJSON_LIKELY(Consume(s, 'N') && Consume(s, 'a') && Consume(s, 'N'))) {
|
if (Consume(s, 'a') && Consume(s, 'N')) {
|
||||||
d = std::numeric_limits<double>::quiet_NaN();
|
d = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
useNanOrInf = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (RAPIDJSON_LIKELY(Consume(s, 'I') && Consume(s, 'n') && Consume(s, 'f'))) {
|
else if (RAPIDJSON_LIKELY(Consume(s, 'I'))) {
|
||||||
d = (minus ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity());
|
if (Consume(s, 'n') && Consume(s, 'f')) {
|
||||||
if (RAPIDJSON_UNLIKELY(s.Peek() == 'i' && !(Consume(s, 'i') && Consume(s, 'n')
|
d = (minus ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity());
|
||||||
&& Consume(s, 'i') && Consume(s, 't') && Consume(s, 'y'))))
|
useNanOrInf = true;
|
||||||
RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell());
|
|
||||||
|
if (RAPIDJSON_UNLIKELY(s.Peek() == 'i' && !(Consume(s, 'i') && Consume(s, 'n')
|
||||||
|
&& Consume(s, 'i') && Consume(s, 't') && Consume(s, 'y')))) {
|
||||||
|
RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (RAPIDJSON_UNLIKELY(!useNanOrInf)) {
|
||||||
RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell());
|
RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell());
|
RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell());
|
||||||
@ -1393,30 +1479,32 @@ private:
|
|||||||
|
|
||||||
// States
|
// States
|
||||||
enum IterativeParsingState {
|
enum IterativeParsingState {
|
||||||
IterativeParsingStartState = 0,
|
IterativeParsingFinishState = 0, // sink states at top
|
||||||
IterativeParsingFinishState,
|
IterativeParsingErrorState, // sink states at top
|
||||||
IterativeParsingErrorState,
|
IterativeParsingStartState,
|
||||||
|
|
||||||
// Object states
|
// Object states
|
||||||
IterativeParsingObjectInitialState,
|
IterativeParsingObjectInitialState,
|
||||||
IterativeParsingMemberKeyState,
|
IterativeParsingMemberKeyState,
|
||||||
IterativeParsingKeyValueDelimiterState,
|
|
||||||
IterativeParsingMemberValueState,
|
IterativeParsingMemberValueState,
|
||||||
IterativeParsingMemberDelimiterState,
|
|
||||||
IterativeParsingObjectFinishState,
|
IterativeParsingObjectFinishState,
|
||||||
|
|
||||||
// Array states
|
// Array states
|
||||||
IterativeParsingArrayInitialState,
|
IterativeParsingArrayInitialState,
|
||||||
IterativeParsingElementState,
|
IterativeParsingElementState,
|
||||||
IterativeParsingElementDelimiterState,
|
|
||||||
IterativeParsingArrayFinishState,
|
IterativeParsingArrayFinishState,
|
||||||
|
|
||||||
// Single value state
|
// Single value state
|
||||||
IterativeParsingValueState
|
IterativeParsingValueState,
|
||||||
|
|
||||||
|
// Delimiter states (at bottom)
|
||||||
|
IterativeParsingElementDelimiterState,
|
||||||
|
IterativeParsingMemberDelimiterState,
|
||||||
|
IterativeParsingKeyValueDelimiterState,
|
||||||
|
|
||||||
|
cIterativeParsingStateCount
|
||||||
};
|
};
|
||||||
|
|
||||||
enum { cIterativeParsingStateCount = IterativeParsingValueState + 1 };
|
|
||||||
|
|
||||||
// Tokens
|
// Tokens
|
||||||
enum Token {
|
enum Token {
|
||||||
LeftBracketToken = 0,
|
LeftBracketToken = 0,
|
||||||
@ -1467,6 +1555,18 @@ private:
|
|||||||
RAPIDJSON_FORCEINLINE IterativeParsingState Predict(IterativeParsingState state, Token token) {
|
RAPIDJSON_FORCEINLINE IterativeParsingState Predict(IterativeParsingState state, Token token) {
|
||||||
// current state x one lookahead token -> new state
|
// current state x one lookahead token -> new state
|
||||||
static const char G[cIterativeParsingStateCount][kTokenCount] = {
|
static const char G[cIterativeParsingStateCount][kTokenCount] = {
|
||||||
|
// Finish(sink state)
|
||||||
|
{
|
||||||
|
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
||||||
|
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
||||||
|
IterativeParsingErrorState
|
||||||
|
},
|
||||||
|
// Error(sink state)
|
||||||
|
{
|
||||||
|
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
||||||
|
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
||||||
|
IterativeParsingErrorState
|
||||||
|
},
|
||||||
// Start
|
// Start
|
||||||
{
|
{
|
||||||
IterativeParsingArrayInitialState, // Left bracket
|
IterativeParsingArrayInitialState, // Left bracket
|
||||||
@ -1481,18 +1581,6 @@ private:
|
|||||||
IterativeParsingValueState, // Null
|
IterativeParsingValueState, // Null
|
||||||
IterativeParsingValueState // Number
|
IterativeParsingValueState // Number
|
||||||
},
|
},
|
||||||
// Finish(sink state)
|
|
||||||
{
|
|
||||||
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
|
||||||
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
|
||||||
IterativeParsingErrorState
|
|
||||||
},
|
|
||||||
// Error(sink state)
|
|
||||||
{
|
|
||||||
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
|
||||||
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
|
||||||
IterativeParsingErrorState
|
|
||||||
},
|
|
||||||
// ObjectInitial
|
// ObjectInitial
|
||||||
{
|
{
|
||||||
IterativeParsingErrorState, // Left bracket
|
IterativeParsingErrorState, // Left bracket
|
||||||
@ -1521,20 +1609,6 @@ private:
|
|||||||
IterativeParsingErrorState, // Null
|
IterativeParsingErrorState, // Null
|
||||||
IterativeParsingErrorState // Number
|
IterativeParsingErrorState // Number
|
||||||
},
|
},
|
||||||
// KeyValueDelimiter
|
|
||||||
{
|
|
||||||
IterativeParsingArrayInitialState, // Left bracket(push MemberValue state)
|
|
||||||
IterativeParsingErrorState, // Right bracket
|
|
||||||
IterativeParsingObjectInitialState, // Left curly bracket(push MemberValue state)
|
|
||||||
IterativeParsingErrorState, // Right curly bracket
|
|
||||||
IterativeParsingErrorState, // Comma
|
|
||||||
IterativeParsingErrorState, // Colon
|
|
||||||
IterativeParsingMemberValueState, // String
|
|
||||||
IterativeParsingMemberValueState, // False
|
|
||||||
IterativeParsingMemberValueState, // True
|
|
||||||
IterativeParsingMemberValueState, // Null
|
|
||||||
IterativeParsingMemberValueState // Number
|
|
||||||
},
|
|
||||||
// MemberValue
|
// MemberValue
|
||||||
{
|
{
|
||||||
IterativeParsingErrorState, // Left bracket
|
IterativeParsingErrorState, // Left bracket
|
||||||
@ -1549,20 +1623,6 @@ private:
|
|||||||
IterativeParsingErrorState, // Null
|
IterativeParsingErrorState, // Null
|
||||||
IterativeParsingErrorState // Number
|
IterativeParsingErrorState // Number
|
||||||
},
|
},
|
||||||
// MemberDelimiter
|
|
||||||
{
|
|
||||||
IterativeParsingErrorState, // Left bracket
|
|
||||||
IterativeParsingErrorState, // Right bracket
|
|
||||||
IterativeParsingErrorState, // Left curly bracket
|
|
||||||
IterativeParsingObjectFinishState, // Right curly bracket
|
|
||||||
IterativeParsingErrorState, // Comma
|
|
||||||
IterativeParsingErrorState, // Colon
|
|
||||||
IterativeParsingMemberKeyState, // String
|
|
||||||
IterativeParsingErrorState, // False
|
|
||||||
IterativeParsingErrorState, // True
|
|
||||||
IterativeParsingErrorState, // Null
|
|
||||||
IterativeParsingErrorState // Number
|
|
||||||
},
|
|
||||||
// ObjectFinish(sink state)
|
// ObjectFinish(sink state)
|
||||||
{
|
{
|
||||||
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
||||||
@ -1597,6 +1657,18 @@ private:
|
|||||||
IterativeParsingErrorState, // Null
|
IterativeParsingErrorState, // Null
|
||||||
IterativeParsingErrorState // Number
|
IterativeParsingErrorState // Number
|
||||||
},
|
},
|
||||||
|
// ArrayFinish(sink state)
|
||||||
|
{
|
||||||
|
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
||||||
|
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
||||||
|
IterativeParsingErrorState
|
||||||
|
},
|
||||||
|
// Single Value (sink state)
|
||||||
|
{
|
||||||
|
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
||||||
|
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
||||||
|
IterativeParsingErrorState
|
||||||
|
},
|
||||||
// ElementDelimiter
|
// ElementDelimiter
|
||||||
{
|
{
|
||||||
IterativeParsingArrayInitialState, // Left bracket(push Element state)
|
IterativeParsingArrayInitialState, // Left bracket(push Element state)
|
||||||
@ -1611,18 +1683,34 @@ private:
|
|||||||
IterativeParsingElementState, // Null
|
IterativeParsingElementState, // Null
|
||||||
IterativeParsingElementState // Number
|
IterativeParsingElementState // Number
|
||||||
},
|
},
|
||||||
// ArrayFinish(sink state)
|
// MemberDelimiter
|
||||||
{
|
{
|
||||||
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
IterativeParsingErrorState, // Left bracket
|
||||||
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
IterativeParsingErrorState, // Right bracket
|
||||||
IterativeParsingErrorState
|
IterativeParsingErrorState, // Left curly bracket
|
||||||
|
IterativeParsingObjectFinishState, // Right curly bracket
|
||||||
|
IterativeParsingErrorState, // Comma
|
||||||
|
IterativeParsingErrorState, // Colon
|
||||||
|
IterativeParsingMemberKeyState, // String
|
||||||
|
IterativeParsingErrorState, // False
|
||||||
|
IterativeParsingErrorState, // True
|
||||||
|
IterativeParsingErrorState, // Null
|
||||||
|
IterativeParsingErrorState // Number
|
||||||
},
|
},
|
||||||
// Single Value (sink state)
|
// KeyValueDelimiter
|
||||||
{
|
{
|
||||||
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
IterativeParsingArrayInitialState, // Left bracket(push MemberValue state)
|
||||||
IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState, IterativeParsingErrorState,
|
IterativeParsingErrorState, // Right bracket
|
||||||
IterativeParsingErrorState
|
IterativeParsingObjectInitialState, // Left curly bracket(push MemberValue state)
|
||||||
}
|
IterativeParsingErrorState, // Right curly bracket
|
||||||
|
IterativeParsingErrorState, // Comma
|
||||||
|
IterativeParsingErrorState, // Colon
|
||||||
|
IterativeParsingMemberValueState, // String
|
||||||
|
IterativeParsingMemberValueState, // False
|
||||||
|
IterativeParsingMemberValueState, // True
|
||||||
|
IterativeParsingMemberValueState, // Null
|
||||||
|
IterativeParsingMemberValueState // Number
|
||||||
|
},
|
||||||
}; // End of G
|
}; // End of G
|
||||||
|
|
||||||
return static_cast<IterativeParsingState>(G[state][token]);
|
return static_cast<IterativeParsingState>(G[state][token]);
|
||||||
@ -1803,44 +1891,53 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RAPIDJSON_FORCEINLINE bool IsIterativeParsingDelimiterState(IterativeParsingState s) {
|
||||||
|
return s >= IterativeParsingElementDelimiterState;
|
||||||
|
}
|
||||||
|
|
||||||
|
RAPIDJSON_FORCEINLINE bool IsIterativeParsingCompleteState(IterativeParsingState s) {
|
||||||
|
return s <= IterativeParsingErrorState;
|
||||||
|
}
|
||||||
|
|
||||||
template <unsigned parseFlags, typename InputStream, typename Handler>
|
template <unsigned parseFlags, typename InputStream, typename Handler>
|
||||||
ParseResult IterativeParse(InputStream& is, Handler& handler) {
|
ParseResult IterativeParse(InputStream& is, Handler& handler) {
|
||||||
parseResult_.Clear();
|
parseResult_.Clear();
|
||||||
ClearStackOnExit scope(*this);
|
ClearStackOnExit scope(*this);
|
||||||
IterativeParsingState state = IterativeParsingStartState;
|
IterativeParsingState state = IterativeParsingStartState;
|
||||||
|
|
||||||
SkipWhitespaceAndComments<parseFlags>(is);
|
SkipWhitespaceAndComments<parseFlags>(is);
|
||||||
RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_);
|
RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_);
|
||||||
while (is.Peek() != '\0') {
|
while (is.Peek() != '\0') {
|
||||||
Token t = Tokenize(is.Peek());
|
Token t = Tokenize(is.Peek());
|
||||||
IterativeParsingState n = Predict(state, t);
|
IterativeParsingState n = Predict(state, t);
|
||||||
IterativeParsingState d = Transit<parseFlags>(state, t, n, is, handler);
|
IterativeParsingState d = Transit<parseFlags>(state, t, n, is, handler);
|
||||||
|
|
||||||
if (d == IterativeParsingErrorState) {
|
if (d == IterativeParsingErrorState) {
|
||||||
HandleError(state, is);
|
HandleError(state, is);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
state = d;
|
state = d;
|
||||||
|
|
||||||
// Do not further consume streams if a root JSON has been parsed.
|
// Do not further consume streams if a root JSON has been parsed.
|
||||||
if ((parseFlags & kParseStopWhenDoneFlag) && state == IterativeParsingFinishState)
|
if ((parseFlags & kParseStopWhenDoneFlag) && state == IterativeParsingFinishState)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
SkipWhitespaceAndComments<parseFlags>(is);
|
SkipWhitespaceAndComments<parseFlags>(is);
|
||||||
RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_);
|
RAPIDJSON_PARSE_ERROR_EARLY_RETURN(parseResult_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the end of file.
|
// Handle the end of file.
|
||||||
if (state != IterativeParsingFinishState)
|
if (state != IterativeParsingFinishState)
|
||||||
HandleError(state, is);
|
HandleError(state, is);
|
||||||
|
|
||||||
return parseResult_;
|
return parseResult_;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string.
|
static const size_t kDefaultStackCapacity = 256; //!< Default stack capacity in bytes for storing a single decoded string.
|
||||||
internal::Stack<StackAllocator> stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing.
|
internal::Stack<StackAllocator> stack_; //!< A stack for storing decoded string temporarily during non-destructive parsing.
|
||||||
ParseResult parseResult_;
|
ParseResult parseResult_;
|
||||||
|
IterativeParsingState state_;
|
||||||
}; // class GenericReader
|
}; // class GenericReader
|
||||||
|
|
||||||
//! Reader with UTF8 encoding and default allocator.
|
//! Reader with UTF8 encoding and default allocator.
|
||||||
|
@ -880,7 +880,7 @@ public:
|
|||||||
#define RAPIDJSON_STRING_(name, ...) \
|
#define RAPIDJSON_STRING_(name, ...) \
|
||||||
static const ValueType& Get##name##String() {\
|
static const ValueType& Get##name##String() {\
|
||||||
static const Ch s[] = { __VA_ARGS__, '\0' };\
|
static const Ch s[] = { __VA_ARGS__, '\0' };\
|
||||||
static const ValueType v(s, sizeof(s) / sizeof(Ch) - 1);\
|
static const ValueType v(s, static_cast<SizeType>(sizeof(s) / sizeof(Ch) - 1));\
|
||||||
return v;\
|
return v;\
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,5 +71,7 @@ Changed
|
|||||||
targets {
|
targets {
|
||||||
// We're trying to be standard about these sorts of thing. (Will help with config.h later :D)
|
// We're trying to be standard about these sorts of thing. (Will help with config.h later :D)
|
||||||
//Defines += HAS_EQCORE;
|
//Defines += HAS_EQCORE;
|
||||||
|
// Fix creating the package with Raggles' fork of CoApp
|
||||||
|
Includes += "$(MSBuildThisFileDirectory)../..${d_include}";
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -152,6 +152,35 @@ TEST_F(RapidJson, SIMD_SUFFIX(ReaderParseIterativeInsitu_DummyHandler)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(RapidJson, SIMD_SUFFIX(ReaderParseIterativePull_DummyHandler)) {
|
||||||
|
for (size_t i = 0; i < kTrialCount; i++) {
|
||||||
|
StringStream s(json_);
|
||||||
|
BaseReaderHandler<> h;
|
||||||
|
Reader reader;
|
||||||
|
reader.IterativeParseInit();
|
||||||
|
while (!reader.IterativeParseComplete()) {
|
||||||
|
if (!reader.IterativeParseNext<kParseDefaultFlags>(s, h))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
EXPECT_FALSE(reader.HasParseError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(RapidJson, SIMD_SUFFIX(ReaderParseIterativePullInsitu_DummyHandler)) {
|
||||||
|
for (size_t i = 0; i < kTrialCount; i++) {
|
||||||
|
memcpy(temp_, json_, length_ + 1);
|
||||||
|
InsituStringStream s(temp_);
|
||||||
|
BaseReaderHandler<> h;
|
||||||
|
Reader reader;
|
||||||
|
reader.IterativeParseInit();
|
||||||
|
while (!reader.IterativeParseComplete()) {
|
||||||
|
if (!reader.IterativeParseNext<kParseDefaultFlags|kParseInsituFlag>(s, h))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
EXPECT_FALSE(reader.HasParseError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler_ValidateEncoding)) {
|
TEST_F(RapidJson, SIMD_SUFFIX(ReaderParse_DummyHandler_ValidateEncoding)) {
|
||||||
for (size_t i = 0; i < kTrialCount; i++) {
|
for (size_t i = 0; i < kTrialCount; i++) {
|
||||||
StringStream s(json_);
|
StringStream s(json_);
|
||||||
|
@ -48,6 +48,24 @@ static char* ReadFile(const char* filename, size_t& length) {
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct NoOpHandler {
|
||||||
|
bool Null() { return true; }
|
||||||
|
bool Bool(bool) { return true; }
|
||||||
|
bool Int(int) { return true; }
|
||||||
|
bool Uint(unsigned) { return true; }
|
||||||
|
bool Int64(int64_t) { return true; }
|
||||||
|
bool Uint64(uint64_t) { return true; }
|
||||||
|
bool Double(double) { return true; }
|
||||||
|
bool RawNumber(const char*, SizeType, bool) { return true; }
|
||||||
|
bool String(const char*, SizeType, bool) { return true; }
|
||||||
|
bool StartObject() { return true; }
|
||||||
|
bool Key(const char*, SizeType, bool) { return true; }
|
||||||
|
bool EndObject(SizeType) { return true; }
|
||||||
|
bool StartArray() { return true; }
|
||||||
|
bool EndArray(SizeType) { return true; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
TEST(JsonChecker, Reader) {
|
TEST(JsonChecker, Reader) {
|
||||||
char filename[256];
|
char filename[256];
|
||||||
|
|
||||||
@ -67,13 +85,26 @@ TEST(JsonChecker, Reader) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test stack-based parsing.
|
||||||
GenericDocument<UTF8<>, CrtAllocator> document; // Use Crt allocator to check exception-safety (no memory leak)
|
GenericDocument<UTF8<>, CrtAllocator> document; // Use Crt allocator to check exception-safety (no memory leak)
|
||||||
document.Parse(json);
|
document.Parse(json);
|
||||||
EXPECT_TRUE(document.HasParseError()) << filename;
|
EXPECT_TRUE(document.HasParseError()) << filename;
|
||||||
|
|
||||||
|
// Test iterative parsing.
|
||||||
document.Parse<kParseIterativeFlag>(json);
|
document.Parse<kParseIterativeFlag>(json);
|
||||||
EXPECT_TRUE(document.HasParseError()) << filename;
|
EXPECT_TRUE(document.HasParseError()) << filename;
|
||||||
|
|
||||||
|
// Test iterative pull-parsing.
|
||||||
|
Reader reader;
|
||||||
|
StringStream ss(json);
|
||||||
|
NoOpHandler h;
|
||||||
|
reader.IterativeParseInit();
|
||||||
|
while (!reader.IterativeParseComplete()) {
|
||||||
|
if (!reader.IterativeParseNext<kParseDefaultFlags>(ss, h))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
EXPECT_TRUE(reader.HasParseError()) << filename;
|
||||||
|
|
||||||
free(json);
|
free(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,12 +118,25 @@ TEST(JsonChecker, Reader) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test stack-based parsing.
|
||||||
GenericDocument<UTF8<>, CrtAllocator> document; // Use Crt allocator to check exception-safety (no memory leak)
|
GenericDocument<UTF8<>, CrtAllocator> document; // Use Crt allocator to check exception-safety (no memory leak)
|
||||||
document.Parse(json);
|
document.Parse(json);
|
||||||
EXPECT_FALSE(document.HasParseError()) << filename;
|
EXPECT_FALSE(document.HasParseError()) << filename;
|
||||||
|
|
||||||
|
// Test iterative parsing.
|
||||||
document.Parse<kParseIterativeFlag>(json);
|
document.Parse<kParseIterativeFlag>(json);
|
||||||
EXPECT_FALSE(document.HasParseError()) << filename;
|
EXPECT_FALSE(document.HasParseError()) << filename;
|
||||||
|
|
||||||
|
// Test iterative pull-parsing.
|
||||||
|
Reader reader;
|
||||||
|
StringStream ss(json);
|
||||||
|
NoOpHandler h;
|
||||||
|
reader.IterativeParseInit();
|
||||||
|
while (!reader.IterativeParseComplete()) {
|
||||||
|
if (!reader.IterativeParseNext<kParseDefaultFlags>(ss, h))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
EXPECT_FALSE(reader.HasParseError()) << filename;
|
||||||
|
|
||||||
free(json);
|
free(json);
|
||||||
}
|
}
|
||||||
|
@ -1157,22 +1157,22 @@ template<typename Encoding = UTF8<> >
|
|||||||
struct IterativeParsingReaderHandler {
|
struct IterativeParsingReaderHandler {
|
||||||
typedef typename Encoding::Ch Ch;
|
typedef typename Encoding::Ch Ch;
|
||||||
|
|
||||||
const static int LOG_NULL = -1;
|
const static uint32_t LOG_NULL = 0x10000000;
|
||||||
const static int LOG_BOOL = -2;
|
const static uint32_t LOG_BOOL = 0x20000000;
|
||||||
const static int LOG_INT = -3;
|
const static uint32_t LOG_INT = 0x30000000;
|
||||||
const static int LOG_UINT = -4;
|
const static uint32_t LOG_UINT = 0x40000000;
|
||||||
const static int LOG_INT64 = -5;
|
const static uint32_t LOG_INT64 = 0x50000000;
|
||||||
const static int LOG_UINT64 = -6;
|
const static uint32_t LOG_UINT64 = 0x60000000;
|
||||||
const static int LOG_DOUBLE = -7;
|
const static uint32_t LOG_DOUBLE = 0x70000000;
|
||||||
const static int LOG_STRING = -8;
|
const static uint32_t LOG_STRING = 0x80000000;
|
||||||
const static int LOG_STARTOBJECT = -9;
|
const static uint32_t LOG_STARTOBJECT = 0x90000000;
|
||||||
const static int LOG_KEY = -10;
|
const static uint32_t LOG_KEY = 0xA0000000;
|
||||||
const static int LOG_ENDOBJECT = -11;
|
const static uint32_t LOG_ENDOBJECT = 0xB0000000;
|
||||||
const static int LOG_STARTARRAY = -12;
|
const static uint32_t LOG_STARTARRAY = 0xC0000000;
|
||||||
const static int LOG_ENDARRAY = -13;
|
const static uint32_t LOG_ENDARRAY = 0xD0000000;
|
||||||
|
|
||||||
const static size_t LogCapacity = 256;
|
const static size_t LogCapacity = 256;
|
||||||
int Logs[LogCapacity];
|
uint32_t Logs[LogCapacity];
|
||||||
size_t LogCount;
|
size_t LogCount;
|
||||||
|
|
||||||
IterativeParsingReaderHandler() : LogCount(0) {
|
IterativeParsingReaderHandler() : LogCount(0) {
|
||||||
@ -1202,8 +1202,8 @@ struct IterativeParsingReaderHandler {
|
|||||||
|
|
||||||
bool EndObject(SizeType c) {
|
bool EndObject(SizeType c) {
|
||||||
RAPIDJSON_ASSERT(LogCount < LogCapacity);
|
RAPIDJSON_ASSERT(LogCount < LogCapacity);
|
||||||
Logs[LogCount++] = LOG_ENDOBJECT;
|
RAPIDJSON_ASSERT((static_cast<uint32_t>(c) & 0xF0000000) == 0);
|
||||||
Logs[LogCount++] = static_cast<int>(c);
|
Logs[LogCount++] = LOG_ENDOBJECT | static_cast<uint32_t>(c);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1211,8 +1211,8 @@ struct IterativeParsingReaderHandler {
|
|||||||
|
|
||||||
bool EndArray(SizeType c) {
|
bool EndArray(SizeType c) {
|
||||||
RAPIDJSON_ASSERT(LogCount < LogCapacity);
|
RAPIDJSON_ASSERT(LogCount < LogCapacity);
|
||||||
Logs[LogCount++] = LOG_ENDARRAY;
|
RAPIDJSON_ASSERT((static_cast<uint32_t>(c) & 0xF0000000) == 0);
|
||||||
Logs[LogCount++] = static_cast<int>(c);
|
Logs[LogCount++] = LOG_ENDARRAY | static_cast<uint32_t>(c);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1228,7 +1228,7 @@ TEST(Reader, IterativeParsing_General) {
|
|||||||
EXPECT_FALSE(r.IsError());
|
EXPECT_FALSE(r.IsError());
|
||||||
EXPECT_FALSE(reader.HasParseError());
|
EXPECT_FALSE(reader.HasParseError());
|
||||||
|
|
||||||
int e[] = {
|
uint32_t e[] = {
|
||||||
handler.LOG_STARTARRAY,
|
handler.LOG_STARTARRAY,
|
||||||
handler.LOG_INT,
|
handler.LOG_INT,
|
||||||
handler.LOG_STARTOBJECT,
|
handler.LOG_STARTOBJECT,
|
||||||
@ -1236,14 +1236,14 @@ TEST(Reader, IterativeParsing_General) {
|
|||||||
handler.LOG_STARTARRAY,
|
handler.LOG_STARTARRAY,
|
||||||
handler.LOG_INT,
|
handler.LOG_INT,
|
||||||
handler.LOG_INT,
|
handler.LOG_INT,
|
||||||
handler.LOG_ENDARRAY, 2,
|
handler.LOG_ENDARRAY | 2,
|
||||||
handler.LOG_ENDOBJECT, 1,
|
handler.LOG_ENDOBJECT | 1,
|
||||||
handler.LOG_NULL,
|
handler.LOG_NULL,
|
||||||
handler.LOG_BOOL,
|
handler.LOG_BOOL,
|
||||||
handler.LOG_BOOL,
|
handler.LOG_BOOL,
|
||||||
handler.LOG_STRING,
|
handler.LOG_STRING,
|
||||||
handler.LOG_DOUBLE,
|
handler.LOG_DOUBLE,
|
||||||
handler.LOG_ENDARRAY, 7
|
handler.LOG_ENDARRAY | 7
|
||||||
};
|
};
|
||||||
|
|
||||||
EXPECT_EQ(sizeof(e) / sizeof(int), handler.LogCount);
|
EXPECT_EQ(sizeof(e) / sizeof(int), handler.LogCount);
|
||||||
@ -1265,20 +1265,20 @@ TEST(Reader, IterativeParsing_Count) {
|
|||||||
EXPECT_FALSE(r.IsError());
|
EXPECT_FALSE(r.IsError());
|
||||||
EXPECT_FALSE(reader.HasParseError());
|
EXPECT_FALSE(reader.HasParseError());
|
||||||
|
|
||||||
int e[] = {
|
uint32_t e[] = {
|
||||||
handler.LOG_STARTARRAY,
|
handler.LOG_STARTARRAY,
|
||||||
handler.LOG_STARTOBJECT,
|
handler.LOG_STARTOBJECT,
|
||||||
handler.LOG_ENDOBJECT, 0,
|
handler.LOG_ENDOBJECT | 0,
|
||||||
handler.LOG_STARTOBJECT,
|
handler.LOG_STARTOBJECT,
|
||||||
handler.LOG_KEY,
|
handler.LOG_KEY,
|
||||||
handler.LOG_INT,
|
handler.LOG_INT,
|
||||||
handler.LOG_ENDOBJECT, 1,
|
handler.LOG_ENDOBJECT | 1,
|
||||||
handler.LOG_STARTARRAY,
|
handler.LOG_STARTARRAY,
|
||||||
handler.LOG_INT,
|
handler.LOG_INT,
|
||||||
handler.LOG_ENDARRAY, 1,
|
handler.LOG_ENDARRAY | 1,
|
||||||
handler.LOG_STARTARRAY,
|
handler.LOG_STARTARRAY,
|
||||||
handler.LOG_ENDARRAY, 0,
|
handler.LOG_ENDARRAY | 0,
|
||||||
handler.LOG_ENDARRAY, 4
|
handler.LOG_ENDARRAY | 4
|
||||||
};
|
};
|
||||||
|
|
||||||
EXPECT_EQ(sizeof(e) / sizeof(int), handler.LogCount);
|
EXPECT_EQ(sizeof(e) / sizeof(int), handler.LogCount);
|
||||||
@ -1289,6 +1289,51 @@ TEST(Reader, IterativeParsing_Count) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(Reader, IterativePullParsing_General) {
|
||||||
|
{
|
||||||
|
IterativeParsingReaderHandler<> handler;
|
||||||
|
uint32_t e[] = {
|
||||||
|
handler.LOG_STARTARRAY,
|
||||||
|
handler.LOG_INT,
|
||||||
|
handler.LOG_STARTOBJECT,
|
||||||
|
handler.LOG_KEY,
|
||||||
|
handler.LOG_STARTARRAY,
|
||||||
|
handler.LOG_INT,
|
||||||
|
handler.LOG_INT,
|
||||||
|
handler.LOG_ENDARRAY | 2,
|
||||||
|
handler.LOG_ENDOBJECT | 1,
|
||||||
|
handler.LOG_NULL,
|
||||||
|
handler.LOG_BOOL,
|
||||||
|
handler.LOG_BOOL,
|
||||||
|
handler.LOG_STRING,
|
||||||
|
handler.LOG_DOUBLE,
|
||||||
|
handler.LOG_ENDARRAY | 7
|
||||||
|
};
|
||||||
|
|
||||||
|
StringStream is("[1, {\"k\": [1, 2]}, null, false, true, \"string\", 1.2]");
|
||||||
|
Reader reader;
|
||||||
|
|
||||||
|
reader.IterativeParseInit();
|
||||||
|
while (!reader.IterativeParseComplete()) {
|
||||||
|
size_t oldLogCount = handler.LogCount;
|
||||||
|
EXPECT_TRUE(oldLogCount < sizeof(e) / sizeof(int)) << "overrun";
|
||||||
|
|
||||||
|
EXPECT_TRUE(reader.IterativeParseNext<kParseDefaultFlags>(is, handler)) << "parse fail";
|
||||||
|
EXPECT_EQ(handler.LogCount, oldLogCount + 1) << "handler should be invoked exactly once each time";
|
||||||
|
EXPECT_EQ(e[oldLogCount], handler.Logs[oldLogCount]) << "wrong event returned";
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_FALSE(reader.HasParseError());
|
||||||
|
EXPECT_EQ(sizeof(e) / sizeof(int), handler.LogCount) << "handler invoked wrong number of times";
|
||||||
|
|
||||||
|
// The handler should not be invoked when the JSON has been fully read, but it should not fail
|
||||||
|
size_t oldLogCount = handler.LogCount;
|
||||||
|
EXPECT_TRUE(reader.IterativeParseNext<kParseDefaultFlags>(is, handler)) << "parse-next past complete is allowed";
|
||||||
|
EXPECT_EQ(handler.LogCount, oldLogCount) << "parse-next past complete should not invoke handler";
|
||||||
|
EXPECT_FALSE(reader.HasParseError()) << "parse-next past complete should not generate parse error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test iterative parsing on kParseErrorTermination.
|
// Test iterative parsing on kParseErrorTermination.
|
||||||
struct HandlerTerminateAtStartObject : public IterativeParsingReaderHandler<> {
|
struct HandlerTerminateAtStartObject : public IterativeParsingReaderHandler<> {
|
||||||
bool StartObject() { return false; }
|
bool StartObject() { return false; }
|
||||||
@ -1832,6 +1877,10 @@ TEST(Reader, ParseNanAndInfinity) {
|
|||||||
TEST_NAN_INF("Infinity", inf);
|
TEST_NAN_INF("Infinity", inf);
|
||||||
TEST_NAN_INF("-Inf", -inf);
|
TEST_NAN_INF("-Inf", -inf);
|
||||||
TEST_NAN_INF("-Infinity", -inf);
|
TEST_NAN_INF("-Infinity", -inf);
|
||||||
|
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "NInf", 1);
|
||||||
|
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "NaInf", 2);
|
||||||
|
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "INan", 1);
|
||||||
|
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "InNan", 2);
|
||||||
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "nan", 1);
|
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "nan", 1);
|
||||||
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "-nan", 1);
|
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "-nan", 1);
|
||||||
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "NAN", 1);
|
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "NAN", 1);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user