changeset 430:625f5d64b093

Ini: - Add support of lists ( "a", "b" ), - Add more noexcept, - Add some assert.
author David Demelier <markand@malikania.fr>
date Wed, 14 Oct 2015 20:19:28 +0200
parents 31bddece9860
children 7f01d500460a 88df9c580c36
files C++/modules/Ini/Ini.cpp C++/modules/Ini/Ini.h C++/tests/Ini/configs/lists.conf C++/tests/Ini/main.cpp CMakeLists.txt
diffstat 5 files changed, 175 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
--- a/C++/modules/Ini/Ini.cpp	Wed Oct 14 19:48:42 2015 +0200
+++ b/C++/modules/Ini/Ini.cpp	Wed Oct 14 20:19:28 2015 +0200
@@ -16,6 +16,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <cassert>
 #include <cctype>
 #include <iostream>
 #include <iterator>
@@ -33,7 +34,7 @@
 
 using namespace ini;
 
-using Iterator = std::istreambuf_iterator<char>;
+using StreamIterator = std::istreambuf_iterator<char>;
 using TokenIterator = std::vector<Token>::const_iterator;
 
 inline bool isAbsolute(const std::string &path) noexcept
@@ -56,36 +57,68 @@
 	return c == ' ' || c == '\t';
 }
 
+inline bool isList(char c) noexcept
+{
+	return c == '(' || c == ')' || c == ',';
+}
+
 inline bool isReserved(char c) noexcept
 {
-	return c == '[' || c == ']' || c == '@' || c == '#' || c == '=' || c == '\'' || c == '"';
+	return isList(c) || isQuote(c) || c == '[' || c == ']' || c == '@' || c == '#' || c == '=';
 }
 
-void analyzeLine(int &line, int &column, Iterator &it)
+void analyzeLine(int &line, int &column, StreamIterator &it) noexcept
 {
+	assert(*it == '\n');
+
 	++ line;
 	++ it;
 	column = 0;
 }
 
-void analyzeComment(int &column, Iterator &it, Iterator end)
+void analyzeComment(int &column, StreamIterator &it, StreamIterator end) noexcept
 {
+	assert(*it == '#');
+
 	while (it != end && *it != '\n') {
 		++ column;
 		++ it;
 	}
 }
 
-void analyzeSpaces(int &column, Iterator &it, Iterator end)
+void analyzeSpaces(int &column, StreamIterator &it, StreamIterator end) noexcept
 {
+	assert(isSpace(*it));
+
 	while (it != end && isSpace(*it)) {
 		++ column;
 		++ it;
 	}
 }
 
-void analyzeSection(Tokens &list, int &line, int &column, Iterator &it, Iterator end)
+void analyzeList(Tokens &list, int line, int &column, StreamIterator &it) noexcept
 {
+	assert(isList(*it));
+
+	switch (*it++) {
+	case '(':
+		list.emplace_back(Token::ListBegin, line, column++);
+		break;
+	case ')':
+		list.emplace_back(Token::ListEnd, line, column++);
+		break;
+	case ',':
+		list.emplace_back(Token::Comma, line, column++);
+		break;
+	default:
+		break;
+	}
+}
+
+void analyzeSection(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end)
+{
+	assert(*it == '[');
+
 	std::string value;
 	int save = column;
 
@@ -109,16 +142,18 @@
 	/* Remove ']' */
 	++ it;
 
-	list.push_back({ Token::Section, line, save, std::move(value) });
+	list.emplace_back(Token::Section, line, save, std::move(value));
 }
 
-void analyzeAssign(Tokens &list, int &line, int &column, Iterator &it)
+void analyzeAssign(Tokens &list, int &line, int &column, StreamIterator &it)
 {
+	assert(*it == '=');
+
 	list.push_back({ Token::Assign, line, column++ });
 	++ it;
 }
 
-void analyzeQuotedWord(Tokens &list, int &line, int &column, Iterator &it, Iterator end)
+void analyzeQuotedWord(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end)
 {
 	std::string value;
 	int save = column;
@@ -140,8 +175,10 @@
 	list.push_back({ Token::QuotedWord, line, save, std::move(value) });
 }
 
-void analyzeWord(Tokens &list, int &line, int &column, Iterator &it, Iterator end)
+void analyzeWord(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end)
 {
+	assert(!isReserved(*it));
+
 	std::string value;
 	int save = column;
 
@@ -153,8 +190,10 @@
 	list.push_back({ Token::Word, line, save, std::move(value) });
 }
 
