Compare commits

...

13 Commits

Author SHA1 Message Date
Toru Niina
cfdd4d4a90 Merge pull request #14 from ToruNiina/error-message
improve error message quality
2018-12-22 18:46:00 +09:00
ToruNiina
5546b3389d Merge branch 'master' into error-message 2018-12-22 17:55:59 +09:00
ToruNiina
9c95992dad fix error message for empty value 2018-12-22 17:44:09 +09:00
ToruNiina
edb48b2872 add test_error_detection to check it detects error 2018-12-22 17:43:42 +09:00
ToruNiina
c63ac7e435 detect syntax_error; appending array-of-tables
toml file like the following is explicitly prohibited.
a = [{b = 1}]
[[a]]
b = 2
this commit detects this kind of syntax-error while parsing toml file
2018-12-22 17:07:06 +09:00
ToruNiina
fec49aaaa3 fix error message: add missing spaces 2018-12-22 17:06:36 +09:00
ToruNiina
617187969c fix result::unwrap_or with rvalue ref; merge branch 'hotfix' 2018-12-17 23:54:17 +09:00
ToruNiina
e3217cd572 quit returning rvalue ref from unwrap_or 2018-12-17 23:17:45 +09:00
ToruNiina
4d02f399a2 add temporary to receive rvalue 2018-12-17 23:03:53 +09:00
ToruNiina
24723226f1 remove template argument from result::unwrap_or 2018-12-17 19:18:16 +09:00
ToruNiina
7b3684b54e add and_other and or_other to toml::result
effectively same as Rust's std::Result::and and or.
2018-12-17 18:24:41 +09:00
ToruNiina
13c1f9c259 output filename of the second value2 if different
in format_error.
2018-12-17 18:07:57 +09:00
ToruNiina
6df75ad28e fix README 2018-12-17 16:56:09 +09:00
8 changed files with 326 additions and 26 deletions

View File

@@ -446,7 +446,7 @@ if(max < min)
you will get an error message like this. you will get an error message like this.
```console ```console
[error] value should be positive [error] max should be larger than min
--> example.toml --> example.toml
3 | min = 54 3 | min = 54
| ~~ minimum number here | ~~ minimum number here

View File

@@ -26,7 +26,8 @@ set(TEST_NAMES
test_from_toml test_from_toml
test_parse_file test_parse_file
test_parse_unicode test_parse_unicode
) test_error_detection
)
CHECK_CXX_COMPILER_FLAG("-Wall" COMPILER_SUPPORTS_WALL) CHECK_CXX_COMPILER_FLAG("-Wall" COMPILER_SUPPORTS_WALL)
CHECK_CXX_COMPILER_FLAG("-Wpedantic" COMPILER_SUPPORTS_WPEDANTIC) CHECK_CXX_COMPILER_FLAG("-Wpedantic" COMPILER_SUPPORTS_WPEDANTIC)

View File

