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