changeset 426:cee5c74c1c83

Ini: - Multiple fixes about using invalid iterators, - Add more tests about line/position in ini::Error.
author David Demelier <markand@malikania.fr>
date Wed, 14 Oct 2015 10:04:24 +0200
parents bb550fbd85e9
children aa9cc55338be
files C++/modules/Ini/Ini.cpp C++/tests/Ini/configs/empty.conf C++/tests/Ini/configs/error-badinclude.conf C++/tests/Ini/configs/error-nosection.conf C++/tests/Ini/configs/error-unterminatedsection.conf C++/tests/Ini/configs/simple.conf C++/tests/Ini/main.cpp CMakeLists.txt
diffstat 8 files changed, 128 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/C++/modules/Ini/Ini.cpp	Mon Oct 12 22:18:27 2015 +0200
+++ b/C++/modules/Ini/Ini.cpp	Wed Oct 14 10:04:24 2015 +0200
@@ -206,10 +206,17 @@
 				while (it != end && isReserved(*it)) {
 					// Single character tokens
 					switch (*it) {
+					case '#':
+						/* Skip comments */
+						while (it != end && *it != '\n') {
+							++ it;
+						}
+						tokens.push_back({ TokenType::Comment, lineno, position });
+						position = 0;
+						break;
 					case '\n':
 						++lineno;
 						position = 0;
-					case '#':
 					case '[':
 					case ']':
 					case '\'':
@@ -226,12 +233,14 @@
 			} else if (std::isspace(*it)) {
 				while (it != end && std::isspace(*it) && *it != '\n') {
 					value.push_back(*it++);
+					++position;
 				}
 
 				tokens.push_back({ TokenType::Space, lineno, position, std::move(value) });
 			} else {
 				while (it != end && !std::isspace(*it) && !isReserved(*it)) {
 					value.push_back(*it++);
+					++position;
 				}
 
 				tokens.push_back({ TokenType::Word, lineno, position, std::move(value) });
@@ -241,13 +250,8 @@
 		return tokens;
 	}
 
-	void readComment(TokenStack::iterator &it, TokenStack::iterator end)
+	void readComment(TokenStack::iterator &it, TokenStack::iterator)
 	{
-		while (it != end && it->type() != TokenType::NewLine) {
-			++ it;
-		}
-
-		// remove new line
 		++ it;
 	}
 
@@ -267,14 +271,26 @@
 
 	Section readSection(TokenStack::iterator &it, TokenStack::iterator end)
 	{
-		if (++it == end || it->type() != TokenType::Word) {
-			throw Error(it->line(), it->position(), "word expected after [, got " + it->toString());
+		// Empty [
+		if (++it == end) {
+			throw Error{it[-1].line(), it[-1].position(), "section declaration expected, got <EOF>"};
+		}
+
+		// Get the section name
+		if (it->type() != TokenType::Word) {
+			throw Error{it->line(), it->position(), "word expected after [, got " + it->toString()};
 		}
 
 		Section section(it->value());
 
-		if (++it == end || it->type() != TokenType::SectionEnd) {
-			throw Error(it->line(), it->position(), "] expected, got " + it->toString());
+		// [unterminated
+		if (++it == end) {
+			throw Error{it[-1].line(), it[-1].position(), "unterminated section"};
+		}
+
+		// Check if terminated
+		if (it->type() != TokenType::SectionEnd) {
+			throw Error{it->line(), it->position(), "] expected, got " + it->toString()};
 		}
 
 		// Remove ]
@@ -284,6 +300,7 @@
 			return section;
 		}
 
+		// Now read all that is allowed to be in a section
 		while (it != end && it->type() != TokenType::SectionBegin) {
 			if (it->type() == TokenType::Space) {
 				readSpace(it, end);
@@ -294,7 +311,7 @@
 			} else if (it->type() == TokenType::Word) {
 				section.push_back(readOption(it, end));
 			} else {
-				throw Error(it->line(), it->position(), "unexpected token " + it->toString());
+				throw Error{it->line(), it->position(), "unexpected token " + it->toString()};
 			}
 		}
 
@@ -303,16 +320,17 @@
 
 	Option readOption(TokenStack::iterator &it, TokenStack::iterator end)
 	{
-		std::string key = it->value();
-
-		if (++it == end) {
-			throw Error(it->line(), it->position(), "expected '=' after option declaration, got <EOF>");
-		}
+		std::string key = it++->value();
 
 		readSpace(it, end);
 
-		if (it == end || it->type() != TokenType::Assign) {
-			throw Error(it->line(), it->position(), "expected '=' after option declaration, got " + it++->toString());
+		if (it == end) {
+			throw Error{it[-1].line(), it[-1].position(), "expected '=' after option declaration, got <EOF>"};
+		}
+
+		if (it->type() != TokenType::Assign) {
+			++ it;
+			throw Error{it[-1].line(), it[-1].position(), "expected '=' after option declaration, got " + it[-1].toString()};
 		}
 
 		readSpace(++it, end);
@@ -326,39 +344,51 @@
 				oss << it++->value();
 			}
 
-			if (it == end)
-				throw Error(save->line(), save->position(), "undisclosed quote: " + save->toString() + " expected");
+			if (it == end) {
+				throw Error{save->line(), save->position(), "undisclosed quote: " + save->toString() + " expected"};
+			}
 
 			++ it;
 		} else if (it->type() == TokenType::Word) {
 			oss << it++->value();
 		} else if (it->type() != TokenType::NewLine && it->type() != TokenType::Comment) {
 			// No value requested, must be NewLine or comment
-			throw Error(it->line(), it->position(), "expected option value after '=', got " + it->toString());
+			throw Error{it->line(), it->position(), "expected option value after '=', got " + it->toString()};
 		}
 
-
-		return Option(std::move(key), oss.str());
+		return Option{std::move(key), oss.str()};
 	}
 
 	void readInclude(TokenStack::iterator &it, TokenStack::iterator end)
 	{
-		if (++it == end || (it->type() != TokenType::Word || it->value() != "include")) {
-			throw Error(it->line(), it->position(), "expected `include' after '@' token, got " + it->toString());
+		if (++it == end) {
+			throw Error{it[-1].line(), it[-1].position(), "expected `include` after '@' token, got <EOF>"};
+		}
+
+		if (it->type() != TokenType::Word && it->value() != "include") {
+			throw Error{it->line(), it->position(), "expected `include' after '@' token, got " + it->toString()};
 		}
 
 		readSpace(++it, end);
 
-		// Quotes mandatory
 		TokenStack::iterator save = it;
 
-		if (it == end || (it->type() != TokenType::QuoteSimple && it->type() != TokenType::QuoteDouble)) {
-			throw Error(it->line(), it->position(), "expected filename after @include statement");
+		if (it == end) {
+			throw Error{it[-1].line(), it[-1].position(), "expected filename after @include statement, got <EOF>"};
+		}
+
+		// First quote
+		if (it->type() != TokenType::QuoteSimple && it->type() != TokenType::QuoteDouble) {
+			throw Error{it->line(), it->position(), "expected filename after @include statement"};
 		}
 
 		// Filename
-		if (++it == end || it->type() != TokenType::Word) {
-			throw Error(it->line(), it->position(), "expected filename after @include statement");
+		if (++it == end) {
+			throw Error{it[-1].line(), it[-1].position(), "expected filename after @include statement, got <EOF>"};
+		}
+	
+		if (it->type() != TokenType::Word) {
+			throw Error{it->line(), it->position(), "expected filename after @include statement"};
 		}
 
 		std::string value = it->value();
@@ -371,8 +401,11 @@
 		}
 
 		// Must be closed with the same quote
-		if (++it == end || it->type() != save->type()) {
-			throw Error(save->line(), save->position(), "undiclosed quote: " + save->toString() + " expected");
+		if (++it == end) {
+			throw Error{save->line(), save->position(), "undiclosed quote: " + save->toString() + " expected, got <EOF>"};
+		}
+		if (it->type() != save->type()) {
+			throw Error{save->line(), save->position(), "undiclosed quote: " + save->toString() + " expected"};
 		}
 
 		// Remove quote
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/tests/Ini/configs/empty.conf	Wed Oct 14 10:04:24 2015 +0200
@@ -0,0 +1,1 @@
+# this file is completely empty
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/tests/Ini/configs/error-badinclude.conf	Wed Oct 14 10:04:24 2015 +0200
@@ -0,0 +1,1 @@
+@include noquotes
\ No newline at end of file
--- a/C++/tests/Ini/configs/error-nosection.conf	Mon Oct 12 22:18:27 2015 +0200
+++ b/C++/tests/Ini/configs/error-nosection.conf	Wed Oct 14 10:04:24 2015 +0200
@@ -1,1 +1,3 @@
+# this file has no section
+# and it's not valid
 option = value
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/tests/Ini/configs/error-unterminatedsection.conf	Wed Oct 14 10:04:24 2015 +0200
@@ -0,0 +1,2 @@
+# This file has unterminated section
+[forgot
\ No newline at end of file
--- a/C++/tests/Ini/configs/simple.conf	Mon Oct 12 22:18:27 2015 +0200
+++ b/C++/tests/Ini/configs/simple.conf	Wed Oct 14 10:04:24 2015 +0200
@@ -2,3 +2,5 @@
 option1=1
 option2 =2
 option3 = 3
+
+# This file ends with a comment.
\ No newline at end of file
--- a/C++/tests/Ini/main.cpp	Mon Oct 12 22:18:27 2015 +0200
+++ b/C++/tests/Ini/main.cpp	Wed Oct 14 10:04:24 2015 +0200
@@ -195,6 +195,19 @@
 }
 
 /* --------------------------------------------------------
+ * Empty
+ * -------------------------------------------------------- */
+
+TEST(Empty, test)
+{
+	try {
+		ini::Document doc{"Ini/empty.conf"};
+	} catch (const ini::Error &error) {
+		FAIL() << error.line() << ":" << error.position() << ": " << error.what();
+	}
+}
+
+/* --------------------------------------------------------
  * Errors
  * -------------------------------------------------------- */
 
@@ -205,7 +218,9 @@
 		ini::Document doc("Ini/error-nosection.conf");
 
 		FAIL() << "Failure expected, got success";
-	} catch (const std::exception &ex) {
+	} catch (const ini::Error &ex) {
+		ASSERT_EQ(3, ex.line());
+		ASSERT_EQ(7, ex.position());
 	}
 }
 
@@ -216,7 +231,9 @@
 		ini::Document doc("Ini/error-lineassigment.conf");
 
 		FAIL() << "Failure expected, got success";
-	} catch (const std::exception &ex) {
+	} catch (const ini::Error &ex) {
+		ASSERT_EQ(3, ex.line());
+		ASSERT_EQ(0, ex.position());
 	}
 }
 
@@ -227,7 +244,9 @@
 		ini::Document doc("Ini/error-badcomment.conf");
 
 		FAIL() << "Failure expected, got success";
-	} catch (const std::exception &ex) {
+	} catch (const ini::Error &ex) {
+		ASSERT_EQ(2, ex.line());
+		ASSERT_EQ(9, ex.position());
 	}
 }
 
