Path: blob/main/contrib/kyua/utils/config/lua_module.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/lua_module.hpp"2930#include <lutok/stack_cleaner.hpp>31#include <lutok/state.ipp>3233#include "utils/config/exceptions.hpp"34#include "utils/config/keys.hpp"35#include "utils/config/tree.ipp"3637namespace config = utils::config;38namespace detail = utils::config::detail;394041namespace {424344/// Gets the tree singleton stored in the Lua state.45///46/// \param state The Lua state. The registry must contain a key named47/// "tree" with a pointer to the singleton.48///49/// \return A reference to the tree associated with the Lua state.50///51/// \throw syntax_error If the tree cannot be located.52config::tree&53get_global_tree(lutok::state& state)54{55lutok::stack_cleaner cleaner(state);5657state.push_value(lutok::registry_index);58state.push_string("tree");59state.get_table(-2);60if (state.is_nil(-1))61throw config::syntax_error("Cannot find tree singleton; global state "62"corrupted?");63config::tree& tree = **state.to_userdata< config::tree* >(-1);64state.pop(1);65return tree;66}676869/// Gets a fully-qualified tree key from the state.70///71/// \param state The Lua state.72/// \param table_index An index to the Lua stack pointing to the table being73/// accessed. If this table contains a tree_key metadata property, this is74/// considered to be the prefix of the tree key.75/// \param field_index An index to the Lua stack pointing to the entry76/// containing the name of the field being indexed.77///78/// \return A dotted key.79///80/// \throw invalid_key_error If the name of the key is invalid.81static std::string82get_tree_key(lutok::state& state, const int table_index, const int field_index)83{84PRE(state.is_string(field_index));85const std::string field = state.to_string(field_index);86if (!field.empty() && field[0] == '_')87throw config::invalid_key_error(88F("Configuration key cannot have an underscore as a prefix; "89"found %s") % field);9091std::string tree_key;92if (state.get_metafield(table_index, "tree_key")) {93tree_key = state.to_string(-1) + "." + state.to_string(field_index - 1);94state.pop(1);95} else96tree_key = state.to_string(field_index);97return tree_key;98}99100101static int redirect_newindex(lutok::state&);102static int redirect_index(lutok::state&);103104105/// Creates a table for a new configuration inner node.106///107/// \post state(-1) Contains the new table.108///109/// \param state The Lua state in which to push the table.110/// \param tree_key The key to which the new table corresponds.111static void112new_table_for_key(lutok::state& state, const std::string& tree_key)113{114state.new_table();115{116state.new_table();117{118state.push_string("__index");119state.push_cxx_function(redirect_index);120state.set_table(-3);121122state.push_string("__newindex");123state.push_cxx_function(redirect_newindex);124state.set_table(-3);125126state.push_string("tree_key");127state.push_string(tree_key);128state.set_table(-3);129}130state.set_metatable(-2);131}132}133134135/// Sets the value of an configuration node.136///137/// \pre state(-3) The table to index. If this is not _G, then the table138/// metadata must contain a tree_key property describing the path to139/// current level.140/// \pre state(-2) The field to index into the table. Must be a string.141/// \pre state(-1) The value to set the indexed table field to.142///143/// \param state The Lua state in which to operate.144///145/// \return The number of result values on the Lua stack; always 0.146///147/// \throw invalid_key_error If the provided key is invalid.148/// \throw unknown_key_error If the key cannot be located.149/// \throw value_error If the value has an unsupported type or cannot be150/// set on the key, or if the input table or index are invalid.151static int152redirect_newindex(lutok::state& state)153{154if (!state.is_table(-3))155throw config::value_error("Indexed object is not a table");156if (!state.is_string(-2))157throw config::value_error("Invalid field in configuration object "158"reference; must be a string");159160const std::string dotted_key = get_tree_key(state, -3, -2);161try {162config::tree& tree = get_global_tree(state);163tree.set_lua(dotted_key, state, -1);164} catch (const config::value_error& e) {165throw config::invalid_key_value(detail::parse_key(dotted_key),166e.what());167}168169// Now really set the key in the Lua table, but prevent direct accesses from170// the user by prefixing it. We do this to ensure that re-setting the same171// key of the tree results in a call to __newindex instead of __index.172state.push_string("_" + state.to_string(-2));173state.push_value(-2);174state.raw_set(-5);175176return 0;177}178179180/// Indexes a configuration node.181///182/// \pre state(-3) The table to index. If this is not _G, then the table183/// metadata must contain a tree_key property describing the path to184/// current level. If the field does not exist, a new table is created.185/// \pre state(-1) The field to index into the table. Must be a string.186///187/// \param state The Lua state in which to operate.188///189/// \return The number of result values on the Lua stack; always 1.190///191/// \throw value_error If the input table or index are invalid.192static int193redirect_index(lutok::state& state)194{195if (!state.is_table(-2))196throw config::value_error("Indexed object is not a table");197if (!state.is_string(-1))198throw config::value_error("Invalid field in configuration object "199"reference; must be a string");200201// Query if the key has already been set by a call to redirect_newindex.202state.push_string("_" + state.to_string(-1));203state.raw_get(-3);204if (!state.is_nil(-1))205return 1;206state.pop(1);207208state.push_value(-1); // Duplicate the field name.209state.raw_get(-3); // Get table[field] to see if it's defined.210if (state.is_nil(-1)) {211state.pop(1);212213// The stack is now the same as when we entered the function, but we214// know that the field is undefined and thus have to create a new215// configuration table.216INV(state.is_table(-2));217INV(state.is_string(-1));218219const config::tree& tree = get_global_tree(state);220const std::string tree_key = get_tree_key(state, -2, -1);221if (tree.is_set(tree_key)) {222// Publish the pre-recorded value in the tree to the Lua state,223// instead of considering this table key a new inner node.224tree.push_lua(tree_key, state);225} else {226state.push_string("_" + state.to_string(-1));227state.insert(-2);228state.pop(1);229230new_table_for_key(state, tree_key);231232// Duplicate the newly created table and place it deep in the stack233// so that the raw_set below leaves us with the return value of this234// function at the top of the stack.235state.push_value(-1);236state.insert(-4);237238state.raw_set(-3);239state.pop(1);240}241}242return 1;243}244245246} // anonymous namespace247248249/// Install wrappers for globals to set values in the configuration tree.250///251/// This function installs wrappers to capture all accesses to global variables.252/// Such wrappers redirect the reads and writes to the out_tree, which is the253/// entity that defines what configuration variables exist.254///255/// \param state The Lua state into which to install the wrappers.256/// \param out_tree The tree with the layout definition and where the257/// configuration settings will be collected.258void259config::redirect(lutok::state& state, tree& out_tree)260{261lutok::stack_cleaner cleaner(state);262263state.get_global_table();264{265state.push_string("__index");266state.push_cxx_function(redirect_index);267state.set_table(-3);268269state.push_string("__newindex");270state.push_cxx_function(redirect_newindex);271state.set_table(-3);272}273state.set_metatable(-1);274275state.push_value(lutok::registry_index);276state.push_string("tree");277config::tree** tree = state.new_userdata< config::tree* >();278*tree = &out_tree;279state.set_table(-3);280state.pop(1);281}282283284