@@ -0,0 +1,199 @@
#define BOOST_TEST_MODULE "test_error_detection"
#ifdef UNITTEST_FRAMEWORK_LIBRARY_EXIST
#include <boost/test/unit_test.hpp>
#else
#define BOOST_TEST_NO_LIB
#include <boost/test/included/unit_test.hpp>
#endif
#include <toml.hpp>
#include <iostream>
#include <fstream>
BOOST_AUTO_TEST_CASE(test_detect_empty_key)
{
std::istringstream stream(std::string("= \"value\""));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_empty_key");
}
catch(const toml::syntax_error& syn)
{
// to see the error message
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_missing_value)
{
std::istringstream stream(std::string("a ="));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_missing_value");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_too_many_value)
{
std::istringstream stream(std::string("a = 1 = \"value\""));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_too_many_value");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_duplicate_table)
{
std::istringstream stream(std::string(
"[table]\n"
"a = 42\n"
"[table]\n"
"b = 42\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_duplicate_table");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_conflict_array_table)
{
std::istringstream stream(std::string(
"[[table]]\n"
"a = 42\n"
"[table]\n"
"b = 42\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_conflict_array_table");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_conflict_table_array)
{
std::istringstream stream(std::string(
"[table]\n"
"a = 42\n"
"[[table]]\n"
"b = 42\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_conflict_table_array");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_duplicate_value)
{
std::istringstream stream(std::string(
"a = 1\n"
"a = 2\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_duplicate_value");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_conflicting_value)
{
std::istringstream stream(std::string(
"a.b = 1\n"
"a.b.c = 2\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_conflicting_value");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_inhomogeneous_array)
{
std::istringstream stream(std::string(
"a = [1, 1.0]\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_inhomogeneous_array");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}
BOOST_AUTO_TEST_CASE(test_detect_appending_array_of_table)
{
std::istringstream stream(std::string(
"a = [{b = 1}]\n"
"[[a]]\n"
"b = 2\n"
));
bool exception_thrown = false;
try
{
toml::parse(stream, "test_detect_appending_array_of_table");
}
catch(const toml::syntax_error& syn)
{
std::cerr << syn.what() << std::endl;
exception_thrown = true;
}
BOOST_CHECK(exception_thrown);
}

View File

@@ -68,9 +68,14 @@ BOOST_AUTO_TEST_CASE(test_expect)
{ {
toml::value v1(42); toml::value v1(42);
toml::value v2(3.14); toml::value v2(3.14);
BOOST_CHECK_EQUAL(42, toml::expect<int>(v1).unwrap_or(0)); const auto v1_or_0 = toml::expect<int>(v1).unwrap_or(0);
BOOST_CHECK_EQUAL( 0, toml::expect<int>(v2).unwrap_or(0)); const auto v2_or_0 = toml::expect<int>(v2).unwrap_or(0);
BOOST_CHECK_EQUAL("42", toml::expect<int>(v1).map([](int i){return std::to_string(i);}).unwrap_or(std::string("none"))); BOOST_CHECK_EQUAL(42, v1_or_0);
BOOST_CHECK_EQUAL("none", toml::expect<int>(v2).map([](int i){return std::to_string(i);}).unwrap_or(std::string("none"))); BOOST_CHECK_EQUAL( 0, v2_or_0);
const auto v1_or_none = toml::expect<int>(v1).map([](int i){return std::to_string(i);}).unwrap_or(std::string("none"));
const auto v2_or_none = toml::expect<int>(v2).map([](int i){return std::to_string(i);}).unwrap_or(std::string("none"));
BOOST_CHECK_EQUAL("42", v1_or_none);
BOOST_CHECK_EQUAL("none", v2_or_none);
} }
} }

View File

@@ -409,3 +409,33 @@ BOOST_AUTO_TEST_CASE(test_or_else)
BOOST_CHECK_EQUAL(mapped.unwrap_err(), "hogehoge"); BOOST_CHECK_EQUAL(mapped.unwrap_err(), "hogehoge");
} }
} }
BOOST_AUTO_TEST_CASE(test_and_or_other)
{
{
const toml::result<int, std::string> r1(toml::ok(42));
const toml::result<int, std::string> r2(toml::err<std::string>("foo"));
BOOST_CHECK_EQUAL(r1, r1.or_other(r2));
BOOST_CHECK_EQUAL(r2, r1.and_other(r2));
BOOST_CHECK_EQUAL(42, r1.or_other(r2).unwrap());
BOOST_CHECK_EQUAL("foo", r1.and_other(r2).unwrap_err());
}
{
auto r1_gen = []() -> toml::result<int, std::string> {
return toml::ok(42);
};
auto r2_gen = []() -> toml::result<int, std::string> {
return toml::err<std::string>("foo");
};
const auto r3 = r1_gen();
const auto r4 = r2_gen();
BOOST_CHECK_EQUAL(r3, r1_gen().or_other (r2_gen()));
BOOST_CHECK_EQUAL(r4, r1_gen().and_other(r2_gen()));
BOOST_CHECK_EQUAL(42, r1_gen().or_other (r2_gen()).unwrap());
BOOST_CHECK_EQUAL("foo", r1_gen().and_other(r2_gen()).unwrap_err());
}
}

View File

@@ -971,7 +971,8 @@ parse_key_value_pair(location<Container>& loc)
} }
else else
{ {
msg = val.unwrap_err(); msg = format_underline("[error] toml::parse_key_value_pair: "
"invalid value format", loc, val.unwrap_err());
} }
loc.iter() = first; loc.iter() = first;
return err(msg); return err(msg);
@@ -1035,9 +1036,9 @@ insert_nested_key(table& root, const toml::value& v,
"[error] toml::insert_value: array of table (\"", "[error] toml::insert_value: array of table (\"",
format_dotted_keys(first, last), "\") collides with" format_dotted_keys(first, last), "\") collides with"
" existing value"), get_region(tab->at(k)), " existing value"), get_region(tab->at(k)),
concat_to_string("this ", tab->at(k).type(), "value" concat_to_string("this ", tab->at(k).type(),
"already exists"), get_region(v), "while inserting" " value already exists"), get_region(v),
"this array-of-tables")); "while inserting this array-of-tables"));
} }
array& a = tab->at(k).template cast<toml::value_t::Array>(); array& a = tab->at(k).template cast<toml::value_t::Array>();
if(!(a.front().is(value_t::Table))) if(!(a.front().is(value_t::Table)))
@@ -1046,9 +1047,34 @@ insert_nested_key(table& root, const toml::value& v,
"[error] toml::insert_value: array of table (\"", "[error] toml::insert_value: array of table (\"",
format_dotted_keys(first, last), "\") collides with" format_dotted_keys(first, last), "\") collides with"
" existing value"), get_region(tab->at(k)), " existing value"), get_region(tab->at(k)),
concat_to_string("this ", tab->at(k).type(), "value" concat_to_string("this ", tab->at(k).type(),
"already exists"), get_region(v), "while inserting" " value already exists"), get_region(v),
"this array-of-tables")); "while inserting this array-of-tables"));
}
// avoid conflicting array of table like the following.
// ```toml
// a = [{b = 42}] # define a as an array of *inline* tables
// [[a]] # a is an array of *multi-line* tables
// b = 54
// ```
// Here, from the type information, these cannot be detected
// bacause inline table is also a table.
// But toml v0.5.0 explicitly says it is invalid. The above
// array-of-tables has a static size and appending to the
// array is invalid.
// In this library, multi-line table value has a region
// that points to the key of the table (e.g. [[a]]). By
// comparing the first two letters in key, we can detect
// the array-of-table is inline or multiline.
if(detail::get_region(a.front()).str().substr(0,2) != "[[")
{
throw syntax_error(format_underline(concat_to_string(
"[error] toml::insert_value: array of table (\"",
format_dotted_keys(first, last), "\") collides with"
" existing array-of-tables"), get_region(tab->at(k)),
concat_to_string("this ", tab->at(k).type(),
" value has static size"), get_region(v),
"appending this to the statically sized array"));
} }
a.push_back(v); a.push_back(v);
return ok(true); return ok(true);

