Commit f7fc3fd4 authored by Huff, Israel's avatar Huff, Israel
Browse files

- changed handling of parse errors to use boolean returns instead of using...

- changed handling of parse errors to use boolean returns instead of using is_null() since using the later causes "null" literals to fail
- fixed a few parser bugs found by unit tests
- fixed a bug in unit test code causing false negatives
- now allowing one trailing comma in arrays and objects
parent ff54c93e
Pipeline #96293 failed with stages
in 50 minutes and 59 seconds
......@@ -48,8 +48,9 @@ class RADIX_PUBLIC JSONParserImpl
m_col = 1;
m_last_error = "";
m_root = parse_value();
if (m_root.is_null()) return false;
m_root = value_type();
bool result = parse_value(m_root);
if (!result) return false;
skip_whitespace();
if (m_po != m_text.size())
{
......@@ -148,42 +149,45 @@ class RADIX_PUBLIC JSONParserImpl
}
//-------------------------------------------------------------------------
value_type parse_array()
bool parse_array(value_type& value)
{
if (m_po >= m_text.size()) return nullptr;
if (m_text[m_po] != '[') return nullptr;
value = value_type();
if (m_po >= m_text.size()) return false;
if (m_text[m_po] != '[') return false;
m_po++;
m_col++;
// save line/column state
size_t line_prev = m_line;
size_t col_prev = m_col;
value_type parent = array_type();
bool found_child = false;
for (; m_po < m_text.size(); m_po++, m_col++)
{
skip_whitespace();
value_type child = parse_value();
if (child.is_null())
if (m_text[m_po] == ']') break;
value_type child;
if (!parse_value(child))
{
if (m_last_error != "")
{
}
else if (m_po >= m_text.size())
found_child = false;
if (m_po >= m_text.size())
{
set_error("no closing bracket ']' for array");
}
else if (m_text[m_po] == ']')
break;
return value_type();
}
else
{
found_child = true;
parent.as_array().push_back(child);
}
skip_whitespace();
char ch = m_text[m_po];
if (ch == ',')
{
if (!found_child)
{
set_error("missing value in array");
return false;
}
continue;
}
else if (ch == ']')
......@@ -194,18 +198,19 @@ class RADIX_PUBLIC JSONParserImpl
err_msg += ch;
err_msg += "' in array";
set_error(err_msg);
return value_type();
return false;
}
}
if (m_po >= m_text.size() || m_text[m_po] != ']')
if (m_po >= m_text.size() || m_text[m_po] != ']' || m_last_error != "")
{
set_error("no closing bracket ']' for array");
return value_type();
return false;
}
m_po++;
m_col++;
return parent;
value = parent;
return true;
}
//-------------------------------------------------------------------------
......@@ -214,9 +219,10 @@ class RADIX_PUBLIC JSONParserImpl
// (0|([1-9][0-9]*))
// (\.[0-9]+)?
// ([Ee][+-]?[0-9]+)?
value_type parse_number()
bool parse_number(value_type& value)
{
if (m_po >= m_text.size()) return value_type();
value = value_type();
if (m_po >= m_text.size()) return false;
size_t len = 0;
......@@ -229,7 +235,7 @@ class RADIX_PUBLIC JSONParserImpl
if (m_po >= m_text.size())
{
set_error("invalid number (no digits after -)");
return value_type();
return false;
}
}
......@@ -238,7 +244,7 @@ class RADIX_PUBLIC JSONParserImpl
if (m_po >= m_text.size() || !(ch >= '0' && m_text[m_po] <= '9'))
{
set_error("invalid number (no digits)");
return value_type();
return false;
}
m_po++;
m_col++;
......@@ -257,13 +263,12 @@ class RADIX_PUBLIC JSONParserImpl
{
try
{
value_type node =
value_type(std::stod(std::string(&m_text[m_po - len], len)));
return node;
value = value_type(std::stod(std::string(&m_text[m_po - len], len)));
return true;
}
catch (...)
{
return value_type();
return false;
}
}
ch = m_text[m_po];
......@@ -277,7 +282,7 @@ class RADIX_PUBLIC JSONParserImpl
if (m_po >= m_text.size())
{
set_error("invalid number (no digits after decimal)");
return value_type();
return false;
}
ch = m_text[m_po];
size_t n_digits = 0;
......@@ -290,7 +295,7 @@ class RADIX_PUBLIC JSONParserImpl
if (n_digits == 0)
{
set_error("invalid number (no digits after decimal)");
return value_type();
return false;
}
}
......@@ -303,7 +308,7 @@ class RADIX_PUBLIC JSONParserImpl
if (m_po >= m_text.size())
{
set_error("invalid number (no digits for exponent)");
return value_type();
return false;
}
ch = m_text[m_po];
// [+-]?
......@@ -316,7 +321,7 @@ class RADIX_PUBLIC JSONParserImpl
if (m_po >= m_text.size())
{
set_error("invalid number (no digits for exponent)");
return value_type();
return false;
}
size_t n_digits = 0;
......@@ -328,20 +333,19 @@ class RADIX_PUBLIC JSONParserImpl
}
if (n_digits == 0)
{
set_error("invalid number (no digits after decimal)");
return value_type();
set_error("invalid number (no digits for exponent)");
return false;
}
}
try
{
value_type node =
value_type(std::stod(std::string(&m_text[m_po - len], len)));
return node;
value = value_type(std::stod(std::string(&m_text[m_po - len], len)));
return true;
}
catch (...)
{
return value_type();
return false;
}
}
......@@ -362,9 +366,10 @@ class RADIX_PUBLIC JSONParserImpl
// 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_type parse_literal()
bool parse_literal(value_type& value)
{
size_t len = 0;
value = value_type();
for (; m_po + len < m_text.size(); len++)
{
char ch = m_text[m_po + len];
......@@ -378,119 +383,91 @@ class RADIX_PUBLIC JSONParserImpl
m_col += len;
if (std::string("true") == literals[i])
{
return value_type(true);
value = value_type(true);
return true;
}
else if (std::string("false") == literals[i])
{
return value_type(false);
value = value_type(false);
return true;
}
// default to null
return value_type();
// "null" default is already handled
return true;
}
}
set_error("invalid literal");
return value_type();
return false;
}
//-------------------------------------------------------------------------
value_type parse_object()
bool parse_object(value_type& value)
{
if (m_po >= m_text.size()) return value_type();
if (m_text[m_po] != '{') return value_type();
value = value_type();
if (m_po >= m_text.size()) return false;
if (m_text[m_po] != '{') return false;
m_po++;
m_col++;
size_t line_prev = m_line;
size_t col_prev = m_col;
bool trailing_comma = false;
value_type parent = object_type();
for (; m_po < m_text.size(); m_po++, m_col++)
{
skip_whitespace();
if (m_po >= m_text.size() || m_text[m_po] == '}')
{
if (trailing_comma)
{
set_error("trailing comma in object");
m_line = line_prev;
m_col = col_prev;
return value_type();
}
break;
}
if (m_po >= m_text.size() || m_text[m_po] == '}') break;
// parse key
std::string key = parse_string_contents();
if (m_last_error != "")
{
if (trailing_comma)
{
set_error("trailing comma on invalid key in object");
m_line = line_prev;
m_col = col_prev;
}
return value_type();
}
if (m_last_error != "") return false;
skip_whitespace();
// parse ':'
if (m_po >= m_text.size() || m_text[m_po] != ':')
{
set_error("no ':' following key in object");
return value_type();
return false;
}
m_po++;
m_col++;
// parse value
value_type child = parse_value();
if (child.is_null())
bool result = false;
value_type child = value_type();
result = parse_value(child);
if (!result)
{
if (m_last_error == "")
if (m_last_error != "")
{
set_error("missing value in object");
return false;
}
if (trailing_comma)
{
set_error("trailing comma in object");
m_line = line_prev;
m_col = col_prev;
}
return value_type();
}
else
{
trailing_comma = false;
parent.as_object()[key] = child;
}
skip_whitespace();
char ch = m_text[m_po];
if (ch == ',')
{
trailing_comma = true;
line_prev = m_line;
col_prev = m_col;
continue;
}
else if (ch == '}')
break;
else
{
set_error("invalid character in object");
return value_type();
return false;
}
}
if (m_po >= m_text.size() || m_text[m_po] != '}')
{
set_error("no closing curly bracket '}' for object");
return value_type();
return false;
}
m_po++;
m_col++;
return parent;
value = parent;
return true;
}
//-------------------------------------------------------------------------
......@@ -562,8 +539,8 @@ class RADIX_PUBLIC JSONParserImpl
for (size_t len = 0; m_po < m_text.size();)
{
char ch = m_text[m_po];
// disallow control characters <= 0x1f and extended ascii >= 0x80
if (ch <= 0x1f)
// disallow control characters <= 0x1f
if (static_cast<unsigned>(ch) <= 0x1f)
{
set_error("invalid character in string");
return "";
......@@ -589,33 +566,36 @@ class RADIX_PUBLIC JSONParserImpl
}
//-------------------------------------------------------------------------
value_type parse_string()
bool parse_string(value_type& value)
{
value = value_type();
std::string str = parse_string_contents();
if (!m_last_error.empty()) return value_type();
return value_type(str);
if (!m_last_error.empty()) return false;
value = value_type(str);
return true;
}
//-------------------------------------------------------------------------
value_type parse_value()
bool parse_value(value_type& value)
{
skip_whitespace();
char ch = m_text[m_po];
value_type node;
char ch = m_text[m_po];
value = value_type();
bool result = false;
if (ch == '"')
node = parse_string();
result = parse_string(value);
else if (ch == '[')
node = parse_array();
result = parse_array(value);
else if (ch == '{')
node = parse_object();
result = parse_object(value);
else if (ch == '-' || (ch >= '0' && ch <= '9'))
node = parse_number();
result = parse_number(value);
else if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
node = parse_literal();
result = parse_literal(value);
// TODO: need to handle ']', '}' and ',' differently from actual
// invalid characters
//~ else {}
return node;
return result;
}
static const size_t N_LITERALS = 3;
......
......@@ -253,7 +253,12 @@ bool parse_and_test(const std::string& file)
{
return_success = !success;
}
if (!return_success)
else
{
return_success = success;
}
// only show error if parse failed and it was supposed to succeed
if (!success && !return_success)
{
// dump the json error
std::cout << parser.last_error() << std::endl;
......@@ -271,12 +276,12 @@ TEST(JSONParser, JSONCheckerFailures)
// EXPECT_TRUE(parse_and_test("fail01_EXCLUDE.json"));
EXPECT_TRUE(parse_and_test("fail02.json"));
EXPECT_TRUE(parse_and_test("fail03.json"));
EXPECT_TRUE(parse_and_test("fail04.json"));
EXPECT_FALSE(parse_and_test("fail04.json"));
EXPECT_TRUE(parse_and_test("fail05.json"));
EXPECT_TRUE(parse_and_test("fail06.json"));
EXPECT_TRUE(parse_and_test("fail07.json"));
EXPECT_TRUE(parse_and_test("fail08.json"));
EXPECT_TRUE(parse_and_test("fail09.json"));
EXPECT_FALSE(parse_and_test("fail09.json"));
EXPECT_TRUE(parse_and_test("fail10.json"));
EXPECT_TRUE(parse_and_test("fail11.json"));
EXPECT_TRUE(parse_and_test("fail12.json"));
......
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