diff C++/modules/Ini/Ini.h @ 427:aa9cc55338be

Ini: rewrite the parse/analyze process
author David Demelier <markand@malikania.fr>
date Wed, 14 Oct 2015 15:08:45 +0200
parents 30e4a93f86c7
children c2b02b5f32a9
line wrap: on
line diff
--- a/C++/modules/Ini/Ini.h	Wed Oct 14 10:04:24 2015 +0200
+++ b/C++/modules/Ini/Ini.h	Wed Oct 14 15:08:45 2015 +0200
@@ -21,43 +21,45 @@
 
 /**
  * @file Ini.h
- * @brief Configuration file parser
+ * @brief Configuration file parser.
  */
 
 #include <algorithm>
-#include <deque>
-#include <stdexcept>
+#include <exception>
 #include <string>
+#include <vector>
 
 namespace ini {
 
+class Document;
+
 /**
  * @class Error
  * @brief Error in a file
  */
 class Error : public std::exception {
 private:
-	int m_line;
-	int m_position;
-	std::string m_error;
+	int m_line;		//!< line number
+	int m_column;		//!< line column
+	std::string m_message;	//!< error message
 
 public:
 	/**
-	 * Construct an error.
+	 * Constructor.
 	 *
-	 * @param line the line
-	 * @param position the position
-	 * @param error the error
+	 * @param l the line
+	 * @param c the column
+	 * @param m the message
 	 */
-	inline Error(int line, int position, std::string error)
-		: m_line(line)
-		, m_position(position)
-		, m_error(std::move(error))
+	inline Error(int l, int c, std::string m) noexcept
+		: m_line{l}
+		, m_column{c}
+		, m_message{std::move(m)}
 	{
 	}
 
 	/**
-	 * Return the line number.
+	 * Get the line number.
 	 *
 	 * @return the line
 	 */
@@ -67,29 +69,146 @@
 	}
 
 	/**
-	 * Return the position in the current line.
+	 * Get the column number.
 	 *
-	 * @return the position
+	 * @return the column
 	 */
-	inline int position() const noexcept
+	inline int column() const noexcept
 	{
-		return m_position;
+		return m_column;
 	}
 
 	/**
-	 * Get the error string.
+	 * Return the raw error message (no line and column shown).
 	 *
-	 * @return the string
+	 * @return the error message
 	 */
-	inline const char *what() const noexcept
+	const char *what() const noexcept override
 	{
-		return m_error.c_str();
+		return m_message.c_str();
 	}
 };
 
 /**
+ * @class Token
+ * @brief Describe a token read in the .ini source
+ *
+ * This class can be used when you want to parse a .ini file yourself.
+ *
+ * @see Document::analyze
+ */
+class Token {
+public:
+	/**
+	 * @brief Token type
+	 */
+	enum Type {
+		Include,	//!< include statement
+		Section,	//!< [section]
+		Word,		//!< word without quotes
+		QuotedWord,	//!< word with quotes
+		Comment,	//!< # comment like this
+		Assign,		//!< = assignment
+		Space,		//!< space or tabs
+		Line		//!< '\n'
+	};
+
+private:
+	Type m_type;
+	int m_line;
+	int m_column;
+	std::string m_value;
+
+public:
+	/**
+	 * Construct a token.
+	 *
+	 * @param type the type
+	 * @param line the line
+	 * @param column the column
+	 * @param value the value
+	 */
+	Token(Type type, int line, int column, std::string value = "") noexcept
+		: m_type{type}
+		, m_line{line}
+		, m_column{column}
+	{
+		switch (type) {
+		case Include:
+			m_value = "@include";
+			break;
+		case Section:
+		case Word:
+		case QuotedWord:
+			m_value = value;
+			break;
+		case Comment:
+			m_value = "comment";
+			break;
+		case Assign:
+			m_value = "=";
+			break;
+		case Line:
+			m_value = "<newline>";
+			break;
+		case Space:
+			m_value = "<space>";
+			break;
+		default:
+			break;
+		}
+	}
+
+	/**
+	 * Get the type.
+	 *
+	 * @return the type
+	 */
+	inline Type type() const noexcept
+	{
+		return m_type;
+	}
+
+	/**
+	 * Get the line.
+	 *
+	 * @return the line
+	 */
+	inline int line() const noexcept
+	{
+		return m_line;
+	}
+
+	/**
+	 * Get the column.
+	 *
+	 * @return the column
+	 */
+	inline int column() const noexcept
+	{
+		return m_column;
+	}
+
+	/**
+	 * Get the value. For words, quoted words and section, the value is the content. Otherwise it's the
+	 * characters parsed.
+	 *
+	 * @return the value
+	 */
+	inline const std::string &value() const noexcept
+	{
+		return m_value;
+	}
+};
+
+/**
+ * List of tokens in order they are analyzed.
+ */
+using Tokens = std::vector<Token>;
+
+/**
  * @class Option
- * @brief Option definition
+ * @brief Option definition.
  */
 class Option {
 private:
@@ -103,9 +222,9 @@
 	 * @param key the key
 	 * @param value the value
 	 */
-	inline Option(std::string key, std::string value)
-		: m_key(std::move(key))
-		, m_value(std::move(value))
+	inline Option(std::string key, std::string value) noexcept
+		: m_key{std::move(key)}
+		, m_value{std::move(value)}
 	{
 	}
 
@@ -132,41 +251,34 @@
 
 /**
  * @class Section
- * @brief Section that contains one or more options
+ * @brief Section that contains one or more options.
  */
-class Section {
+class Section : public std::vector<Option> {
 private:
 	std::string m_key;
-	std::deque<Option> m_options;
 
 	template <typename T>
 	T find(const std::string &key) const
 	{
-		auto it = std::find_if(m_options.begin(), m_options.end(), [&] (const Option &o) {
+		auto it = std::find_if(begin(), end(), [&] (const Option &o) {
 			return o.key() == key;
 		});
 
-		if (it == m_options.end())
-			throw std::out_of_range("option " + key + " not found");
+		if (it == end()) {
+			throw std::out_of_range{"option " + key + " not found"};
+		}
 
 		return const_cast<T>(*it);
 	}
 
 public:
 	/**
-	 * Default constructor has no sections and no values.
-	 */
-	Section() = default;
-
-	/**
-	 * Construct a section with a set of options.
+	 * Construct a section with its name.
 	 *
-	 * @param key the section name
-	 * @param options the list of options
+	 * @param key the key
 	 */
-	inline Section(std::string key, std::deque<Option> options = {}) noexcept
-		: m_key(std::move(key))
-		, m_options(std::move(options))
+	inline Section(std::string key) noexcept
+		: m_key{std::move(key)}
 	{
 	}
 
@@ -181,117 +293,14 @@
 	}
 
 	/**
-	 * Get an iterator to the beginning.
-	 *
-	 * @return the iterator
-	 */
-	inline auto begin() noexcept
-	{
-		return m_options.begin();
-	}
-
-	/**
-	 * Overloaded function.
+	 * Check if the section contains a specific option.
 	 *
-	 * @return the iterator
-	 */
-	inline auto begin() const noexcept
-	{
-		return m_options.begin();
-	}
-
-	/**
-	 * Overloaded function.
-	 *
-	 * @return the iterator
+	 * @param key the option key
+	 * @return true if the option exists
 	 */
-	inline auto cbegin() const noexcept
-	{
-		return m_options.cbegin();
-	}
-
-	/**
-	 * Get an iterator to the end.
-	 *
-	 * @return the iterator
-	 */
-	inline auto end() noexcept
-	{
-		return m_options.end();
-	}
-
-	/**
-	 * Overloaded function.
-	 *
-	 * @return the iterator
-	 */
-	inline auto end() const noexcept
-	{
-		return m_options.end();
-	}
-
-	/**
-	 * Overloaded function.
-	 *
-	 * @return the iterator
-	 */
-	inline auto cend() const noexcept
+	inline bool contains(const std::string &key) const noexcept
 	{
-		return m_options.cend();
-	}
-
-	/**
-	 * Append an option.
-	 *
-	 * @param option the option to add
-	 */
-	inline void push_back(Option option)
-	{
-		m_options.push_back(std::move(option));
-	}
-
-	/**
-	 * Push an option to the beginning.
-	 *
-	 * @param option the option to add
-	 */
-	inline void push_front(Option option)
-	{
-		m_options.push_front(std::move(option));
-	}
-
-	/**
-	 * Get the number of options in that section.
-	 *
-	 * @return the size
-	 */
-	inline unsigned size() const noexcept
-	{
-		return m_options.size();
-	}
-
-	/**
-	 * Access an option at the specified index.
-	 *
-	 * @param index the index
-	 * @return the option
-	 * @warning No bounds checking is performed
-	 */
-	inline Option &operator[](int index) noexcept
-	{
-		return m_options[index];
-	}
-
-	/**
-	 * Access an option at the specified index.
-	 *
-	 * @param index the index
-	 * @return the option
-	 * @warning No bounds checking is performed
-	 */
-	inline const Option &operator[](int index) const noexcept
-	{
-		return m_options[index];
+		return std::find_if(begin(), end(), [&] (const auto &opt) { return opt.key() == key; }) != end();
 	}
 
 	/**
@@ -317,155 +326,140 @@
 	{
 		return find<const Option &>(key);
 	}
+
+	/**
+	 * Inherited operators.
+	 */
+	using std::vector<Option>::operator[];
+};
+
+/**
+ * @class File
+ * @brief Source for reading .ini files.
+ */
+class File {
+public:
+	/**
+	 * Path to the file.
+	 */
+	std::string path;
+
+	/**
+	 * Load the file into the document.
+	 *
+	 * @param doc the document
+	 * @throw Error on errors
+	 */
+	void load(Document &doc);
+};
+
+/**
+ * @class Buffer
+ * @brief Source for reading ini from text.
+ * @note the include statement is not supported with buffers.
+ */
+class Buffer {
+public:
+	/**
+	 * The ini content.
+	 */
+	std::string text;
+
+	/**
+	 * Load the file into the document.
+	 *
+	 * @param doc the document
+	 * @throw Error on errors
+	 */
+	void load(Document &doc);
 };
 
 /**
  * @class Document
  * @brief Ini config file loader
  */
-class Document {
+class Document : public std::vector<Section> {
 private:
-	std::deque<Section> m_sections;
+	std::string m_path;
 
 	template <typename T>
 	T find(const std::string &key) const
 	{
-		auto it = std::find_if(m_sections.begin(), m_sections.end(), [&] (const Section &s) {
+		auto it = std::find_if(begin(), end(), [&] (const Section &s) {
 			return s.key() == key;
 		});
 
-		if (it == m_sections.end())
-			throw std::out_of_range("section " + key + " not found");
+		if (it == end()) {
+			throw std::out_of_range{"section " + key + " not found"};
+		}
 
 		return const_cast<T>(*it);
 	}
 
 public:
 	/**
-	 * Default constructor with an empty configuration.
+	 * Analyze a file and extract tokens. If the function succeeds, that does not mean the content is valid,
+	 * it just means that there are no syntax error.
+	 *
+	 * For example, this class does not allow adding options under no sections and this function will not
+	 * detect that issue.
+	 *
+	 * @param file the file to read
+	 * @return the list of tokens
+	 * @throws Error on errors
 	 */
-	Document() = default;
+	static Tokens analyze(const File &file);
 
 	/**
-	 * Open the path as the configuration file.
+	 * Overloaded function for buffers.
 	 *
-	 * @param path the path
-	 * @throw Error on any error
+	 * @param buffer the buffer to read
+	 * @return the list of tokens
+	 * @throws Error on errors
 	 */
-	Document(const std::string &path);
-
-	/**
-	 * Get an iterator to the beginning.
-	 *
-	 * @return the iterator
-	 */
-	inline auto begin() noexcept
-	{
-		return m_sections.begin();
-	}
+	static Tokens analyze(const Buffer &buffer);
 
 	/**
-	 * Overloaded function.
+	 * Show all tokens and their description.
 	 *
-	 * @return the iterator
+	 * @param tokens the tokens
 	 */
-	inline auto begin() const noexcept
-	{
-		return m_sections.begin();
-	}
+	static void dump(const Tokens &tokens);
+
+	/**
+	 * Construct a document from a file.
+	 *
+	 * @param file the file to read
+	 * @throws Error on errors
+	 */
+	Document(const File &file);
 
 	/**
-	 * Overloaded function.
+	 * Overloaded constructor for buffers.
 	 *
-	 * @return the iterator
+	 * @param buffer the buffer to read
+	 * @throws Error on errors
 	 */
-	inline auto cbegin() const noexcept
-	{
-		return m_sections.cbegin();
-	}
+	Document(const Buffer &buffer);
 
 	/**
-	 * Get an iterator to the end.
+	 * Get the current document path, only useful when constructed from File source.
 	 *
-	 * @return the iterator
+	 * @return the path
 	 */
-	inline auto end() noexcept
+	inline const std::string &path() const noexcept
 	{
-		return m_sections.end();
+		return m_path;
 	}
 
 	/**
-	 * Overloaded function.
+	 * Check if a document has a specific section.
 	 *
-	 * @return the iterator
-	 */
-	inline auto end() const noexcept
-	{
-		return m_sections.end();
-	}
-
-	/**
-	 * Overloaded function.
-	 *
-	 * @return the iterator
+	 * @param key the key
 	 */
-	inline auto cend() const noexcept
-	{
-		return m_sections.cend();
-	}
-
-	/**
-	 * Get the number of sections in the configuration.
-	 *
-	 * @return the size
-	 */
-	inline unsigned size() const noexcept
-	{
-		return m_sections.size();
-	}
-
-	/**
-	 * Append a section to the end.
-	 *
-	 * @param section the section to add
-	 */
-	inline void push_back(Section section)
+	inline bool contains(const std::string &key) const noexcept
 	{
-		m_sections.push_back(std::move(section));
-	}
-
-	/**
-	 * Add a section to the beginning.
-	 *
-	 * @param section the section to add
-	 */
-	inline void push_front(Section section)
-	{
-		m_sections.push_front(std::move(section));
-	}
-
-	/**
-	 * Access a section at the specified index.
-	 *
-	 * @param index the index
-	 * @return the section
-	 * @warning No bounds checking is performed
-	 */
-	inline Section &operator[](int index) noexcept
-	{
-		return m_sections[index];
-	}
-
-	/**
-	 * Access a section at the specified index.
-	 *
-	 * @param index the index
-	 * @return the section
-	 * @warning No bounds checking is performed
-	 */
-	inline const Section &operator[](int index) const noexcept
-	{
-		return m_sections[index];
+		return std::find_if(begin(), end(), [&] (const auto &sc) { return sc.key() == key; }) != end();
 	}
 
 	/**
@@ -491,8 +485,13 @@
 	{
 		return find<Section &>(key);
 	}
+
+	/**
+	 * Inherited operators.
+	 */
+	using std::vector<Section>::operator[];
 };
 
 } // !ini
 
-#endif // !_INI_H_
+#endif // !_INI_H_
\ No newline at end of file