@@ -238,7 +257,35 @@
 		ini::Document doc("Ini/error-badsection.conf");
 
 		FAIL() << "Failure expected, got success";
-	} catch (const std::exception &ex) {
+	} catch (const ini::Error &ex) {
+		ASSERT_EQ(1, ex.line());
+		ASSERT_EQ(1, ex.position());
+	}
+}
+
+TEST(Errors, unterminatedsection)
+{
+	// Section unfinished
+	try {
+		ini::Document doc("Ini/error-unterminatedsection.conf");
+
+		FAIL() << "Failure expected, got success";
+	} catch (const ini::Error &ex) {
+		ASSERT_EQ(2, ex.line());
+		ASSERT_EQ(8, ex.position());
+	}
+}
+
+TEST(Errors, badinclude)
+{
+	// Section unfinished
+	try {
+		ini::Document doc("Ini/error-badinclude.conf");
+
+		FAIL() << "Failure expected, got success";
+	} catch (const ini::Error &ex) {
+		ASSERT_EQ(1, ex.line());
+		ASSERT_EQ(17, ex.position());
 	}
 }
 
--- a/CMakeLists.txt	Mon Oct 12 22:18:27 2015 +0200
+++ b/CMakeLists.txt	Wed Oct 14 10:04:24 2015 +0200
@@ -232,10 +232,13 @@
 		${INI_LIBRARIES}
 	RESOURCES
 		${code_SOURCE_DIR}/C++/tests/Ini/configs/compact.conf
+		${code_SOURCE_DIR}/C++/tests/Ini/configs/empty.conf
 		${code_SOURCE_DIR}/C++/tests/Ini/configs/error-badcomment.conf
+		${code_SOURCE_DIR}/C++/tests/Ini/configs/error-badinclude.conf
 		${code_SOURCE_DIR}/C++/tests/Ini/configs/error-badsection.conf
 		${code_SOURCE_DIR}/C++/tests/Ini/configs/error-lineassigment.conf
 		${code_SOURCE_DIR}/C++/tests/Ini/configs/error-nosection.conf
+		${code_SOURCE_DIR}/C++/tests/Ini/configs/error-unterminatedsection.conf
 		${code_SOURCE_DIR}/C++/tests/Ini/configs/includes.conf
 		${code_SOURCE_DIR}/C++/tests/Ini/configs/multi.conf
 		${code_SOURCE_DIR}/C++/tests/Ini/configs/novalue.conf