Mercurial > code
view C++/modules/Ini/Ini.cpp @ 367:99484c154d8a
Pack: complete removal
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 29 Apr 2015 11:17:56 +0200 |
parents | 0b576ee64d45 |
children | d5ec1174b707 |
line wrap: on
line source
/* * Ini.cpp -- .ini file parsing * * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include <cctype> #include <cerrno> #include <cstring> #include <fstream> #include <iostream> #include <iterator> #include <memory> #include <ostream> #include <sstream> #include <vector> #if defined(_WIN32) # include <Shlwapi.h> // for PathIsRelative #endif #include "Ini.h" namespace { /* -------------------------------------------------------- * Tokens * -------------------------------------------------------- */ enum class TokenType { Comment = '#', SectionBegin = '[', SectionEnd = ']', Escape = '\\', QuoteSimple = '\'', QuoteDouble = '"', NewLine = '\n', Assign = '=', Include = '@', Word, Space }; class Token { private: TokenType m_type; int m_line; int m_position; std::string m_value; public: inline Token(TokenType type, int line, int position, std::string value = "") : m_type(type) , m_line(line) , m_position(position) , m_value(std::move(value)) { } inline TokenType type() const noexcept { return m_type; } inline int line() const noexcept { return m_line; } inline int position() const noexcept { return m_position; } inline std::string value() const { switch (m_type) { case TokenType::Comment: return "#"; case TokenType::SectionBegin: return "["; case TokenType::SectionEnd: return "]"; case TokenType::QuoteSimple: return "'"; case TokenType::QuoteDouble: return "\""; case TokenType::NewLine: return "\n"; case TokenType::Assign: return "="; case TokenType::Include: return "@"; case TokenType::Space: return m_value; case TokenType::Word: return m_value; default: break; } return ""; } inline std::string toString() const { switch (m_type) { case TokenType::Comment: return "'#'"; case TokenType::SectionBegin: return "'['"; case TokenType::SectionEnd: return "']'"; case TokenType::QuoteSimple: return "'"; case TokenType::QuoteDouble: return "\""; case TokenType::NewLine: return "<newline>"; case TokenType::Assign: return "="; case TokenType::Include: return "@"; case TokenType::Space: return "<blank>"; case TokenType::Word: return "`" + m_value + "'"; default: break; } return ""; } }; using TokenStack = std::vector<Token>; /* -------------------------------------------------------- * IniBuilder * -------------------------------------------------------- */ class IniBuilder { private: std::string m_path; std::string m_base; Ini &m_ini; private: inline bool isReserved(char c) const noexcept { return c == '\n' || c == '#' || c == '"' || c == '\'' || c == '=' || c == '[' || c == ']' || c == '@'; } std::string base(std::string path) { auto pos = path.find_last_of("/\\"); if (pos != std::string::npos) { path.erase(pos); } else { path = "."; } return path; } #if defined(_WIN32) bool isAbsolute(const std::string &path) { return !PathIsRelative(path.c_str()); } #else bool isAbsolute(const std::string &path) { return path.size() > 0 && path[0] == '/'; } #endif std::vector<Token> analyze(std::istream &stream) const { std::istreambuf_iterator<char> it(stream); std::istreambuf_iterator<char> end; std::vector<Token> tokens; int lineno{1}; int position{0}; while (it != end) { std::string value; if (isReserved(*it)) { while (it != end && isReserved(*it)) { // Single character tokens switch (*it) { case '\n': ++lineno; position = 0; case '#': case '[': case ']': case '\'': case '"': case '=': case '@': tokens.push_back({ static_cast<TokenType>(*it), lineno, position }); ++it; ++position; default: break; } } } else if (std::isspace(*it)) { while (it != end && std::isspace(*it) && *it != '\n') { value.push_back(*it++); } tokens.push_back({ TokenType::Space, lineno, position, std::move(value) }); } else { while (it != end && !std::isspace(*it) && !isReserved(*it)) { value.push_back(*it++); } tokens.push_back({ TokenType::Word, lineno, position, std::move(value) }); } } return tokens; } void readComment(TokenStack::iterator &it, TokenStack::iterator end) { while (it != end && it->type() != TokenType::NewLine) { ++ it; } // remove new line ++ it; } void readSpace(TokenStack::iterator &it, TokenStack::iterator end) { while (it != end && it->type() == TokenType::Space) { ++ it; } } void readNewLine(TokenStack::iterator &it, TokenStack::iterator end) { while (it != end && it->type() == TokenType::NewLine) { ++ it; } } IniSection readSection(TokenStack::iterator &it, TokenStack::iterator end) { if (++it == end || it->type() != TokenType::Word) { throw IniError(it->line(), it->position(), "word expected after [, got " + it->toString()); } IniSection section(it->value()); if (++it == end || it->type() != TokenType::SectionEnd) { throw IniError(it->line(), it->position(), "] expected, got " + it->toString()); } // Remove ] ++ it; if (it == end) { return section; } while (it != end && it->type() != TokenType::SectionBegin) { if (it->type() == TokenType::Space) { readSpace(it, end); } else if (it->type() == TokenType::NewLine) { readNewLine(it, end); } else if (it->type() == TokenType::Comment) { readComment(it, end); } else if (it->type() == TokenType::Word) { section.push_back(readOption(it, end)); } else { throw IniError(it->line(), it->position(), "unexpected token " + it->toString()); } } return section; } IniOption readOption(TokenStack::iterator &it, TokenStack::iterator end) { std::string key = it->value(); if (++it == end) { throw IniError(it->line(), it->position(), "expected '=' after option declaration, got <EOF>"); } readSpace(it, end); if (it == end || it->type() != TokenType::Assign) { throw IniError(it->line(), it->position(), "expected '=' after option declaration, got " + it++->toString()); } readSpace(++it, end); std::ostringstream oss; if (it->type() == TokenType::QuoteSimple || it->type() == TokenType::QuoteDouble) { TokenStack::iterator save = it++; while (it != end && it->type() != save->type()) { oss << it++->value(); } if (it == end) throw IniError(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 IniError(it->line(), it->position(), "expected option value after '=', got " + it->toString()); } return IniOption(std::move(key), oss.str()); } void readInclude(TokenStack::iterator &it, TokenStack::iterator end) { if (++it == end || (it->type() != TokenType::Word || it->value() != "include")) { throw IniError(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 IniError(it->line(), it->position(), "expected filename after @include statement"); } // Filename if (++it == end || it->type() != TokenType::Word) { throw IniError(it->line(), it->position(), "expected filename after @include statement"); } std::string value = it->value(); std::string fullpath; if (isAbsolute(value)) { fullpath = value; } else { fullpath = m_base + "/" + it->value(); } // Must be closed with the same quote if (++it == end || it->type() != save->type()) { throw IniError(save->line(), save->position(), "undiclosed quote: " + save->toString() + " expected"); } // Remove quote ++ it; IniBuilder(m_ini, fullpath); } public: IniBuilder(Ini &ini, std::string path) : m_path(path) , m_base(base(std::move(path))) , m_ini(ini) { std::ifstream file(m_path); if (!file.is_open()) throw std::runtime_error(std::strerror(errno)); std::vector<Token> ts = analyze(file); auto it = ts.begin(); auto end = ts.end(); while (it != end) { if (it->type() == TokenType::Space) { readSpace(it, end); } else if (it->type() == TokenType::NewLine) { readNewLine(it, end); } else if (it->type() == TokenType::Comment) { readComment(it, end); } else if (it->type() == TokenType::Include) { readInclude(it, end); } else if (it->type() == TokenType::SectionBegin) { m_ini.push_back(readSection(it, end)); } else { throw IniError(it->line(), it->position(), "unexpected " + it->toString() + " on root document"); } } } }; } // !namespace /* -------------------------------------------------------- * Ini * -------------------------------------------------------- */ Ini::Ini(const std::string &path) { IniBuilder(*this, path); }