View File

@@ -262,10 +262,8 @@ inline std::string format_underline(const std::string& message,
std::max(line_number1.size(), line_number2.size()); std::max(line_number1.size(), line_number2.size());
std::ostringstream retval; std::ostringstream retval;
retval << message; retval << message << newline;
retval << newline; retval << " --> " << reg1.name() << newline;
retval << " --> ";
retval << reg1.name() << newline;;
// --------------------------------------- // ---------------------------------------
retval << ' ' << std::setw(line_num_width) << line_number1; retval << ' ' << std::setw(line_num_width) << line_number1;
retval << " | " << line1 << newline; retval << " | " << line1 << newline;
@@ -276,7 +274,14 @@ inline std::string format_underline(const std::string& message,
retval << ' '; retval << ' ';
retval << comment_for_underline1 << newline; retval << comment_for_underline1 << newline;
// --------------------------------------- // ---------------------------------------
retval << " ..." << newline; if(reg2.name() != reg1.name())
{
retval << " --> " << reg2.name() << newline;
}
else
{
retval << " ..." << newline;
}
retval << ' ' << std::setw(line_num_width) << line_number2; retval << ' ' << std::setw(line_num_width) << line_number2;
retval << " | " << line2 << newline; retval << " | " << line2 << newline;
retval << make_string(line_num_width + 1, ' '); retval << make_string(line_num_width + 1, ' ');

View File

@@ -396,23 +396,20 @@ struct result
return std::move(this->succ.value); return std::move(this->succ.value);
} }
template<typename U> value_type& unwrap_or(value_type& opt) &
value_type& unwrap_or(U& opt) &
{ {
if(is_err()) {return opt;} if(is_err()) {return opt;}
return this->succ.value; return this->succ.value;
} }
template<typename U> value_type const& unwrap_or(value_type const& opt) const&
value_type const& unwrap_or(U const& opt) const&
{ {
if(is_err()) {return opt;} if(is_err()) {return opt;}
return this->succ.value; return this->succ.value;
} }
template<typename U> value_type unwrap_or(value_type opt) &&
value_type&& unwrap_or(U&& opt) &&
{ {
if(is_err()) {return std::move(opt);} if(is_err()) {return opt;}
return std::move(this->succ.value); return this->succ.value;
} }
error_type& unwrap_err() & error_type& unwrap_err() &
@@ -592,6 +589,26 @@ struct result
return ok(std::move(this->as_ok())); return ok(std::move(this->as_ok()));
} }
// if *this is error, returns *this. otherwise, returns other.
result and_other(const result& other) const&
{
return this->is_err() ? *this : other;
}
result and_other(result&& other) &&
{
return this->is_err() ? std::move(*this) : std::move(other);
}
// if *this is okay, returns *this. otherwise, returns other.
result or_other(const result& other) const&
{
return this->is_ok() ? *this : other;
}
result or_other(result&& other) &&
{
return this->is_ok() ? std::move(*this) : std::move(other);
}
void swap(result<T, E>& other) void swap(result<T, E>& other)
{ {
result<T, E> tmp(std::move(*this)); result<T, E> tmp(std::move(*this));
@@ -638,5 +655,22 @@ void swap(result<T, E>& lhs, result<T, E>& rhs)
return; return;
} }
// this might be confusing because it eagerly evaluated, while in the other
// cases operator && and || are short-circuited.
//
// template<typename T, typename E>
// inline result<T, E>
// operator&&(const result<T, E>& lhs, const result<T, E>& rhs) noexcept
// {
// return lhs.is_ok() ? rhs : lhs;
// }
//
// template<typename T, typename E>
// inline result<T, E>
// operator||(const result<T, E>& lhs, const result<T, E>& rhs) noexcept
// {
// return lhs.is_ok() ? lhs : rhs;
// }
} // toml11 } // toml11
#endif// TOML11_RESULT_H #endif// TOML11_RESULT_H