xref: /src/contrib/kyua/utils/config/lua_module.cpp (revision b0d29bc47dba79f6f38e67eabadfb4b32ffd9390)
108334c51SBrooks Davis // Copyright 2012 The Kyua Authors.
208334c51SBrooks Davis // All rights reserved.
308334c51SBrooks Davis //
408334c51SBrooks Davis // Redistribution and use in source and binary forms, with or without
508334c51SBrooks Davis // modification, are permitted provided that the following conditions are
608334c51SBrooks Davis // met:
708334c51SBrooks Davis //
808334c51SBrooks Davis // * Redistributions of source code must retain the above copyright
908334c51SBrooks Davis //   notice, this list of conditions and the following disclaimer.
1008334c51SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
1108334c51SBrooks Davis //   notice, this list of conditions and the following disclaimer in the
1208334c51SBrooks Davis //   documentation and/or other materials provided with the distribution.
1308334c51SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
1408334c51SBrooks Davis //   may be used to endorse or promote products derived from this software
1508334c51SBrooks Davis //   without specific prior written permission.
1608334c51SBrooks Davis //
1708334c51SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1808334c51SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1908334c51SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2008334c51SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
2108334c51SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2208334c51SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2308334c51SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2408334c51SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2508334c51SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2608334c51SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2708334c51SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2808334c51SBrooks Davis 
2908334c51SBrooks Davis #include "utils/config/lua_module.hpp"
3008334c51SBrooks Davis 
3108334c51SBrooks Davis #include <lutok/stack_cleaner.hpp>
3208334c51SBrooks Davis #include <lutok/state.ipp>
3308334c51SBrooks Davis 
3408334c51SBrooks Davis #include "utils/config/exceptions.hpp"
3508334c51SBrooks Davis #include "utils/config/keys.hpp"
3608334c51SBrooks Davis #include "utils/config/tree.ipp"
3708334c51SBrooks Davis 
3808334c51SBrooks Davis namespace config = utils::config;
3908334c51SBrooks Davis namespace detail = utils::config::detail;
4008334c51SBrooks Davis 
4108334c51SBrooks Davis 
4208334c51SBrooks Davis namespace {
4308334c51SBrooks Davis 
4408334c51SBrooks Davis 
4508334c51SBrooks Davis /// Gets the tree singleton stored in the Lua state.
4608334c51SBrooks Davis ///
4708334c51SBrooks Davis /// \param state The Lua state.  The registry must contain a key named
4808334c51SBrooks Davis ///    "tree" with a pointer to the singleton.
4908334c51SBrooks Davis ///
5008334c51SBrooks Davis /// \return A reference to the tree associated with the Lua state.
5108334c51SBrooks Davis ///
5208334c51SBrooks Davis /// \throw syntax_error If the tree cannot be located.
5308334c51SBrooks Davis config::tree&
get_global_tree(lutok::state & state)5408334c51SBrooks Davis get_global_tree(lutok::state& state)
5508334c51SBrooks Davis {
5608334c51SBrooks Davis     lutok::stack_cleaner cleaner(state);
5708334c51SBrooks Davis 
5808334c51SBrooks Davis     state.push_value(lutok::registry_index);
5908334c51SBrooks Davis     state.push_string("tree");
6008334c51SBrooks Davis     state.get_table(-2);
6108334c51SBrooks Davis     if (state.is_nil(-1))
6208334c51SBrooks Davis         throw config::syntax_error("Cannot find tree singleton; global state "
6308334c51SBrooks Davis                                    "corrupted?");
6408334c51SBrooks Davis     config::tree& tree = **state.to_userdata< config::tree* >(-1);
6508334c51SBrooks Davis     state.pop(1);
6608334c51SBrooks Davis     return tree;
6708334c51SBrooks Davis }
6808334c51SBrooks Davis 
6908334c51SBrooks Davis 
7008334c51SBrooks Davis /// Gets a fully-qualified tree key from the state.
7108334c51SBrooks Davis ///
7208334c51SBrooks Davis /// \param state The Lua state.
7308334c51SBrooks Davis /// \param table_index An index to the Lua stack pointing to the table being
7408334c51SBrooks Davis ///     accessed.  If this table contains a tree_key metadata property, this is
7508334c51SBrooks Davis ///     considered to be the prefix of the tree key.
7608334c51SBrooks Davis /// \param field_index An index to the Lua stack pointing to the entry
7708334c51SBrooks Davis ///     containing the name of the field being indexed.
7808334c51SBrooks Davis ///
7908334c51SBrooks Davis /// \return A dotted key.
8008334c51SBrooks Davis ///
8108334c51SBrooks Davis /// \throw invalid_key_error If the name of the key is invalid.
8208334c51SBrooks Davis static std::string
get_tree_key(lutok::state & state,const int table_index,const int field_index)8308334c51SBrooks Davis get_tree_key(lutok::state& state, const int table_index, const int field_index)
8408334c51SBrooks Davis {
8508334c51SBrooks Davis     PRE(state.is_string(field_index));
8608334c51SBrooks Davis     const std::string field = state.to_string(field_index);
8708334c51SBrooks Davis     if (!field.empty() && field[0] == '_')
8808334c51SBrooks Davis         throw config::invalid_key_error(
8908334c51SBrooks Davis             F("Configuration key cannot have an underscore as a prefix; "
9008334c51SBrooks Davis               "found %s") % field);
9108334c51SBrooks Davis 
9208334c51SBrooks Davis     std::string tree_key;
9308334c51SBrooks Davis     if (state.get_metafield(table_index, "tree_key")) {
9408334c51SBrooks Davis         tree_key = state.to_string(-1) + "." + state.to_string(field_index - 1);
9508334c51SBrooks Davis         state.pop(1);
9608334c51SBrooks Davis     } else
9708334c51SBrooks Davis         tree_key = state.to_string(field_index);
9808334c51SBrooks Davis     return tree_key;
9908334c51SBrooks Davis }
10008334c51SBrooks Davis 
10108334c51SBrooks Davis 
10208334c51SBrooks Davis static int redirect_newindex(lutok::state&);
10308334c51SBrooks Davis static int redirect_index(lutok::state&);
10408334c51SBrooks Davis 
10508334c51SBrooks Davis 
10608334c51SBrooks Davis /// Creates a table for a new configuration inner node.
10708334c51SBrooks Davis ///
10808334c51SBrooks Davis /// \post state(-1) Contains the new table.
10908334c51SBrooks Davis ///
11008334c51SBrooks Davis /// \param state The Lua state in which to push the table.
11108334c51SBrooks Davis /// \param tree_key The key to which the new table corresponds.
11208334c51SBrooks Davis static void
new_table_for_key(lutok::state & state,const std::string & tree_key)11308334c51SBrooks Davis new_table_for_key(lutok::state& state, const std::string& tree_key)
11408334c51SBrooks Davis {
11508334c51SBrooks Davis     state.new_table();
11608334c51SBrooks Davis     {
11708334c51SBrooks Davis         state.new_table();
11808334c51SBrooks Davis         {
11908334c51SBrooks Davis             state.push_string("__index");
12008334c51SBrooks Davis             state.push_cxx_function(redirect_index);
12108334c51SBrooks Davis             state.set_table(-3);
12208334c51SBrooks Davis 
12308334c51SBrooks Davis             state.push_string("__newindex");
12408334c51SBrooks Davis             state.push_cxx_function(redirect_newindex);
12508334c51SBrooks Davis             state.set_table(-3);
12608334c51SBrooks Davis 
12708334c51SBrooks Davis             state.push_string("tree_key");
12808334c51SBrooks Davis             state.push_string(tree_key);
12908334c51SBrooks Davis             state.set_table(-3);
13008334c51SBrooks Davis         }
13108334c51SBrooks Davis         state.set_metatable(-2);
13208334c51SBrooks Davis     }
13308334c51SBrooks Davis }
13408334c51SBrooks Davis 
13508334c51SBrooks Davis 
13608334c51SBrooks Davis /// Sets the value of an configuration node.
13708334c51SBrooks Davis ///
13808334c51SBrooks Davis /// \pre state(-3) The table to index.  If this is not _G, then the table
13908334c51SBrooks Davis ///     metadata must contain a tree_key property describing the path to
14008334c51SBrooks Davis ///     current level.
14108334c51SBrooks Davis /// \pre state(-2) The field to index into the table.  Must be a string.
14208334c51SBrooks Davis /// \pre state(-1) The value to set the indexed table field to.
14308334c51SBrooks Davis ///
14408334c51SBrooks Davis /// \param state The Lua state in which to operate.
14508334c51SBrooks Davis ///
14608334c51SBrooks Davis /// \return The number of result values on the Lua stack; always 0.
14708334c51SBrooks Davis ///
14808334c51SBrooks Davis /// \throw invalid_key_error If the provided key is invalid.
14908334c51SBrooks Davis /// \throw unknown_key_error If the key cannot be located.
15008334c51SBrooks Davis /// \throw value_error If the value has an unsupported type or cannot be
15108334c51SBrooks Davis ///     set on the key, or if the input table or index are invalid.
15208334c51SBrooks Davis static int
redirect_newindex(lutok::state & state)15308334c51SBrooks Davis redirect_newindex(lutok::state& state)
15408334c51SBrooks Davis {
15508334c51SBrooks Davis     if (!state.is_table(-3))
15608334c51SBrooks Davis         throw config::value_error("Indexed object is not a table");
15708334c51SBrooks Davis     if (!state.is_string(-2))
15808334c51SBrooks Davis         throw config::value_error("Invalid field in configuration object "
15908334c51SBrooks Davis                                   "reference; must be a string");
16008334c51SBrooks Davis 
16108334c51SBrooks Davis     const std::string dotted_key = get_tree_key(state, -3, -2);
16208334c51SBrooks Davis     try {
16308334c51SBrooks Davis         config::tree& tree = get_global_tree(state);
16408334c51SBrooks Davis         tree.set_lua(dotted_key, state, -1);
16508334c51SBrooks Davis     } catch (const config::value_error& e) {
16608334c51SBrooks Davis         throw config::invalid_key_value(detail::parse_key(dotted_key),
16708334c51SBrooks Davis                                         e.what());
16808334c51SBrooks Davis     }
16908334c51SBrooks Davis 
17008334c51SBrooks Davis     // Now really set the key in the Lua table, but prevent direct accesses from
17108334c51SBrooks Davis     // the user by prefixing it.  We do this to ensure that re-setting the same
17208334c51SBrooks Davis     // key of the tree results in a call to __newindex instead of __index.
17308334c51SBrooks Davis     state.push_string("_" + state.to_string(-2));
17408334c51SBrooks Davis     state.push_value(-2);
17508334c51SBrooks Davis     state.raw_set(-5);
17608334c51SBrooks Davis 
17708334c51SBrooks Davis     return 0;
17808334c51SBrooks Davis }
17908334c51SBrooks Davis 
18008334c51SBrooks Davis 
18108334c51SBrooks Davis /// Indexes a configuration node.
18208334c51SBrooks Davis ///
18308334c51SBrooks Davis /// \pre state(-3) The table to index.  If this is not _G, then the table
18408334c51SBrooks Davis ///     metadata must contain a tree_key property describing the path to
18508334c51SBrooks Davis ///     current level.  If the field does not exist, a new table is created.
18608334c51SBrooks Davis /// \pre state(-1) The field to index into the table.  Must be a string.
18708334c51SBrooks Davis ///
18808334c51SBrooks Davis /// \param state The Lua state in which to operate.
18908334c51SBrooks Davis ///
19008334c51SBrooks Davis /// \return The number of result values on the Lua stack; always 1.
19108334c51SBrooks Davis ///
19208334c51SBrooks Davis /// \throw value_error If the input table or index are invalid.
19308334c51SBrooks Davis static int
redirect_index(lutok::state & state)19408334c51SBrooks Davis redirect_index(lutok::state& state)
19508334c51SBrooks Davis {
19608334c51SBrooks Davis     if (!state.is_table(-2))
19708334c51SBrooks Davis         throw config::value_error("Indexed object is not a table");
19808334c51SBrooks Davis     if (!state.is_string(-1))
19908334c51SBrooks Davis         throw config::value_error("Invalid field in configuration object "
20008334c51SBrooks Davis                                   "reference; must be a string");
20108334c51SBrooks Davis 
20208334c51SBrooks Davis     // Query if the key has already been set by a call to redirect_newindex.
20308334c51SBrooks Davis     state.push_string("_" + state.to_string(-1));
20408334c51SBrooks Davis     state.raw_get(-3);
20508334c51SBrooks Davis     if (!state.is_nil(-1))
20608334c51SBrooks Davis         return 1;
20708334c51SBrooks Davis     state.pop(1);
20808334c51SBrooks Davis 
20908334c51SBrooks Davis     state.push_value(-1);  // Duplicate the field name.
21008334c51SBrooks Davis     state.raw_get(-3);  // Get table[field] to see if it's defined.
21108334c51SBrooks Davis     if (state.is_nil(-1)) {
21208334c51SBrooks Davis         state.pop(1);
21308334c51SBrooks Davis 
21408334c51SBrooks Davis         // The stack is now the same as when we entered the function, but we
21508334c51SBrooks Davis         // know that the field is undefined and thus have to create a new
21608334c51SBrooks Davis         // configuration table.
21708334c51SBrooks Davis         INV(state.is_table(-2));
21808334c51SBrooks Davis         INV(state.is_string(-1));
21908334c51SBrooks Davis 
22008334c51SBrooks Davis         const config::tree& tree = get_global_tree(state);
22108334c51SBrooks Davis         const std::string tree_key = get_tree_key(state, -2, -1);
22208334c51SBrooks Davis         if (tree.is_set(tree_key)) {
22308334c51SBrooks Davis             // Publish the pre-recorded value in the tree to the Lua state,
22408334c51SBrooks Davis             // instead of considering this table key a new inner node.
22508334c51SBrooks Davis             tree.push_lua(tree_key, state);
22608334c51SBrooks Davis         } else {
22708334c51SBrooks Davis             state.push_string("_" + state.to_string(-1));
22808334c51SBrooks Davis             state.insert(-2);
22908334c51SBrooks Davis             state.pop(1);
23008334c51SBrooks Davis 
23108334c51SBrooks Davis             new_table_for_key(state, tree_key);
23208334c51SBrooks Davis 
23308334c51SBrooks Davis             // Duplicate the newly created table and place it deep in the stack
23408334c51SBrooks Davis             // so that the raw_set below leaves us with the return value of this
23508334c51SBrooks Davis             // function at the top of the stack.
23608334c51SBrooks Davis             state.push_value(-1);
23708334c51SBrooks Davis             state.insert(-4);
23808334c51SBrooks Davis 
23908334c51SBrooks Davis             state.raw_set(-3);
24008334c51SBrooks Davis             state.pop(1);
24108334c51SBrooks Davis         }
24208334c51SBrooks Davis     }
24308334c51SBrooks Davis     return 1;
24408334c51SBrooks Davis }
24508334c51SBrooks Davis 
24608334c51SBrooks Davis 
24708334c51SBrooks Davis }  // anonymous namespace
24808334c51SBrooks Davis 
24908334c51SBrooks Davis 
25008334c51SBrooks Davis /// Install wrappers for globals to set values in the configuration tree.
25108334c51SBrooks Davis ///
25208334c51SBrooks Davis /// This function installs wrappers to capture all accesses to global variables.
25308334c51SBrooks Davis /// Such wrappers redirect the reads and writes to the out_tree, which is the
25408334c51SBrooks Davis /// entity that defines what configuration variables exist.
25508334c51SBrooks Davis ///
25608334c51SBrooks Davis /// \param state The Lua state into which to install the wrappers.
25708334c51SBrooks Davis /// \param out_tree The tree with the layout definition and where the
25808334c51SBrooks Davis ///     configuration settings will be collected.
25908334c51SBrooks Davis void
redirect(lutok::state & state,tree & out_tree)26008334c51SBrooks Davis config::redirect(lutok::state& state, tree& out_tree)
26108334c51SBrooks Davis {
26208334c51SBrooks Davis     lutok::stack_cleaner cleaner(state);
26308334c51SBrooks Davis 
26408334c51SBrooks Davis     state.get_global_table();
26508334c51SBrooks Davis     {
26608334c51SBrooks Davis         state.push_string("__index");
26708334c51SBrooks Davis         state.push_cxx_function(redirect_index);
26808334c51SBrooks Davis         state.set_table(-3);
26908334c51SBrooks Davis 
27008334c51SBrooks Davis         state.push_string("__newindex");
27108334c51SBrooks Davis         state.push_cxx_function(redirect_newindex);
27208334c51SBrooks Davis         state.set_table(-3);
27308334c51SBrooks Davis     }
27408334c51SBrooks Davis     state.set_metatable(-1);
27508334c51SBrooks Davis 
27608334c51SBrooks Davis     state.push_value(lutok::registry_index);
27708334c51SBrooks Davis     state.push_string("tree");
27808334c51SBrooks Davis     config::tree** tree = state.new_userdata< config::tree* >();
27908334c51SBrooks Davis     *tree = &out_tree;
28008334c51SBrooks Davis     state.set_table(-3);
28108334c51SBrooks Davis     state.pop(1);
28208334c51SBrooks Davis }
283