Path: blob/main/contrib/kyua/utils/config/parser.cpp
48081 views
// Copyright 2012 The Kyua Authors.1// All rights reserved.2//3// Redistribution and use in source and binary forms, with or without4// modification, are permitted provided that the following conditions are5// met:6//7// * Redistributions of source code must retain the above copyright8// notice, this list of conditions and the following disclaimer.9// * Redistributions in binary form must reproduce the above copyright10// notice, this list of conditions and the following disclaimer in the11// documentation and/or other materials provided with the distribution.12// * Neither the name of Google Inc. nor the names of its contributors13// may be used to endorse or promote products derived from this software14// without specific prior written permission.15//16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.2728#include "utils/config/parser.hpp"2930#include <lutok/exceptions.hpp>31#include <lutok/operations.hpp>32#include <lutok/stack_cleaner.hpp>33#include <lutok/state.ipp>3435#include "utils/config/exceptions.hpp"36#include "utils/config/lua_module.hpp"37#include "utils/config/tree.ipp"38#include "utils/fs/path.hpp"39#include "utils/logging/macros.hpp"40#include "utils/noncopyable.hpp"4142namespace config = utils::config;434445// History of configuration file versions:46//47// 2 - Changed the syntax() call to take only a version number, instead of the48// word 'config' as the first argument and the version as the second one.49// Files now start with syntax(2) instead of syntax('config', 1).50//51// 1 - Initial version.525354/// Internal implementation of the parser.55struct utils::config::parser::impl : utils::noncopyable {56/// Pointer to the parent parser. Needed for callbacks.57parser* _parent;5859/// The Lua state used by this parser to process the configuration file.60lutok::state _state;6162/// The tree to be filed in by the configuration parameters, as provided by63/// the caller.64config::tree& _tree;6566/// Whether syntax() has been called or not.67bool _syntax_called;6869/// Constructs a new implementation.70///71/// \param parent_ Pointer to the class being constructed.72/// \param config_tree_ The configuration tree provided by the user.73impl(parser* const parent_, tree& config_tree_) :74_parent(parent_), _tree(config_tree_), _syntax_called(false)75{76}7778friend void lua_syntax(lutok::state&);7980/// Callback executed by the Lua syntax() function.81///82/// \param syntax_version The syntax format version as provided by the83/// configuration file in the call to syntax().84void85syntax_callback(const int syntax_version)86{87if (_syntax_called)88throw syntax_error("syntax() can only be called once");89_syntax_called = true;9091// Allow the parser caller to populate the tree with its own schema92// depending on the format/version combination.93_parent->setup(_tree, syntax_version);9495// Export the config module to the Lua state so that all global variable96// accesses are redirected to the configuration tree.97config::redirect(_state, _tree);98}99};100101102namespace {103104105static int106lua_syntax(lutok::state& state)107{108if (!state.is_number(-1))109throw config::value_error("Last argument to syntax must be a number");110const int syntax_version = state.to_integer(-1);111112if (syntax_version == 1) {113if (state.get_top() != 2)114throw config::value_error("Version 1 files need two arguments to "115"syntax()");116if (!state.is_string(-2) || state.to_string(-2) != "config")117throw config::value_error("First argument to syntax must be "118"'config' for version 1 files");119} else {120if (state.get_top() != 1)121throw config::value_error("syntax() only takes one argument");122}123124state.get_global("_config_parser");125config::parser::impl* impl =126*state.to_userdata< config::parser::impl* >(-1);127state.pop(1);128129impl->syntax_callback(syntax_version);130131return 0;132}133134135} // anonymous namespace136137138/// Constructs a new parser.139///140/// \param [in,out] config_tree The configuration tree into which the values set141/// in the configuration file will be stored.142config::parser::parser(tree& config_tree) :143_pimpl(new impl(this, config_tree))144{145lutok::stack_cleaner cleaner(_pimpl->_state);146147_pimpl->_state.push_cxx_function(lua_syntax);148_pimpl->_state.set_global("syntax");149*_pimpl->_state.new_userdata< config::parser::impl* >() = _pimpl.get();150_pimpl->_state.set_global("_config_parser");151}152153154/// Destructor.155config::parser::~parser(void)156{157}158159160/// Parses a configuration file.161///162/// \post The tree registered during the construction of this class is updated163/// to contain the values read from the configuration file. If the processing164/// fails, the state of the output tree is undefined.165///166/// \param file The path to the file to process.167///168/// \throw syntax_error If there is any problem processing the file.169void170config::parser::parse(const fs::path& file)171{172try {173lutok::do_file(_pimpl->_state, file.str(), 0, 0, 0);174} catch (const lutok::error& e) {175throw syntax_error(e.what());176}177178if (!_pimpl->_syntax_called)179throw syntax_error("No syntax defined (no call to syntax() found)");180}181182183