/* * Copyright 2016 The Cartographer Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // When a LuaParameterDictionary is constructed, a new Lua state (i.e. an // independent Lua interpreter) is fired up to evaluate the Lua code. The code // is expected to return a Lua table that contains key/value pairs that are the // key/value pairs of our parameter dictionary. // // We keep the Lua interpreter around and the table on the stack and reference // it in the Get* methods instead of moving its contents from Lua into a C++ map // since we can only know in the Get* methods what data type to expect in the // table. // // Some of the methods below documentation the current stack with the following // notation: S: ... #include "cartographer/common/lua_parameter_dictionary.h" #include #include #include #include namespace cartographer { namespace common { namespace { // Replace the string at the top of the stack through a quoted version that Lua // can read back. void QuoteStringOnStack(lua_State* L) { CHECK(lua_isstring(L, -1)) << "Top of stack is not a string value."; int current_index = lua_gettop(L); // S: ... string lua_pushglobaltable(L); // S: ... string globals lua_getfield(L, -1, "string"); // S: ... string globals lua_getfield(L, -1, "format"); // S: ... string globals format lua_pushstring(L, "%q"); // S: ... string globals format "%q" lua_pushvalue(L, current_index); // S: ... string globals // format "%q" string lua_call(L, 2, 1); // S: ... string globals quoted lua_replace(L, current_index); // S: ... quoted globals lua_pop(L, 2); // S: ... quoted } // Sets the given 'dictionary' as the value of the "this" key in Lua's registry // table. void SetDictionaryInRegistry(lua_State* L, LuaParameterDictionary* dictionary) { lua_pushstring(L, "this"); lua_pushlightuserdata(L, dictionary); lua_settable(L, LUA_REGISTRYINDEX); } // Gets the 'dictionary' from the "this" key in Lua's registry table. LuaParameterDictionary* GetDictionaryFromRegistry(lua_State* L) { lua_pushstring(L, "this"); lua_gettable(L, LUA_REGISTRYINDEX); void* return_value = lua_isnil(L, -1) ? nullptr : lua_touserdata(L, -1); lua_pop(L, 1); CHECK(return_value != nullptr); return reinterpret_cast(return_value); } // CHECK()s if a Lua method returned an error. void CheckForLuaErrors(lua_State* L, int status) { CHECK_EQ(status, 0) << lua_tostring(L, -1); } // Returns 'a' if 'condition' is true, else 'b'. int LuaChoose(lua_State* L) { CHECK_EQ(lua_gettop(L), 3) << "choose() takes (condition, a, b)."; CHECK(lua_isboolean(L, 1)) << "condition is not a boolean value."; const bool condition = lua_toboolean(L, 1); if (condition) { lua_pushvalue(L, 2); } else { lua_pushvalue(L, 3); } return 1; } // Pushes a value to the Lua stack. void PushValue(lua_State* L, const int key) { lua_pushinteger(L, key); } void PushValue(lua_State* L, const std::string& key) { lua_pushstring(L, key.c_str()); } // Reads the value with the given 'key' from the Lua dictionary and pushes it to // the top of the stack. template void GetValueFromLuaTable(lua_State* L, const T& key) { PushValue(L, key); lua_rawget(L, -2); } // CHECK() that the topmost parameter on the Lua stack is a table. void CheckTableIsAtTopOfStack(lua_State* L) { CHECK(lua_istable(L, -1)) << "Topmost item on Lua stack is not a table!"; } // Returns true if 'key' is in the table at the top of the Lua stack. template bool HasKeyOfType(lua_State* L, const T& key) { CheckTableIsAtTopOfStack(L); PushValue(L, key); lua_rawget(L, -2); const bool key_not_found = lua_isnil(L, -1); lua_pop(L, 1); // Pop the item again. return !key_not_found; } // Iterates over the integer keys of the table at the top of the stack of 'L• // and pushes the values one by one. 'pop_value' is expected to pop a value and // put them into a C++ container. void GetArrayValues(lua_State* L, const std::function& pop_value) { int idx = 1; while (true) { GetValueFromLuaTable(L, idx); if (lua_isnil(L, -1)) { lua_pop(L, 1); break; } pop_value(); ++idx; } } } // namespace std::unique_ptr LuaParameterDictionary::NonReferenceCounted( const std::string& code, std::unique_ptr file_resolver) { return std::unique_ptr(new LuaParameterDictionary( code, ReferenceCount::NO, std::move(file_resolver))); } LuaParameterDictionary::LuaParameterDictionary( const std::string& code, std::unique_ptr file_resolver) : LuaParameterDictionary(code, ReferenceCount::YES, std::move(file_resolver)) {} LuaParameterDictionary::LuaParameterDictionary( const std::string& code, ReferenceCount reference_count, std::unique_ptr file_resolver) : L_(luaL_newstate()), index_into_reference_table_(-1), file_resolver_(std::move(file_resolver)), reference_count_(reference_count) { CHECK_NOTNULL(L_); SetDictionaryInRegistry(L_, this); luaL_openlibs(L_); lua_register(L_, "choose", LuaChoose); lua_register(L_, "include", LuaInclude); lua_register(L_, "read", LuaRead); CheckForLuaErrors(L_, luaL_loadstring(L_, code.c_str())); CheckForLuaErrors(L_, lua_pcall(L_, 0, 1, 0)); CheckTableIsAtTopOfStack(L_); } LuaParameterDictionary::LuaParameterDictionary( lua_State* const L, ReferenceCount reference_count, std::shared_ptr file_resolver) : L_(lua_newthread(L)), file_resolver_(std::move(file_resolver)), reference_count_(reference_count) { CHECK_NOTNULL(L_); // Make sure this is never garbage collected. CHECK(lua_isthread(L, -1)); index_into_reference_table_ = luaL_ref(L, LUA_REGISTRYINDEX); CHECK(lua_istable(L, -1)) << "Topmost item on Lua stack is not a table!"; lua_xmove(L, L_, 1); // Moves the table and the coroutine over. CheckTableIsAtTopOfStack(L_); } LuaParameterDictionary::~LuaParameterDictionary() { if (reference_count_ == ReferenceCount::YES) { CheckAllKeysWereUsedExactlyOnceAndReset(); } if (index_into_reference_table_ > 0) { luaL_unref(L_, LUA_REGISTRYINDEX, index_into_reference_table_); } else { lua_close(L_); } } std::vector LuaParameterDictionary::GetKeys() const { CheckTableIsAtTopOfStack(L_); std::vector keys; lua_pushnil(L_); // Push the first key while (lua_next(L_, -2) != 0) { lua_pop(L_, 1); // Pop value, keep key. if (!lua_isnumber(L_, -1)) { keys.emplace_back(lua_tostring(L_, -1)); } } return keys; } bool LuaParameterDictionary::HasKey(const std::string& key) const { return HasKeyOfType(L_, key); } std::string LuaParameterDictionary::GetString(const std::string& key) { CheckHasKeyAndReference(key); GetValueFromLuaTable(L_, key); return PopString(Quoted::NO); } std::string LuaParameterDictionary::PopString(Quoted quoted) const { CHECK(lua_isstring(L_, -1)) << "Top of stack is not a string value."; if (quoted == Quoted::YES) { QuoteStringOnStack(L_); } const std::string value = lua_tostring(L_, -1); lua_pop(L_, 1); return value; } double LuaParameterDictionary::GetDouble(const std::string& key) { CheckHasKeyAndReference(key); GetValueFromLuaTable(L_, key); return PopDouble(); } double LuaParameterDictionary::PopDouble() const { CHECK(lua_isnumber(L_, -1)) << "Top of stack is not a number value."; const double value = lua_tonumber(L_, -1); lua_pop(L_, 1); return value; } int LuaParameterDictionary::GetInt(const std::string& key) { CheckHasKeyAndReference(key); GetValueFromLuaTable(L_, key); return PopInt(); } int LuaParameterDictionary::PopInt() const { CHECK(lua_isnumber(L_, -1)) << "Top of stack is not a number value."; const int value = lua_tointeger(L_, -1); lua_pop(L_, 1); return value; } bool LuaParameterDictionary::GetBool(const std::string& key) { CheckHasKeyAndReference(key); GetValueFromLuaTable(L_, key); return PopBool(); } bool LuaParameterDictionary::PopBool() const { CHECK(lua_isboolean(L_, -1)) << "Top of stack is not a boolean value."; const bool value = lua_toboolean(L_, -1); lua_pop(L_, 1); return value; } std::unique_ptr LuaParameterDictionary::GetDictionary( const std::string& key) { CheckHasKeyAndReference(key); GetValueFromLuaTable(L_, key); return PopDictionary(reference_count_); } std::unique_ptr LuaParameterDictionary::PopDictionary( ReferenceCount reference_count) const { CheckTableIsAtTopOfStack(L_); std::unique_ptr value( new LuaParameterDictionary(L_, reference_count, file_resolver_)); // The constructor lua_xmove()s the value, no need to pop it. CheckTableIsAtTopOfStack(L_); return value; } std::string LuaParameterDictionary::DoToString( const std::string& indent) const { std::string result = "{"; bool dictionary_is_empty = true; const auto top_of_stack_to_string = [this, indent, &dictionary_is_empty]() -> std::string { dictionary_is_empty = false; const int value_type = lua_type(L_, -1); switch (value_type) { case LUA_TBOOLEAN: return PopBool() ? "true" : "false"; break; case LUA_TSTRING: return PopString(Quoted::YES); break; case LUA_TNUMBER: { const double value = PopDouble(); if (std::isinf(value)) { return value < 0 ? "-math.huge" : "math.huge"; } else { return std::to_string(value); } } break; case LUA_TTABLE: { std::unique_ptr subdict( PopDictionary(ReferenceCount::NO)); return subdict->DoToString(indent + " "); } break; default: LOG(FATAL) << "Unhandled type " << lua_typename(L_, value_type); } }; // Integer (array) keys. for (int i = 1; i; ++i) { GetValueFromLuaTable(L_, i); if (lua_isnil(L_, -1)) { lua_pop(L_, 1); break; } result.append("\n"); result.append(indent); result.append(" "); result.append(top_of_stack_to_string()); result.append(","); } // String keys. std::vector keys = GetKeys(); if (!keys.empty()) { std::sort(keys.begin(), keys.end()); for (const std::string& key : keys) { GetValueFromLuaTable(L_, key); result.append("\n"); result.append(indent); result.append(" "); result.append(key); result.append(" = "); result.append(top_of_stack_to_string()); result.append(","); } } result.append("\n"); result.append(indent); result.append("}"); if (dictionary_is_empty) { return "{}"; } return result; } std::string LuaParameterDictionary::ToString() const { return DoToString(""); } std::vector LuaParameterDictionary::GetArrayValuesAsDoubles() { std::vector values; GetArrayValues(L_, [&values, this] { values.push_back(PopDouble()); }); return values; } std::vector> LuaParameterDictionary::GetArrayValuesAsDictionaries() { std::vector> values; GetArrayValues(L_, [&values, this] { values.push_back(PopDictionary(reference_count_)); }); return values; } std::vector LuaParameterDictionary::GetArrayValuesAsStrings() { std::vector values; GetArrayValues(L_, [&values, this] { values.push_back(PopString(Quoted::NO)); }); return values; } void LuaParameterDictionary::CheckHasKey(const std::string& key) const { CHECK(HasKey(key)) << "Key '" << key << "' not in dictionary:\n" << ToString(); } void LuaParameterDictionary::CheckHasKeyAndReference(const std::string& key) { CheckHasKey(key); reference_counts_[key]++; } void LuaParameterDictionary::CheckAllKeysWereUsedExactlyOnceAndReset() { for (const auto& key : GetKeys()) { CHECK_EQ(1, reference_counts_.count(key)) << "Key '" << key << "' was used the wrong number of times."; CHECK_EQ(1, reference_counts_.at(key)) << "Key '" << key << "' was used the wrong number of times."; } reference_counts_.clear(); } int LuaParameterDictionary::GetNonNegativeInt(const std::string& key) { const int temp = GetInt(key); // Will increase reference count. CHECK_GE(temp, 0) << temp << " is negative."; return temp; } // Lua function to run a script in the current Lua context. Takes the filename // as its only argument. int LuaParameterDictionary::LuaInclude(lua_State* L) { CHECK_EQ(lua_gettop(L), 1); CHECK(lua_isstring(L, -1)) << "include takes a filename."; LuaParameterDictionary* parameter_dictionary = GetDictionaryFromRegistry(L); const std::string basename = lua_tostring(L, -1); const std::string filename = parameter_dictionary->file_resolver_->GetFullPathOrDie(basename); if (std::find(parameter_dictionary->included_files_.begin(), parameter_dictionary->included_files_.end(), filename) != parameter_dictionary->included_files_.end()) { std::string error_msg = "Tried to include " + filename + " twice. Already included files in order of inclusion: "; for (const std::string& filename : parameter_dictionary->included_files_) { error_msg.append(filename); error_msg.append("\n"); } LOG(FATAL) << error_msg; } parameter_dictionary->included_files_.push_back(filename); lua_pop(L, 1); CHECK_EQ(lua_gettop(L), 0); const std::string content = parameter_dictionary->file_resolver_->GetFileContentOrDie(basename); CheckForLuaErrors( L, luaL_loadbuffer(L, content.c_str(), content.size(), filename.c_str())); CheckForLuaErrors(L, lua_pcall(L, 0, LUA_MULTRET, 0)); return lua_gettop(L); } // Lua function to read a file into a string. int LuaParameterDictionary::LuaRead(lua_State* L) { CHECK_EQ(lua_gettop(L), 1); CHECK(lua_isstring(L, -1)) << "read takes a filename."; LuaParameterDictionary* parameter_dictionary = GetDictionaryFromRegistry(L); const std::string file_content = parameter_dictionary->file_resolver_->GetFileContentOrDie( lua_tostring(L, -1)); lua_pushstring(L, file_content.c_str()); return 1; } } // namespace common } // namespace cartographer