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 ...@@ -48,8 +48,9 @@ class RADIX_PUBLIC JSONParserImpl
m_col = 1; m_col = 1;
m_last_error = ""; m_last_error = "";
m_root = parse_value(); m_root = value_type();
if (m_root.is_null()) return false; bool result = parse_value(m_root);
if (!result) return false;
skip_whitespace(); skip_whitespace();
if (m_po != m_text.size()) if (m_po != m_text.size())
{ {
...@@ -148,42 +149,45 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -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; value = value_type();
if (m_text[m_po] != '[') return nullptr; if (m_po >= m_text.size()) return false;
if (m_text[m_po] != '[') return false;
m_po++; m_po++;
m_col++; m_col++;
// save line/column state
size_t line_prev = m_line;
size_t col_prev = m_col;
value_type parent = array_type(); value_type parent = array_type();
bool found_child = false;
for (; m_po < m_text.size(); m_po++, m_col++) for (; m_po < m_text.size(); m_po++, m_col++)
{ {
skip_whitespace(); skip_whitespace();
value_type child = parse_value(); if (m_text[m_po] == ']') break;
if (child.is_null()) value_type child;
if (!parse_value(child))
{ {
if (m_last_error != "") found_child = false;
{ if (m_po >= m_text.size())
}
else if (m_po >= m_text.size())
{ {
set_error("no closing bracket ']' for array"); set_error("no closing bracket ']' for array");
} }
else if (m_text[m_po] == ']') else if (m_text[m_po] == ']')
break; break;
return value_type();
} }
else else
{ {
found_child = true;
parent.as_array().push_back(child); parent.as_array().push_back(child);
} }
skip_whitespace(); skip_whitespace();
char ch = m_text[m_po]; char ch = m_text[m_po];
if (ch == ',') if (ch == ',')
{ {
if (!found_child)
{
set_error("missing value in array");
return false;
}
continue; continue;
} }
else if (ch == ']') else if (ch == ']')
...@@ -194,18 +198,19 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -194,18 +198,19 @@ class RADIX_PUBLIC JSONParserImpl
err_msg += ch; err_msg += ch;
err_msg += "' in array"; err_msg += "' in array";
set_error(err_msg); 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"); set_error("no closing bracket ']' for array");
return value_type(); return false;
} }
m_po++; m_po++;
m_col++; m_col++;
return parent; value = parent;
return true;
} }
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
...@@ -214,9 +219,10 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -214,9 +219,10 @@ class RADIX_PUBLIC JSONParserImpl
// (0|([1-9][0-9]*)) // (0|([1-9][0-9]*))
// (\.[0-9]+)? // (\.[0-9]+)?
// ([Ee][+-]?[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; size_t len = 0;
...@@ -229,7 +235,7 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -229,7 +235,7 @@ class RADIX_PUBLIC JSONParserImpl
if (m_po >= m_text.size()) if (m_po >= m_text.size())
{ {
set_error("invalid number (no digits after -)"); set_error("invalid number (no digits after -)");
return value_type(); return false;
} }
} }
...@@ -238,7 +244,7 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -238,7 +244,7 @@ class RADIX_PUBLIC JSONParserImpl
if (m_po >= m_text.size() || !(ch >= '0' && m_text[m_po] <= '9')) if (m_po >= m_text.size() || !(ch >= '0' && m_text[m_po] <= '9'))
{ {
set_error("invalid number (no digits)"); set_error("invalid number (no digits)");
return value_type(); return false;
} }
m_po++; m_po++;
m_col++; m_col++;
...@@ -257,13 +263,12 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -257,13 +263,12 @@ class RADIX_PUBLIC JSONParserImpl
{ {
try try
{ {
value_type node = value = value_type(std::stod(std::string(&m_text[m_po - len], len)));
value_type(std::stod(std::string(&m_text[m_po - len], len))); return true;
return node;
} }
catch (...) catch (...)
{ {
return value_type(); return false;
} }
} }
ch = m_text[m_po]; ch = m_text[m_po];
...@@ -277,7 +282,7 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -277,7 +282,7 @@ class RADIX_PUBLIC JSONParserImpl
if (m_po >= m_text.size()) if (m_po >= m_text.size())
{ {
set_error("invalid number (no digits after decimal)"); set_error("invalid number (no digits after decimal)");
return value_type(); return false;
} }
ch = m_text[m_po]; ch = m_text[m_po];
size_t n_digits = 0; size_t n_digits = 0;
...@@ -290,7 +295,7 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -290,7 +295,7 @@ class RADIX_PUBLIC JSONParserImpl
if (n_digits == 0) if (n_digits == 0)
{ {
set_error("invalid number (no digits after decimal)"); set_error("invalid number (no digits after decimal)");
return value_type(); return false;
} }
} }
...@@ -303,7 +308,7 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -303,7 +308,7 @@ class RADIX_PUBLIC JSONParserImpl
if (m_po >= m_text.size()) if (m_po >= m_text.size())
{ {
set_error("invalid number (no digits for exponent)"); set_error("invalid number (no digits for exponent)");
return value_type(); return false;
} }
ch = m_text[m_po]; ch = m_text[m_po];
// [+-]? // [+-]?
...@@ -316,7 +321,7 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -316,7 +321,7 @@ class RADIX_PUBLIC JSONParserImpl
if (m_po >= m_text.size()) if (m_po >= m_text.size())
{ {
set_error("invalid number (no digits for exponent)"); set_error("invalid number (no digits for exponent)");
return value_type(); return false;
} }
size_t n_digits = 0; size_t n_digits = 0;
...@@ -328,20 +333,19 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -328,20 +333,19 @@ class RADIX_PUBLIC JSONParserImpl
} }
if (n_digits == 0) if (n_digits == 0)
{ {
set_error("invalid number (no digits after decimal)"); set_error("invalid number (no digits for exponent)");
return value_type(); return false;
} }
} }
try try
{ {
value_type node = value = value_type(std::stod(std::string(&m_text[m_po - len], len)));
value_type(std::stod(std::string(&m_text[m_po - len], len))); return true;
return node;
} }
catch (...) catch (...)
{ {
return value_type(); return false;
} }
} }
...@@ -362,9 +366,10 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -362,9 +366,10 @@ class RADIX_PUBLIC JSONParserImpl
// only 3 valid literals all in lower case: false, null, true // only 3 valid literals all in lower case: false, null, true
// TODO: need to refactor code to support null return (currently it is // TODO: need to refactor code to support null return (currently it is
// treated as an error) // treated as an error)
value_type parse_literal() bool parse_literal(value_type& value)
{ {
size_t len = 0; size_t len = 0;
value = value_type();
for (; m_po + len < m_text.size(); len++) for (; m_po + len < m_text.size(); len++)
{ {
char ch = m_text[m_po + len]; char ch = m_text[m_po + len];
...@@ -378,119 +383,91 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -378,119 +383,91 @@ class RADIX_PUBLIC JSONParserImpl
m_col += len; m_col += len;
if (std::string("true") == literals[i]) if (std::string("true") == literals[i])
{ {
return value_type(true); value = value_type(true);
return true;
} }
else if (std::string("false") == literals[i]) else if (std::string("false") == literals[i])
{ {
return value_type(false); value = value_type(false);
return true;
} }
// default to null // "null" default is already handled
return value_type(); return true;
} }
} }
set_error("invalid literal"); 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(); value = value_type();
if (m_text[m_po] != '{') return value_type(); if (m_po >= m_text.size()) return false;
if (m_text[m_po] != '{') return false;
m_po++; m_po++;
m_col++; m_col++;
size_t line_prev = m_line;
size_t col_prev = m_col;
bool trailing_comma = false;
value_type parent = object_type(); value_type parent = object_type();
for (; m_po < m_text.size(); m_po++, m_col++) for (; m_po < m_text.size(); m_po++, m_col++)
{ {
skip_whitespace(); skip_whitespace();
if (m_po >= m_text.size() || m_text[m_po] == '}') if (m_po >= m_text.size() || m_text[m_po] == '}') break;
{
if (trailing_comma)
{
set_error("trailing comma in object");
m_line = line_prev;
m_col = col_prev;
return value_type();
}
break;
}
// parse key // parse key
std::string key = parse_string_contents(); std::string key = parse_string_contents();
if (m_last_error != "") if (m_last_error != "") return false;
{
if (trailing_comma)
{
set_error("trailing comma on invalid key in object");
m_line = line_prev;
m_col = col_prev;
}
return value_type();
}
skip_whitespace(); skip_whitespace();
// parse ':' // parse ':'
if (m_po >= m_text.size() || m_text[m_po] != ':') if (m_po >= m_text.size() || m_text[m_po] != ':')
{ {
set_error("no ':' following key in object"); set_error("no ':' following key in object");
return value_type(); return false;
} }
m_po++; m_po++;
m_col++; m_col++;
// parse value // parse value
value_type child = parse_value(); bool result = false;
if (child.is_null()) 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"); 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 else
{ {
trailing_comma = false;
parent.as_object()[key] = child; parent.as_object()[key] = child;
} }
skip_whitespace(); skip_whitespace();
char ch = m_text[m_po]; char ch = m_text[m_po];
if (ch == ',') if (ch == ',')
{
trailing_comma = true;
line_prev = m_line;
col_prev = m_col;
continue; continue;
}
else if (ch == '}') else if (ch == '}')
break; break;
else else
{ {
set_error("invalid character in object"); set_error("invalid character in object");
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] != '}')
{ {
set_error("no closing curly bracket '}' for object"); set_error("no closing curly bracket '}' for object");
return value_type(); return false;
} }
m_po++; m_po++;
m_col++; m_col++;
return parent; value = parent;
return true;
} }
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
...@@ -562,8 +539,8 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -562,8 +539,8 @@ class RADIX_PUBLIC JSONParserImpl
for (size_t len = 0; m_po < m_text.size();) for (size_t len = 0; m_po < m_text.size();)
{ {
char ch = m_text[m_po]; char ch = m_text[m_po];
// disallow control characters <= 0x1f and extended ascii >= 0x80 // disallow control characters <= 0x1f
if (ch <= 0x1f) if (static_cast<unsigned>(ch) <= 0x1f)
{ {
set_error("invalid character in string"); set_error("invalid character in string");
return ""; return "";
...@@ -589,33 +566,36 @@ class RADIX_PUBLIC JSONParserImpl ...@@ -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(); std::string str = parse_string_contents();
if (!m_last_error.empty()) return value_type(); if (!m_last_error.empty()) return false;
return value_type(str); value = value_type(str);
return true;
} }
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
value_type parse_value() bool parse_value(value_type& value)
{ {
skip_whitespace(); skip_whitespace();
char ch = m_text[m_po]; char ch = m_text[m_po];
value_type node; value = value_type();
bool result = false;
if (ch == '"') if (ch == '"')
node = parse_string(); result = parse_string(value);
else if (ch == '[') else if (ch == '[')
node = parse_array(); result = parse_array(value);
else if (ch == '{') else if (ch == '{')
node = parse_object(); result = parse_object(value);
else if (ch == '-' || (ch >= '0' && ch <= '9')) else if (ch == '-' || (ch >= '0' && ch <= '9'))
node = parse_number(); result = parse_number(value);
else if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) 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 // TODO: need to handle ']', '}' and ',' differently from actual
// invalid characters // invalid characters
//~ else {} //~ else {}
return node; return result;
} }
static const size_t N_LITERALS = 3; static const size_t N_LITERALS = 3;
......
...@@ -253,7 +253,12 @@ bool parse_and_test(const std::string& file) ...@@ -253,7 +253,12 @@ bool parse_and_test(const std::string& file)
{ {
return_success = !success; 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 // dump the json error
std::cout << parser.last_error() << std::endl; std::cout << parser.last_error() << std::endl;
...@@ -271,12 +276,12 @@ TEST(JSONParser, JSONCheckerFailures) ...@@ -271,12 +276,12 @@ TEST(JSONParser, JSONCheckerFailures)
// EXPECT_TRUE(parse_and_test("fail01_EXCLUDE.json")); // EXPECT_TRUE(parse_and_test("fail01_EXCLUDE.json"));
EXPECT_TRUE(parse_and_test("fail02.json")); EXPECT_TRUE(parse_and_test("fail02.json"));
EXPECT_TRUE(parse_and_test("fail03.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("fail05.json"));
EXPECT_TRUE(parse_and_test("fail06.json")); EXPECT_TRUE(parse_and_test("fail06.json"));
EXPECT_TRUE(parse_and_test("fail07.json")); EXPECT_TRUE(parse_and_test("fail07.json"));
EXPECT_TRUE(parse_and_test("fail08.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("fail10.json"));
EXPECT_TRUE(parse_and_test("fail11.json")); EXPECT_TRUE(parse_and_test("fail11.json"));
EXPECT_TRUE(parse_and_test("fail12.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