Home | History | Annotate | Line # | Download | only in config
      1      1.1  jmmv // Copyright 2012 Google Inc.
      2      1.1  jmmv // All rights reserved.
      3      1.1  jmmv //
      4      1.1  jmmv // Redistribution and use in source and binary forms, with or without
      5      1.1  jmmv // modification, are permitted provided that the following conditions are
      6      1.1  jmmv // met:
      7      1.1  jmmv //
      8      1.1  jmmv // * Redistributions of source code must retain the above copyright
      9      1.1  jmmv //   notice, this list of conditions and the following disclaimer.
     10      1.1  jmmv // * Redistributions in binary form must reproduce the above copyright
     11      1.1  jmmv //   notice, this list of conditions and the following disclaimer in the
     12      1.1  jmmv //   documentation and/or other materials provided with the distribution.
     13      1.1  jmmv // * Neither the name of Google Inc. nor the names of its contributors
     14      1.1  jmmv //   may be used to endorse or promote products derived from this software
     15      1.1  jmmv //   without specific prior written permission.
     16      1.1  jmmv //
     17      1.1  jmmv // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     18      1.1  jmmv // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     19      1.1  jmmv // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     20      1.1  jmmv // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     21      1.1  jmmv // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     22      1.1  jmmv // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     23      1.1  jmmv // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     24      1.1  jmmv // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     25      1.1  jmmv // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26      1.1  jmmv // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     27      1.1  jmmv // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28      1.1  jmmv 
     29      1.1  jmmv #include "utils/config/lua_module.hpp"
     30      1.1  jmmv 
     31      1.1  jmmv #include <lutok/stack_cleaner.hpp>
     32      1.1  jmmv #include <lutok/state.ipp>
     33      1.1  jmmv 
     34      1.1  jmmv #include "utils/config/exceptions.hpp"
     35      1.1  jmmv #include "utils/config/tree.ipp"
     36      1.1  jmmv 
     37      1.1  jmmv namespace config = utils::config;
     38      1.1  jmmv 
     39      1.1  jmmv 
     40      1.1  jmmv namespace {
     41      1.1  jmmv 
     42      1.1  jmmv 
     43      1.1  jmmv /// Gets the tree singleton stored in the Lua state.
     44      1.1  jmmv ///
     45  1.1.1.2  jmmv /// \param state The Lua state.  The registry must contain a key named
     46      1.1  jmmv ///    "tree" with a pointer to the singleton.
     47      1.1  jmmv ///
     48      1.1  jmmv /// \return A reference to the tree associated with the Lua state.
     49      1.1  jmmv ///
     50      1.1  jmmv /// \throw syntax_error If the tree cannot be located.
     51      1.1  jmmv config::tree&
     52      1.1  jmmv get_global_tree(lutok::state& state)
     53      1.1  jmmv {
     54      1.1  jmmv     lutok::stack_cleaner cleaner(state);
     55      1.1  jmmv 
     56  1.1.1.2  jmmv     state.push_value(lutok::registry_index);
     57  1.1.1.2  jmmv     state.push_string("tree");
     58  1.1.1.2  jmmv     state.get_table(-2);
     59  1.1.1.2  jmmv     if (state.is_nil())
     60      1.1  jmmv         throw config::syntax_error("Cannot find tree singleton; global state "
     61      1.1  jmmv                                    "corrupted?");
     62  1.1.1.2  jmmv     config::tree& tree = **state.to_userdata< config::tree* >();
     63  1.1.1.2  jmmv     state.pop(1);
     64  1.1.1.2  jmmv     return tree;
     65      1.1  jmmv }
     66      1.1  jmmv 
     67      1.1  jmmv 
     68      1.1  jmmv /// Gets a fully-qualified tree key from the state.
     69      1.1  jmmv ///
     70      1.1  jmmv /// \param state The Lua state.
     71      1.1  jmmv /// \param table_index An index to the Lua stack pointing to the table being
     72      1.1  jmmv ///     accessed.  If this table contains a tree_key metadata property, this is
     73      1.1  jmmv ///     considered to be the prefix of the tree key.
     74      1.1  jmmv /// \param field_index An index to the Lua stack pointing to the entry
     75      1.1  jmmv ///     containing the name of the field being indexed.
     76      1.1  jmmv ///
     77      1.1  jmmv /// \return A dotted key.
     78      1.1  jmmv ///
     79      1.1  jmmv /// \throw invalid_key_error If the name of the key is invalid.
     80      1.1  jmmv static std::string
     81      1.1  jmmv get_tree_key(lutok::state& state, const int table_index, const int field_index)
     82      1.1  jmmv {
     83      1.1  jmmv     PRE(state.is_string(field_index));
     84      1.1  jmmv     const std::string field = state.to_string(field_index);
     85      1.1  jmmv     if (!field.empty() && field[0] == '_')
     86      1.1  jmmv         throw config::invalid_key_error(
     87      1.1  jmmv             F("Configuration key cannot have an underscore as a prefix; "
     88      1.1  jmmv               "found %s") % field);
     89      1.1  jmmv 
     90      1.1  jmmv     std::string tree_key;
     91      1.1  jmmv     if (state.get_metafield(table_index, "tree_key")) {
     92      1.1  jmmv         tree_key = state.to_string(-1) + "." + state.to_string(field_index - 1);
     93      1.1  jmmv         state.pop(1);
     94      1.1  jmmv     } else
     95      1.1  jmmv         tree_key = state.to_string(field_index);
     96      1.1  jmmv     return tree_key;
     97      1.1  jmmv }
     98      1.1  jmmv 
     99      1.1  jmmv 
    100      1.1  jmmv static int redirect_newindex(lutok::state&);
    101      1.1  jmmv static int redirect_index(lutok::state&);
    102      1.1  jmmv 
    103      1.1  jmmv 
    104      1.1  jmmv /// Creates a table for a new configuration inner node.
    105      1.1  jmmv ///
    106      1.1  jmmv /// \post state(-1) Contains the new table.
    107      1.1  jmmv ///
    108      1.1  jmmv /// \param state The Lua state in which to push the table.
    109      1.1  jmmv /// \param tree_key The key to which the new table corresponds.
    110      1.1  jmmv static void
    111      1.1  jmmv new_table_for_key(lutok::state& state, const std::string& tree_key)
    112      1.1  jmmv {
    113      1.1  jmmv     state.new_table();
    114      1.1  jmmv     {
    115      1.1  jmmv         state.new_table();
    116      1.1  jmmv         {
    117      1.1  jmmv             state.push_string("__index");
    118      1.1  jmmv             state.push_cxx_function(redirect_index);
    119      1.1  jmmv             state.set_table(-3);
    120      1.1  jmmv 
    121      1.1  jmmv             state.push_string("__newindex");
    122      1.1  jmmv             state.push_cxx_function(redirect_newindex);
    123      1.1  jmmv             state.set_table(-3);
    124      1.1  jmmv 
    125      1.1  jmmv             state.push_string("tree_key");
    126      1.1  jmmv             state.push_string(tree_key);
    127      1.1  jmmv             state.set_table(-3);
    128      1.1  jmmv         }
    129      1.1  jmmv         state.set_metatable(-2);
    130      1.1  jmmv     }
    131      1.1  jmmv }
    132      1.1  jmmv 
    133      1.1  jmmv 
    134      1.1  jmmv /// Sets the value of an configuration node.
    135      1.1  jmmv ///
    136      1.1  jmmv /// \pre state(-3) The table to index.  If this is not _G, then the table
    137      1.1  jmmv ///     metadata must contain a tree_key property describing the path to
    138      1.1  jmmv ///     current level.
    139      1.1  jmmv /// \pre state(-2) The field to index into the table.  Must be a string.
    140      1.1  jmmv /// \pre state(-1) The value to set the indexed table field to.
    141      1.1  jmmv ///
    142      1.1  jmmv /// \param state The Lua state in which to operate.
    143      1.1  jmmv ///
    144      1.1  jmmv /// \return The number of result values on the Lua stack; always 0.
    145      1.1  jmmv ///
    146      1.1  jmmv /// \throw invalid_key_error If the provided key is invalid.
    147      1.1  jmmv /// \throw unknown_key_error If the key cannot be located.
    148      1.1  jmmv /// \throw value_error If the value has an unsupported type or cannot be
    149      1.1  jmmv ///     set on the key, or if the input table or index are invalid.
    150      1.1  jmmv static int
    151      1.1  jmmv redirect_newindex(lutok::state& state)
    152      1.1  jmmv {
    153      1.1  jmmv     if (!state.is_table(-3))
    154      1.1  jmmv         throw config::value_error("Indexed object is not a table");
    155      1.1  jmmv     if (!state.is_string(-2))
    156      1.1  jmmv         throw config::value_error("Invalid field in configuration object "
    157      1.1  jmmv                                   "reference; must be a string");
    158      1.1  jmmv 
    159      1.1  jmmv     const std::string dotted_key = get_tree_key(state, -3, -2);
    160      1.1  jmmv     try {
    161      1.1  jmmv         config::tree& tree = get_global_tree(state);
    162      1.1  jmmv         tree.set_lua(dotted_key, state, -1);
    163      1.1  jmmv     } catch (const config::value_error& e) {
    164      1.1  jmmv         throw config::value_error(F("Invalid value for key '%s' (%s)") %
    165      1.1  jmmv                                   dotted_key % e.what());
    166      1.1  jmmv     }
    167      1.1  jmmv 
    168      1.1  jmmv     // Now really set the key in the Lua table, but prevent direct accesses from
    169      1.1  jmmv     // the user by prefixing it.  We do this to ensure that re-setting the same
    170      1.1  jmmv     // key of the tree results in a call to __newindex instead of __index.
    171      1.1  jmmv     state.push_string("_" + state.to_string(-2));
    172      1.1  jmmv     state.push_value(-2);
    173      1.1  jmmv     state.raw_set(-5);
    174      1.1  jmmv 
    175      1.1  jmmv     return 0;
    176      1.1  jmmv }
    177      1.1  jmmv 
    178      1.1  jmmv 
    179      1.1  jmmv /// Indexes a configuration node.
    180      1.1  jmmv ///
    181      1.1  jmmv /// \pre state(-3) The table to index.  If this is not _G, then the table
    182      1.1  jmmv ///     metadata must contain a tree_key property describing the path to
    183      1.1  jmmv ///     current level.  If the field does not exist, a new table is created.
    184      1.1  jmmv /// \pre state(-1) The field to index into the table.  Must be a string.
    185      1.1  jmmv ///
    186      1.1  jmmv /// \param state The Lua state in which to operate.
    187      1.1  jmmv ///
    188      1.1  jmmv /// \return The number of result values on the Lua stack; always 1.
    189      1.1  jmmv ///
    190      1.1  jmmv /// \throw value_error If the input table or index are invalid.
    191      1.1  jmmv static int
    192      1.1  jmmv redirect_index(lutok::state& state)
    193      1.1  jmmv {
    194      1.1  jmmv     if (!state.is_table(-2))
    195      1.1  jmmv         throw config::value_error("Indexed object is not a table");
    196      1.1  jmmv     if (!state.is_string(-1))
    197      1.1  jmmv         throw config::value_error("Invalid field in configuration object "
    198      1.1  jmmv                                   "reference; must be a string");
    199      1.1  jmmv 
    200      1.1  jmmv     // Query if the key has already been set by a call to redirect_newindex.
    201      1.1  jmmv     state.push_string("_" + state.to_string(-1));
    202      1.1  jmmv     state.raw_get(-3);
    203      1.1  jmmv     if (!state.is_nil(-1))
    204      1.1  jmmv         return 1;
    205      1.1  jmmv     state.pop(1);
    206      1.1  jmmv 
    207      1.1  jmmv     state.push_value(-1);  // Duplicate the field name.
    208      1.1  jmmv     state.raw_get(-3);  // Get table[field] to see if it's defined.
    209      1.1  jmmv     if (state.is_nil(-1)) {
    210      1.1  jmmv         state.pop(1);
    211      1.1  jmmv 
    212      1.1  jmmv         // The stack is now the same as when we entered the function, but we
    213      1.1  jmmv         // know that the field is undefined and thus have to create a new
    214      1.1  jmmv         // configuration table.
    215      1.1  jmmv         INV(state.is_table(-2));
    216      1.1  jmmv         INV(state.is_string(-1));
    217      1.1  jmmv 
    218      1.1  jmmv         const config::tree& tree = get_global_tree(state);
    219      1.1  jmmv         const std::string tree_key = get_tree_key(state, -2, -1);
    220      1.1  jmmv         if (tree.is_set(tree_key)) {
    221      1.1  jmmv             // Publish the pre-recorded value in the tree to the Lua state,
    222      1.1  jmmv             // instead of considering this table key a new inner node.
    223      1.1  jmmv             tree.push_lua(tree_key, state);
    224      1.1  jmmv         } else {
    225      1.1  jmmv             state.push_string("_" + state.to_string(-1));
    226      1.1  jmmv             state.insert(-2);
    227      1.1  jmmv             state.pop(1);
    228      1.1  jmmv 
    229      1.1  jmmv             new_table_for_key(state, tree_key);
    230      1.1  jmmv 
    231      1.1  jmmv             // Duplicate the newly created table and place it deep in the stack
    232      1.1  jmmv             // so that the raw_set below leaves us with the return value of this
    233      1.1  jmmv             // function at the top of the stack.
    234      1.1  jmmv             state.push_value(-1);
    235      1.1  jmmv             state.insert(-4);
    236      1.1  jmmv 
    237      1.1  jmmv             state.raw_set(-3);
    238      1.1  jmmv             state.pop(1);
    239      1.1  jmmv         }
    240      1.1  jmmv     }
    241      1.1  jmmv     return 1;
    242      1.1  jmmv }
    243      1.1  jmmv 
    244      1.1  jmmv 
    245      1.1  jmmv }  // anonymous namespace
    246      1.1  jmmv 
    247      1.1  jmmv 
    248      1.1  jmmv /// Install wrappers for globals to set values in the configuration tree.
    249      1.1  jmmv ///
    250      1.1  jmmv /// This function installs wrappers to capture all accesses to global variables.
    251      1.1  jmmv /// Such wrappers redirect the reads and writes to the out_tree, which is the
    252      1.1  jmmv /// entity that defines what configuration variables exist.
    253      1.1  jmmv ///
    254      1.1  jmmv /// \param state The Lua state into which to install the wrappers.
    255      1.1  jmmv /// \param out_tree The tree with the layout definition and where the
    256      1.1  jmmv ///     configuration settings will be collected.
    257      1.1  jmmv void
    258      1.1  jmmv config::redirect(lutok::state& state, tree& out_tree)
    259      1.1  jmmv {
    260      1.1  jmmv     lutok::stack_cleaner cleaner(state);
    261      1.1  jmmv 
    262  1.1.1.2  jmmv     state.get_global_table();
    263      1.1  jmmv     {
    264      1.1  jmmv         state.push_string("__index");
    265      1.1  jmmv         state.push_cxx_function(redirect_index);
    266      1.1  jmmv         state.set_table(-3);
    267      1.1  jmmv 
    268      1.1  jmmv         state.push_string("__newindex");
    269      1.1  jmmv         state.push_cxx_function(redirect_newindex);
    270      1.1  jmmv         state.set_table(-3);
    271      1.1  jmmv     }
    272  1.1.1.2  jmmv     state.set_metatable(-1);
    273  1.1.1.2  jmmv 
    274  1.1.1.2  jmmv     state.push_value(lutok::registry_index);
    275  1.1.1.2  jmmv     state.push_string("tree");
    276  1.1.1.2  jmmv     config::tree** tree = state.new_userdata< config::tree* >();
    277  1.1.1.2  jmmv     *tree = &out_tree;
    278  1.1.1.2  jmmv     state.set_table(-3);
    279  1.1.1.2  jmmv     state.pop(1);
    280      1.1  jmmv }
    281