-void analyzeInclude(Tokens &list, int &line, int &column, Iterator &it, Iterator end)
+void analyzeInclude(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end)
 {
+	assert(*it == '@');
+
 	std::string include;
 	int save = column;
 
@@ -172,7 +211,7 @@
 	list.push_back({ Token::Include, line, save });
 }
 
-Tokens analyze(std::istreambuf_iterator<char> &it, std::istreambuf_iterator<char> end)
+Tokens analyze(StreamIterator &it, StreamIterator end)
 {
 	Tokens list;
 	int line = 1;
@@ -193,6 +232,8 @@
 			analyzeInclude(list, line, column, it, end);
 		} else if (isQuote(*it)) {
 			analyzeQuotedWord(list, line, column, it, end);
+		} else if (isList(*it)) {
+			analyzeList(list, line, column, it);
 		} else {
 			analyzeWord(list, line, column, it, end);
 		}
@@ -201,10 +242,50 @@
 	return list;
 }
 
+void parseOptionValueSimple(Option &option, TokenIterator &it)
+{
+	assert(it->type() == Token::Word || it->type() == Token::QuotedWord);
+
+	option.push_back((it++)->value());
+}
+
+void parseOptionValueList(Option &option, TokenIterator &it, TokenIterator end)
+{
+	assert(it->type() == Token::ListBegin);
+
+	TokenIterator save = it++;
+
+	while (it != end && it->type() != Token::ListEnd) {
+		switch (it->type()) {
+		case Token::Comma:
+			/* Previous must be a word */
+			if (it[-1].type() != Token::Word && it[-1].type() != Token::QuotedWord) {
+				throw Error{it->line(), it->column(), "unexpected comma after '" + it[-1].value() + "'"};
+			}
+
+			++ it;
+			break;
+		case Token::Word:
+		case Token::QuotedWord:
+			option.push_back((it++)->value());
+			break;
+		default:
+			throw Error{it->line(), it->column(), "unexpected '" + it[-1].value() + "' in list construct"};
+			break;
+		}
+	}
+
+	if (it == end) {
+		throw Error{save->line(), save->column(), "unterminated list construct"};
+	}
+
+	/* Remove ) */
+	++ it;
+}
+
 void parseOption(Section &sc, TokenIterator &it, TokenIterator end)
 {
-	std::string key = it->value();
-	std::string value;
+	Option option{it->value()};
 
 	TokenIterator save = it;
 
@@ -219,11 +300,13 @@
 	/* Empty options are allowed so just test for words */
 	if (++it != end) {
 		if (it->type() == Token::Word || it->type() == Token::QuotedWord) {
-			value = it++->value();
+			parseOptionValueSimple(option, it);
+		} else if (it->type() == Token::ListBegin) {
+			parseOptionValueList(option, it, end);
 		}
 	}
 
-	sc.emplace_back(std::move(key), std::move(value));
+	sc.push_back(std::move(option));
 }
 
 void parseInclude(Document &doc, TokenIterator &it, TokenIterator end)
@@ -340,14 +423,13 @@
 
 Document::Document(const Buffer &buffer)
 {
-	dump(analyze(buffer));
 	parse(*this, analyze(buffer));
 }
 
 void Document::dump(const Tokens &tokens)
 {
 	for (const Token &token: tokens) {
-		// TODO: type
+		// TODO: add better description
 		std::cout << token.line() << ":" << token.column() << ": " << token.value() << std::endl;
 	}
 }
--- a/C++/modules/Ini/Ini.h	Wed Oct 14 19:48:42 2015 +0200
+++ b/C++/modules/Ini/Ini.h	Wed Oct 14 20:19:28 2015 +0200
@@ -111,7 +111,10 @@
 		Section,	//!< [section]
 		Word,		//!< word without quotes
 		QuotedWord,	//!< word with quotes
-		Assign		//!< = assignment
+		Assign,		//!< = assignment
+		ListBegin,	//!< begin of list (
+		ListEnd,	//!< end of list )
+		Comma		//!< list separation
 	};
 
 private:
@@ -146,6 +149,15 @@
 		case Assign:
 			m_value = "=";
 			break;
+		case ListBegin:
+			m_value = "(";
+			break;
+		case ListEnd:
+			m_value = ")";
+			break;
+		case Comma:
+			m_value = ",";
+			break;
 		default:
 			break;
 		}
@@ -202,21 +214,44 @@
  * @class Option
  * @brief Option definition.
  */
-class Option {
+class Option : public std::vector<std::string> {
 private:
 	std::string m_key;
-	std::string m_value;
 
 public:
 	/**
-	 * Construct an option.
+	 * Construct an empty option.
+	 *
+	 * @param key the key
+	 * @param value the value
+	 */
+	inline Option(std::string key) noexcept
+		: std::vector<std::string>{}
+		, m_key{std::move(key)}
+	{
+	}
+
+	/**
+	 * Construct a single option.
 	 *
 	 * @param key the key
 	 * @param value the value
 	 */
 	inline Option(std::string key, std::string value) noexcept
 		: m_key{std::move(key)}
-		, m_value{std::move(value)}
+	{
+		push_back(std::move(value));
+	}
+
+	/**
+	 * Construct a list option.
+	 *
+	 * @param key the key
+	 * @param values the values
+	 */
+	inline Option(std::string key, std::vector<std::string> values) noexcept
+		: std::vector<std::string>{std::move(values)}
+		, m_key{std::move(key)}
 	{
 	}
 
@@ -237,7 +272,9 @@
 	 */
 	inline const std::string &value() const noexcept
 	{
-		return m_value;
+		static std::string dummy;
+
+		return empty() ? dummy : (*this)[0];
 	}
 };
 
@@ -300,7 +337,7 @@
 	 *
 	 * @param key the key
 	 * @return the option
-	 * @warning No bounds checking is performed
+	 * @throw std::out_of_range if the key does not exist
 	 */
 	inline Option &operator[](const std::string &key)
 	{
@@ -312,7 +349,7 @@
 	 *
 	 * @param key the key
 	 * @return the option
-	 * @warning No bounds checking is performed
+	 * @throw std::out_of_range if the key does not exist
 	 */
 	inline const Option &operator[](const std::string &key) const
 	{
@@ -459,7 +496,7 @@
 	 *
 	 * @param key the key
 	 * @return the section
-	 * @warning No bounds checking is performed
+	 * @throw std::out_of_range if the key does not exist
 	 */
 	inline Section &operator[](const std::string &key)
 	{
@@ -471,7 +508,7 @@
 	 *
 	 * @param key the key
 	 * @return the section
-	 * @warning No bounds checking is performed
+	 * @throw std::out_of_range if the key does not exist
 	 */
 	inline const Section &operator[](const std::string &key) const
 	{
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/tests/Ini/configs/lists.conf	Wed Oct 14 20:19:28 2015 +0200
@@ -0,0 +1,9 @@
+[rule1]
+servers = ( "abc", "bcd" )
+
+[rule2]
+servers =
+(
+ xyz,
+ poi
+)
--- a/C++/tests/Ini/main.cpp	Wed Oct 14 19:48:42 2015 +0200
+++ b/C++/tests/Ini/main.cpp	Wed Oct 14 20:19:28 2015 +0200
@@ -208,6 +208,24 @@
 }
 
 /* --------------------------------------------------------
+ * List
+ * -------------------------------------------------------- */
+
+TEST(List, test)
+{
+	try {
+		std::vector<std::string> rule1{"abc", "bcd"};
+		std::vector<std::string> rule2{"xyz", "poi"};
+		ini::Document doc{ini::File{"Ini/lists.conf"}};
+
+		ASSERT_EQ(rule1, doc[0][0]);
+		ASSERT_EQ(rule2, doc[1][0]);
+	} catch (const ini::Error &error) {
+		FAIL() << error.line() << ":" << error.column() << ": " << error.what();
+	}
+}
+
+/* --------------------------------------------------------
  * Errors
  * -------------------------------------------------------- */
 
--- a/CMakeLists.txt	Wed Oct 14 19:48:42 2015 +0200
+++ b/CMakeLists.txt	Wed Oct 14 20:19:28 2015 +0200
@@ -239,6 +239,7 @@
 		${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/lists.conf
 		${code_SOURCE_DIR}/C++/tests/Ini/configs/multi.conf
 		${code_SOURCE_DIR}/C++/tests/Ini/configs/novalue.conf
 		${code_SOURCE_DIR}/C++/tests/Ini/configs/simple.conf