Mercurial > code
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