Commit c56b5004 authored by Norby, Tom's avatar Norby, Tom

Template JSONParser; add tests.

parent 44a307f8
Pipeline #95934 failed with stages
in 5 minutes and 17 seconds
......@@ -8,13 +8,13 @@
#include <sstream>
#include <vector>
#include "radixcore/value.hh"
#include "radixcore/visibility.hh"
//-----------------------------------------------------------------------------
// see www.json.org for parsing grammar
namespace radix
{
template <class JsonType>
class RADIX_PUBLIC JSONParser
{
public:
......@@ -29,7 +29,7 @@ class RADIX_PUBLIC JSONParser
}
//-------------------------------------------------------------------------
Value& root() { return m_root; }
JsonType& root() { return m_root; }
//-------------------------------------------------------------------------
std::string last_error()
......@@ -136,7 +136,7 @@ class RADIX_PUBLIC JSONParser
}
//-------------------------------------------------------------------------
Value parse_array()
JsonType parse_array()
{
if (m_po >= m_text.size()) return nullptr;
if (m_text[m_po] != '[') return nullptr;
......@@ -146,11 +146,11 @@ class RADIX_PUBLIC JSONParser
size_t col_prev = m_col;
bool trailing_comma = false;
Value parent = Value(DataArray());
JsonType parent = JsonType(DataArray());
for (; m_po < m_text.size(); m_po++, m_col++)
{
skip_whitespace();
Value child = parse_value();
JsonType child = parse_value();
if (child.is_null())
{
if (m_last_error != "")
......@@ -168,7 +168,7 @@ class RADIX_PUBLIC JSONParser
}
else if (m_text[m_po] == ']')
break;
return Value();
return JsonType();
}
else
{
......@@ -191,13 +191,13 @@ class RADIX_PUBLIC JSONParser
m_last_error = "invalid character '";
m_last_error += ch;
m_last_error += "' in array";
return Value();
return JsonType();
}
}
if (m_po >= m_text.size() || m_text[m_po] != ']')
{
m_last_error = "no closing bracket ']' for array";
return Value();
return JsonType();
}
m_po++;
m_col++;
......@@ -211,9 +211,9 @@ class RADIX_PUBLIC JSONParser
// (0|([1-9][0-9]*))
// (\.[0-9]+)?
// ([Ee][+-]?[0-9]+)?
Value parse_number()
JsonType parse_number()
{
if (m_po >= m_text.size()) return Value();
if (m_po >= m_text.size()) return JsonType();
size_t len = 0;
......@@ -226,7 +226,7 @@ class RADIX_PUBLIC JSONParser
if (m_po >= m_text.size())
{
m_last_error = "invalid number (no digits after -)";
return Value();
return JsonType();
}
}
......@@ -235,7 +235,7 @@ class RADIX_PUBLIC JSONParser
if (m_po >= m_text.size() || !(ch >= '0' && m_text[m_po] <= '9'))
{
m_last_error = "invalid number (no digits)";
return Value();
return JsonType();
}
m_po++;
m_col++;
......@@ -254,12 +254,13 @@ class RADIX_PUBLIC JSONParser
{
try
{
Value node = Value(std::stod(std::string(&m_text[m_po - len], len)));
JsonType node =
JsonType(std::stod(std::string(&m_text[m_po - len], len)));
return node;
}
catch (...)
{
return Value();
return JsonType();
}
}
ch = m_text[m_po];
......@@ -273,7 +274,7 @@ class RADIX_PUBLIC JSONParser
if (m_po >= m_text.size())
{
m_last_error = "invalid number (no digits after decimal)";
return Value();
return JsonType();
}
ch = m_text[m_po];
size_t n_digits = 0;
......@@ -286,7 +287,7 @@ class RADIX_PUBLIC JSONParser
if (n_digits == 0)
{
m_last_error = "invalid number (no digits after decimal)";
return Value();
return JsonType();
}
}
......@@ -299,7 +300,7 @@ class RADIX_PUBLIC JSONParser
if (m_po >= m_text.size())
{
m_last_error = "invalid number (no digits for exponent)";
return Value();
return JsonType();
}
ch = m_text[m_po];
// [+-]?
......@@ -312,7 +313,7 @@ class RADIX_PUBLIC JSONParser
if (m_po >= m_text.size())
{
m_last_error = "invalid number (no digits for exponent)";
return Value();
return JsonType();
}
size_t n_digits = 0;
......@@ -325,18 +326,19 @@ class RADIX_PUBLIC JSONParser
if (n_digits == 0)
{
m_last_error = "invalid number (no digits after decimal)";
return Value();
return JsonType();
}
}
try
{
Value node = Value(std::stod(std::string(&m_text[m_po - len], len)));
JsonType node =
JsonType(std::stod(std::string(&m_text[m_po - len], len)));
return node;
}
catch (...)
{
return Value();
return JsonType();
}
}
......@@ -357,7 +359,7 @@ class RADIX_PUBLIC JSONParser
// only 3 valid literals all in lower case: false, null, true
// TODO: need to refactor code to support null return (currently it is
// treated as an error)
Value parse_literal()
JsonType parse_literal()
{
size_t len = 0;
for (; m_po + len < m_text.size(); len++)
......@@ -373,32 +375,32 @@ class RADIX_PUBLIC JSONParser
m_col += len;
if (std::string("true") == literals[i])
{
return Value(true);
return JsonType(true);
}
else if (std::string("false") == literals[i])
{
return Value(false);
return JsonType(false);
}
// default to null
return Value();
return JsonType();
}
}
m_last_error = "invalid literal";
return Value();
return JsonType();
}
//-------------------------------------------------------------------------
Value parse_object()
JsonType parse_object()
{
if (m_po >= m_text.size()) return Value();
if (m_text[m_po] != '{') return Value();
if (m_po >= m_text.size()) return JsonType();
if (m_text[m_po] != '{') return JsonType();
m_po++;
m_col++;
size_t line_prev = m_line;
size_t col_prev = m_col;
bool trailing_comma = false;
Value parent = Value(DataObject());
JsonType parent = JsonType(DataObject());
for (; m_po < m_text.size(); m_po++, m_col++)
{
skip_whitespace();
......@@ -410,7 +412,7 @@ class RADIX_PUBLIC JSONParser
m_last_error = "trailing comma in object";
m_line = line_prev;
m_col = col_prev;
return Value();
return JsonType();
}
break;
}
......@@ -425,7 +427,7 @@ class RADIX_PUBLIC JSONParser
m_line = line_prev;
m_col = col_prev;
}
return Value();
return JsonType();
}
skip_whitespace();
......@@ -433,13 +435,13 @@ class RADIX_PUBLIC JSONParser
if (m_po >= m_text.size() || m_text[m_po] != ':')
{
m_last_error = "no ':' following key in object";
return Value();
return JsonType();
}
m_po++;
m_col++;
// parse value
Value child = parse_value();
JsonType child = parse_value();
if (child.is_null())
{
if (m_last_error == "")
......@@ -452,7 +454,7 @@ class RADIX_PUBLIC JSONParser
m_line = line_prev;
m_col = col_prev;
}
return Value();
return JsonType();
}
else
{
......@@ -474,13 +476,13 @@ class RADIX_PUBLIC JSONParser
else
{
m_last_error = "invalid character in object";
return Value();
return JsonType();
}
}
if (m_po >= m_text.size() || m_text[m_po] != '}')
{
m_last_error = "no closing curly bracket '}' for object";
return Value();
return JsonType();
}
m_po++;
m_col++;
......@@ -584,19 +586,19 @@ class RADIX_PUBLIC JSONParser
}
//-------------------------------------------------------------------------
Value parse_string()
JsonType parse_string()
{
std::string str = parse_string_contents();
if (m_last_error != "") return Value();
return Value(str);
if (m_last_error != "") return JsonType();
return JsonType(str);
}
//-------------------------------------------------------------------------
Value parse_value()
JsonType parse_value()
{
skip_whitespace();
char ch = m_text[m_po];
Value node;
JsonType node;
if (ch == '"')
node = parse_string();
else if (ch == '[')
......@@ -617,7 +619,7 @@ class RADIX_PUBLIC JSONParser
const char* literals[N_LITERALS];
size_t literal_lens[N_LITERALS];
Value m_root;
JsonType m_root;
std::string m_text = "";
......
INCLUDE(GoogleTest)
ADD_GOOGLE_TEST(tstJson.cc NP 1)
ADD_GOOGLE_TEST(tstString.cc NP 1)
ADD_GOOGLE_TEST(tstSystem.cc NP 1)
ADD_GOOGLE_TEST(tstValue.cc NP 1)
configure_file(testfiles/wellformed.json testfiles/wellformed.json COPYONLY)
{
"root object": {
"bool": true,
"int": 1,
"double": 2.34,
"string": "This is a string!",
"array": [
"Array of JSON values.",
1,
2.34,
true
]
}
}
\ No newline at end of file
#include "gtest/gtest.h"
#include "radixcore/json.hh"
#include "radixcore/value.hh"
#include <QDir>
using namespace radix;
// JsonType& root() { return m_root; }
// std::string last_error()
// bool parse()
// bool parse_from_stream(std::istream& in_stream)
// bool parse_from_file(std::string fn)
TEST(JSONParser, parse_from_file)
{
QDir workingDir(".");
{
JSONParser<Value> sunnyDay;
std::string filename =
workingDir.absoluteFilePath("testfiles/wellformed.json").toStdString();
bool success = sunnyDay.parse_from_file(filename);
EXPECT_TRUE(success);
// not really an error just the null terminating at eof
std::string errorMsg = sunnyDay.last_error();
EXPECT_EQ(" at line 14 column 2", errorMsg);
Value root = sunnyDay.root();
Value rootObj = root["root object"];
EXPECT_EQ(Value::TYPE_OBJECT, rootObj.type());
EXPECT_EQ(Value::TYPE_BOOLEAN, rootObj["bool"].type());
EXPECT_TRUE(rootObj["bool"].to_bool());
// reads in as double, not int.
// EXPECT_EQ(Value::TYPE_INTEGER, rootObj["int"].type());
EXPECT_EQ(1, rootObj["int"].to_int());
EXPECT_EQ(Value::TYPE_DOUBLE, rootObj["double"].type());
EXPECT_EQ(2.34, rootObj["double"].to_double());
EXPECT_EQ(Value::TYPE_STRING, rootObj["string"].type());
EXPECT_EQ("This is a string!", rootObj["string"].to_string());
EXPECT_EQ(Value::TYPE_ARRAY, rootObj["array"].type());
DataArray& array = rootObj["array"].as_array();
EXPECT_EQ("Array of JSON values.", array.at(0).to_string());
EXPECT_EQ(1, array.at(1).to_int());
EXPECT_EQ(2.34, array.at(2).to_double());
EXPECT_TRUE(array.at(3).to_bool());
}
{
JSONParser<Value> noFile;
std::string filename =
workingDir.absoluteFilePath("wrong_path/file_doesn't_exist.json")
.toStdString();
bool success = noFile.parse_from_file(filename);
EXPECT_FALSE(success);
std::string errorMsg = noFile.last_error();
EXPECT_EQ("could not open file at line 1 column 1", errorMsg);
}
}
TEST(JSONParser, parse_from_stream)
{
// check code paths in JSONParser class
{
JSONParser<Value> jp;
std::string errorMsg = "";
// trailing junk
std::stringstream ss("{}\r\nfoo");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("unexpected trailing character(s) at line 2 column 1", errorMsg);
// unclosed array with no child elements
ss.str("[");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("no closing bracket ']' for array at line 1 column 2", errorMsg);
// unclosed array with at least one child element
ss.str("[ \"bar\"");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
std::string expected("invalid character '\0' in array at line 1 column 8",
49);
EXPECT_EQ(expected, errorMsg);
// trailing comma in array
ss.str("[ \"baz\" , ]");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("trailing comma in array at line 1 column 9", errorMsg);
// lone '-' at end (considered as invalid number)
ss.str("-");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("invalid number (no digits after -) at line 1 column 2",
errorMsg);
// lone '-' followed by whitespace (considered as invalid number)
ss.str("- ");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("invalid number (no digits) at line 1 column 2", errorMsg);
// no digits after decimal
ss.str("5.");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("invalid number (no digits after decimal) at line 1 column 3",
errorMsg);
// no digits after decimal
ss.str("5. ");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("invalid number (no digits after decimal) at line 1 column 3",
errorMsg);
// no digits after 'e' in exponent at end
ss.str("1.2e");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("invalid number (no digits for exponent) at line 1 column 5",
errorMsg);
// no digits after 'e' in exponent at end
ss.str("1.2e-");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("invalid number (no digits for exponent) at line 1 column 6",
errorMsg);
// no digits after 'e' in exponent followed by whitespace
ss.str("3.4E ");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("invalid number (no digits after decimal) at line 1 column 5",
errorMsg);
// invalid literal
ss.str("foo");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("invalid literal at line 1 column 1", errorMsg);
// null literal
ss.str("null");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ(" at line 1 column 5", errorMsg);
// unclosed object with no child elements
ss.str("{");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("no closing curly bracket '}' for object at line 1 column 2",
errorMsg);
// object child with bad key
ss.str("{ \"bar");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("string missing closing quote at line 1 column 7", errorMsg);
// unclosed object with at least one child element
ss.str("{ \"bar\" : 1");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("invalid character in object at line 1 column 12", errorMsg);
// trailing comma in object
ss.str("{ \"baz\" : 1 , }");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("trailing comma in object at line 1 column 13", errorMsg);
// missing value in object
ss.str("{ \"foo\" : }");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("missing value in object at line 1 column 11", errorMsg);
// missing ':' and value in object
ss.str("{ \"foo\" }");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("no ':' following key in object at line 1 column 9", errorMsg);
// invalid escape sequence
ss.str("\"\\");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ(
"incomplete unicode character escape sequence in string at line 1 "
"column 3",
errorMsg);
// invalid escape sequence
ss.str("\"\\q");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("invalid escape sequence in string at line 1 column 3", errorMsg);
// incomplete unicode escape sequence
ss.str("\"\\u012");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ(
"incomplete unicode character escape sequence in string at line 1 "
"column 7",
errorMsg);
// invalid unicode escape sequence
ss.str("\"\\u012Q\"");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ(
"invalid unicode character escape sequence in string at line 1 column "
"7",
errorMsg);
// non-terminated string
ss.str("\"bar");
EXPECT_FALSE(jp.parse_from_stream(ss));
errorMsg = jp.last_error();
EXPECT_EQ("string missing closing quote at line 1 column 5", errorMsg);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment