void LuaMatUniforms::Parse(lua_State* L, const int tableIdx) { auto uniformAndNames = GetUniformsAndPossibleNames(); decltype(uniformAndNames) uniformAndNamesLower; for (auto& p: uniformAndNames) { uniformAndNamesLower[StringToLower(p.first)] = p.second; } for (lua_pushnil(L); lua_next(L, tableIdx) != 0; lua_pop(L, 1)) { if (!lua_israwstring(L, -2) || !lua_isnumber(L, -1)) continue; // get the lua table key // and remove the "loc" at the end (cameraPosLoc -> camerapos) std::string uniformLocStr = luaL_tosstring(L, -2); StringToLowerInPlace(uniformLocStr); if (StringEndsWith(uniformLocStr, "loc")) { uniformLocStr.resize(uniformLocStr.size() - 3); } const auto uniformLocIt = uniformAndNamesLower.find(uniformLocStr); if (uniformLocIt == uniformAndNamesLower.end()) { LOG_L(L_WARNING, "LuaMaterial: unknown uniform \"%s\"", lua_tostring(L, -2)); continue; } uniformLocIt->second->loc = static_cast<GLint>(lua_tonumber(L, -1)); } }
bool CLuaHandleSynced::LightCopyTable(int dstIndex, int srcIndex) { // use positive indices if (dstIndex < 0) { dstIndex = lua_gettop(L) + dstIndex + 1; } if (srcIndex < 0) { srcIndex = lua_gettop(L) + srcIndex + 1; } if (!lua_istable(L, dstIndex) || !lua_istable(L, srcIndex)) { return false; } for (lua_pushnil(L); lua_next(L, srcIndex) != 0; lua_pop(L, 1)) { if (!lua_israwstring(L, -2)) { // key must be a string continue; } if (lua_isstring(L, -1) || lua_isnumber(L, -1) || lua_isboolean(L, -1) || lua_isfunction(L, -1)) { lua_pushvalue(L, -2); // copy the key lua_pushvalue(L, -2); // copy the value lua_rawset(L, dstIndex); } } return true; }
static void ParseTexture(lua_State* L, int index, LuaMatTexture& texUnit) { if (index < 0) index = lua_gettop(L) + index + 1; if (lua_isstring(L, index)) { LuaOpenGLUtils::ParseTextureImage(L, texUnit, lua_tostring(L, index)); texUnit.enable = true; return; } if (!lua_istable(L, index)) return; const int table = (index > 0) ? index : (lua_gettop(L) + index + 1); for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { if (!lua_israwstring(L, -2)) continue; const string key = StringToLower(lua_tostring(L, -2)); if (key == "tex") { LuaOpenGLUtils::ParseTextureImage(L, texUnit, lua_tostring(L, -1)); texUnit.enable = true; continue; } if (key == "enable") { texUnit.enable = lua_isboolean(L, -1) && lua_toboolean(L, -1); continue; } } }
int LuaFonts::meta_index(lua_State* L) { //! first check if there is a function luaL_getmetatable(L, "Font"); lua_pushvalue(L, 2); lua_rawget(L, -2); if (!lua_isnil(L, -1)) { return 1; } lua_pop(L, 1); //! couldn't find a function, so check properties CglFont* font = tofont(L, 1); if (lua_israwstring(L, 2)) { const string key = lua_tostring(L, 2); if (key == "size") { lua_pushnumber(L, font->GetSize()); return 1; //} else if (key == "outlinecolor") { //} else if (key == "textcolor") { } else if (key == "path") { const std::string& filepath = font->GetFilePath(); lua_pushlstring(L, filepath.c_str(), filepath.length()); return 1; } else if (key == "height" || key == "lineheight") { lua_pushnumber(L, font->GetLineHeight()); return 1; } else if (key == "descender") { lua_pushnumber(L, font->GetDescender()); return 1; } else if (key == "outlinewidth") { lua_pushnumber(L, font->GetOutlineWidth()); return 1; } else if (key == "outlineweight") { lua_pushnumber(L, font->GetOutlineWeight()); return 1; } else if (key == "family") { const std::string& family = font->GetFamily(); lua_pushlstring(L, family.c_str(), family.length()); return 1; } else if (key == "style") { const std::string& style = font->GetStyle(); lua_pushlstring(L, style.c_str(), style.length()); return 1; } else if (key == "texturewidth") { lua_pushnumber(L, font->GetTexWidth()); return 1; } else if (key == "textureheight") { lua_pushnumber(L, font->GetTexHeight()); return 1; } } return 0; }
static int IndexHook(lua_State* L) { CLuaHandle** addr = (CLuaHandle**) lua_touserdata(L, lua_upvalueindex(1)); if (!lua_israwstring(L, 2)) { return 0; // missing string name for function } lua_pushlightuserdata(L, addr); lua_pushstring(L, lua_tostring(L, 2)); lua_pushcclosure(L, HandleXCall, 2); return 1; }
static bool LowerKeysReal(lua_State* L, int depth) { lua_checkstack(L, lowerKeysTable + 8 + (depth * 3)); const int table = lua_gettop(L); if (LowerKeysCheck(L, table)) { return true; } // a new table for changed values const int changed = table + 1; lua_newtable(L); for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { if (lua_istable(L, -1)) { LowerKeysReal(L, depth + 1); } if (lua_israwstring(L, -2)) { const string rawKey = lua_tostring(L, -2); const string lowerKey = StringToLower(rawKey); if (rawKey != lowerKey) { // removed the mixed case entry lua_pushvalue(L, -2); // the key lua_pushnil(L); lua_rawset(L, table); // does the lower case key alread exist in the table? lua_pushsstring(L, lowerKey); lua_rawget(L, table); if (lua_isnil(L, -1)) { // lower case does not exist, add it to the changed table lua_pushsstring(L, lowerKey); lua_pushvalue(L, -3); // the value lua_rawset(L, changed); } lua_pop(L, 1); } } } // copy the changed values into the table for (lua_pushnil(L); lua_next(L, changed) != 0; lua_pop(L, 1)) { lua_pushvalue(L, -2); // copy the key to the top lua_pushvalue(L, -2); // copy the value to the top lua_rawset(L, table); } lua_pop(L, 1); // pop the changed table return true; }
bool LuaTable::GetKeys(vector<string>& data) const { if (!PushTable()) { return false; } const int table = lua_gettop(L); for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { if (lua_israwstring(L, -2)) { const string value = lua_tostring(L, -2); data.push_back(value); } } std::sort(data.begin(), data.end()); return true; }
bool LuaTable::GetMap(map<string, string>& data) const { if (!PushTable()) { return false; } const int table = lua_gettop(L); for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { if (lua_israwstring(L, -2) && lua_isstring(L, -1)) { const string key = lua_tostring(L, -2); const string value = lua_tostring(L, -1); data[key] = value; } } return true; }
/*- Logs a msg to the logfile / console @param loglevel loglevel that will be used for the message @param msg string to be logged @fn Spring.Log(string logsection, int loglevel, ...) @fn Spring.Log(string logsection, string loglevel, ...) */ int LuaUtils::Log(lua_State* L) { const int args = lua_gettop(L); // number of arguments if (args < 2) return luaL_error(L, "Incorrect arguments to Spring.Log(logsection, loglevel, ...)"); if (args < 3) return 0; const std::string section = luaL_checkstring(L, 1); int loglevel; if (lua_israwnumber(L, 2)) { loglevel = lua_tonumber(L, 2); } else if (lua_israwstring(L, 2)) { std::string loglvlstr = lua_tostring(L, 2); StringToLowerInPlace(loglvlstr); if (loglvlstr == "debug") { loglevel = LOG_LEVEL_DEBUG; } else if (loglvlstr == "info") { loglevel = LOG_LEVEL_INFO; } else if (loglvlstr == "warning") { loglevel = LOG_LEVEL_WARNING; } else if (loglvlstr == "error") { loglevel = LOG_LEVEL_ERROR; } else if (loglvlstr == "fatal") { loglevel = LOG_LEVEL_FATAL; } else { return luaL_error(L, "Incorrect arguments to Spring.Log(logsection, loglevel, ...)"); } } else { return luaL_error(L, "Incorrect arguments to Spring.Log(logsection, loglevel, ...)"); } const std::string msg = getprintf_msg(L, 3); LOG_SI(section.c_str(), loglevel, "%s", msg.c_str()); return 0; }
int CLuaRules::SetRulesInfoMap(lua_State* L) { assert(luaRules != NULL); if (!lua_istable(L, 1)) { luaL_error(L, "Incorrect arguments to SetRulesInfoMap(teamID)"); } map<string, string>& infoMap = luaRules->infoMap; infoMap.clear(); const int table = 1; for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { if (lua_israwstring(L, -2) && lua_isstring(L, -1)) { const string key = lua_tostring(L, -2); const string value = lua_tostring(L, -1); infoMap[key] = value; } } lua_pushnumber(L, infoMap.size()); return 2; }
static int HandleXCall(lua_State* L) { const int addrIndex = lua_upvalueindex(1); const int nameIndex = lua_upvalueindex(2); if (!lua_isuserdata(L, addrIndex) || !lua_israwstring(L, nameIndex)) { luaL_error(L, "Bad function name type"); } CLuaHandle** addr = (CLuaHandle**) lua_touserdata(L, addrIndex); if (*addr == NULL) { return 0; // handle is not currently active } CLuaHandle* lh = *addr; const char* funcName = lua_tostring(L, nameIndex); return lh->SyncedXCall(L, funcName); }
int LuaUtils::ParseFacing(lua_State* L, const char* caller, int index) { if (lua_israwnumber(L, index)) { return std::max(0, std::min(3, lua_toint(L, index))); } else if (lua_israwstring(L, index)) { const string dir = StringToLower(lua_tostring(L, index)); if (dir == "s") { return 0; } if (dir == "e") { return 1; } if (dir == "n") { return 2; } if (dir == "w") { return 3; } if (dir == "south") { return 0; } if (dir == "east") { return 1; } if (dir == "north") { return 2; } if (dir == "west") { return 3; } luaL_error(L, "%s(): bad facing string", caller); } luaL_error(L, "%s(): bad facing parameter", caller); return 0; }
static int CallHook(lua_State* L) { CLuaHandle** addr = (CLuaHandle**) lua_touserdata(L, lua_upvalueindex(1)); const int args = lua_gettop(L); // arg 1 is the table if (args <= 1) { // is the handle currently active? lua_pushboolean(L, (*addr != NULL)); return 1; } else if ((args >= 2) && lua_israwstring(L, 2)) { // see if the specified function exists const string funcName = lua_tostring(L, 2); CLuaHandle* lh = *addr; if (lh == NULL) { return 0; // not running } lua_pushboolean(L, lh->HasSyncedXCall(funcName)); return 1; } return 0; }
void CLuaRules::CreateRulesParams(lua_State* L, const char* caller, int offset, vector<float>& params, map<string, int>& paramsMap) { const int table = offset + 1; if (!lua_istable(L, table)) { luaL_error(L, "Incorrect arguments to %s()", caller); } params.clear(); paramsMap.clear(); for (int i = 1; /* no test */; i++) { lua_rawgeti(L, table, i); if (lua_isnil(L, -1)) { lua_pop(L, 1); return; } else if (lua_israwnumber(L, -1)) { const float value = lua_tofloat(L, -1); params.push_back(value); } else if (lua_istable(L, -1)) { lua_pushnil(L); if (lua_next(L, -2)) { if (lua_israwstring(L, -2) && lua_isnumber(L, -1)) { const string name = lua_tostring(L, -2); const float value = lua_tonumber(L, -1); paramsMap[name] = params.size(); params.push_back(value); } lua_pop(L, 2); } } lua_pop(L, 1); } return; }
static int SetMoveTypeData(lua_State* L, AMoveType* moveType, const char* caller) { int numAssignedValues = 0; if (moveType == NULL) { luaL_error(L, "[%s] unit %d has incompatible movetype for %s", __FUNCTION__, lua_tonumber(L, 1), caller); return numAssignedValues; } switch (lua_gettop(L)) { case 2: { // two args (unitID, {"key1" = (number | bool) value1, ...}) const int tableIdx = 2; if (lua_istable(L, tableIdx)) { for (lua_pushnil(L); lua_next(L, tableIdx) != 0; lua_pop(L, 1)) { if (lua_israwstring(L, -2) && SetMoveTypeValue(L, moveType, -2, -1)) { numAssignedValues++; } else { LOG_L(L_WARNING, "[%s] incompatible movetype key for %s", __FUNCTION__, caller); } } } } break; case 3: { // three args (unitID, "key", (number | bool) value) if (lua_isstring(L, 2) && SetMoveTypeValue(L, moveType, 2, 3)) { numAssignedValues++; } else { LOG_L(L_WARNING, "[%s] incompatible movetype key for %s", __FUNCTION__, caller); } } break; } lua_pushnumber(L, numAssignedValues); return 1; }
void CLuaRules::SetRulesParam(lua_State* L, const char* caller, int offset, vector<float>& params, map<string, int>& paramsMap) { const int index = offset + 1; const int valIndex = offset + 2; int pIndex = -1; if (lua_israwnumber(L, index)) { pIndex = lua_toint(L, index) - 1; } else if (lua_israwstring(L, index)) { const string pName = lua_tostring(L, index); map<string, int>::const_iterator it = paramsMap.find(pName); if (it != paramsMap.end()) { pIndex = it->second; } else { // create a new parameter pIndex = params.size(); paramsMap[pName] = params.size(); params.push_back(0.0f); // dummy value } } else { luaL_error(L, "Incorrect arguments to %s()", caller); } if ((pIndex < 0) || (pIndex >= (int)params.size())) { return; } if (!lua_isnumber(L, valIndex)) { luaL_error(L, "Incorrect arguments to %s()", caller); } params[pIndex] = lua_tofloat(L, valIndex); return; }
int LuaSyncedMoveCtrl::SetGunshipMoveTypeData(lua_State *L) { CHoverAirMoveType* moveType = ParseMoveType<CHoverAirMoveType>(L, __FUNCTION__, 1); if (moveType == NULL) { luaL_error(L, "Unit does not have a compatible MoveType"); } const int args = lua_gettop(L); // number of arguments if (args == 3 && lua_isstring(L, 2)) { // a single value SetSingleHoverAirMoveTypeValue(L, 2, 3, moveType); } else if (args == 2 && lua_istable(L, 2)) { // a table of values const int table = 2; for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { if (lua_israwstring(L, -2)) { SetSingleHoverAirMoveTypeValue(L, -2, -1, moveType); } } } return 0; }
void LuaMaterial::Parse( lua_State* L, const int tableIdx, std::function<void(lua_State*, int, LuaMatShader&)> ParseShader, std::function<void(lua_State*, int, LuaMatTexture&)> ParseTexture, std::function<GLuint(lua_State*, int)> ParseDisplayList ) { for (lua_pushnil(L); lua_next(L, tableIdx) != 0; lua_pop(L, 1)) { if (!lua_israwstring(L, -2)) continue; const std::string key = StringToLower(lua_tostring(L, -2)); // uniforms if (key.find("uniforms") != std::string::npos) { if (!lua_istable(L, -1)) continue; if (key.find("standard") != std::string::npos) { uniforms[LuaMatShader::LUASHADER_PASS_FWD].Parse(L, lua_gettop(L)); continue; } if (key.find("deferred") != std::string::npos) { uniforms[LuaMatShader::LUASHADER_PASS_DFR].Parse(L, lua_gettop(L)); continue; } // fallback uniforms[LuaMatShader::LUASHADER_PASS_FWD].Parse(L, lua_gettop(L)); continue; } // shaders if (key.find("shader") != std::string::npos) { if (key.find("standard") != std::string::npos) { ParseShader(L, -1, shaders[LuaMatShader::LUASHADER_PASS_FWD]); continue; } if (key.find("deferred") != std::string::npos) { ParseShader(L, -1, shaders[LuaMatShader::LUASHADER_PASS_DFR]); continue; } // fallback ParseShader(L, -1, shaders[LuaMatShader::LUASHADER_PASS_FWD]); continue; } // textures if (key.substr(0, 7) == "texunit") { if (key.size() < 8) continue; if (key[7] == 's') { // "texunits" = {[0] = string|table, ...} if (!lua_istable(L, -1)) continue; const int texTable = lua_gettop(L); for (lua_pushnil(L); lua_next(L, texTable) != 0; lua_pop(L, 1)) { if (!lua_israwnumber(L, -2)) continue; const unsigned int texUnit = lua_toint(L, -2); if (texUnit >= LuaMatTexture::maxTexUnits) continue; ParseTexture(L, -1, textures[texUnit]); } } else { // "texunitX" = string|table const unsigned int texUnit = atoi(key.c_str() + 7); if (texUnit >= LuaMatTexture::maxTexUnits) continue; ParseTexture(L, -1, textures[texUnit]); } continue; } // dlists if (key == "prelist") { preList = ParseDisplayList(L, -1); continue; } if (key == "postlist") { postList = ParseDisplayList(L, -1); continue; } // misc if (key == "order") { order = luaL_checkint(L, -1); continue; } if (key == "culling") { if (lua_isnumber(L, -1)) cullingMode = (GLenum)lua_tonumber(L, -1); continue; } if (key == "usecamera") { useCamera = lua_isboolean(L, -1) && lua_toboolean(L, -1); continue; } LOG_L(L_WARNING, "LuaMaterial: incorrect key \"%s\"", key.c_str()); } }
int CLuaHandleSynced::CallAsTeam(lua_State* L) { const int args = lua_gettop(L); if ((args < 2) || !lua_isfunction(L, 2)) { luaL_error(L, "Incorrect arguments to CallAsTeam()"); } // save the current access const bool prevFullCtrl = CLuaHandle::GetHandleFullCtrl(L); const bool prevFullRead = CLuaHandle::GetHandleFullRead(L); const int prevCtrlTeam = CLuaHandle::GetHandleCtrlTeam(L); const int prevReadTeam = CLuaHandle::GetHandleReadTeam(L); const int prevReadAllyTeam = CLuaHandle::GetHandleReadAllyTeam(L); const int prevSelectTeam = CLuaHandle::GetHandleSelectTeam(L); // parse the new access if (lua_isnumber(L, 1)) { const int teamID = lua_toint(L, 1); if ((teamID < CEventClient::MinSpecialTeam) || (teamID >= teamHandler->ActiveTeams())) { luaL_error(L, "Bad teamID in SetCtrlTeam"); } // ctrl CLuaHandle::SetHandleCtrlTeam(L, teamID); CLuaHandle::SetHandleFullCtrl(L, prevCtrlTeam == CEventClient::AllAccessTeam); // read CLuaHandle::SetHandleReadTeam(L, teamID); CLuaHandle::SetHandleReadAllyTeam(L, (teamID < 0) ? teamID : teamHandler->AllyTeam(teamID)); CLuaHandle::SetHandleFullRead(L, prevReadAllyTeam == CEventClient::AllAccessTeam); // select CLuaHandle::SetHandleSelectTeam(L, teamID); } else if (lua_istable(L, 1)) { const int table = 1; for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { if (!lua_israwstring(L, -2) || !lua_isnumber(L, -1)) { continue; } const string key = lua_tostring(L, -2); const int teamID = lua_toint(L, -1); if ((teamID < CEventClient::MinSpecialTeam) || (teamID >= teamHandler->ActiveTeams())) { luaL_error(L, "Bad teamID in SetCtrlTeam"); } if (key == "ctrl") { CLuaHandle::SetHandleCtrlTeam(L, teamID); CLuaHandle::SetHandleFullCtrl(L, prevCtrlTeam == CEventClient::AllAccessTeam); } else if (key == "read") { CLuaHandle::SetHandleReadTeam(L, teamID); CLuaHandle::SetHandleReadAllyTeam(L, (teamID < 0) ? teamID : teamHandler->AllyTeam(teamID)); CLuaHandle::SetHandleFullRead(L, prevReadAllyTeam == CEventClient::AllAccessTeam); } else if (key == "select") { CLuaHandle::SetHandleSelectTeam(L, teamID); } } } else { luaL_error(L, "Incorrect arguments to CallAsTeam()"); } // call the function const int funcArgs = lua_gettop(L) - 2; // protected call so that the permissions are always reverted const int error = lua_pcall(L, funcArgs, LUA_MULTRET, 0); // revert the permissions CLuaHandle::SetHandleFullCtrl(L, prevFullCtrl); CLuaHandle::SetHandleFullRead(L, prevFullRead); CLuaHandle::SetHandleCtrlTeam(L, prevCtrlTeam); CLuaHandle::SetHandleReadTeam(L, prevReadTeam); CLuaHandle::SetHandleReadAllyTeam(L, prevReadAllyTeam); CLuaHandle::SetHandleSelectTeam(L, prevSelectTeam); if (error != 0) { LOG_L(L_ERROR, "error = %i, %s, %s", error, "CallAsTeam", lua_tostring(L, -1)); lua_error(L); } return lua_gettop(L) - 1; // the teamID/table is still on the stack }
int CLuaHandleSynced::CallAsTeam(lua_State* L) { CLuaHandleSynced* lhs = GetActiveHandle(); if (lhs->teamsLocked) { luaL_error(L, "CallAsTeam() called when teams are locked"); } const int args = lua_gettop(L); if ((args < 2) || !lua_isfunction(L, 2)) { luaL_error(L, "Incorrect arguments to CallAsTeam()"); } // save the current access const bool prevFullCtrl = lhs->fullCtrl; const bool prevFullRead = lhs->fullRead; const int prevCtrlTeam = lhs->ctrlTeam; const int prevReadTeam = lhs->readTeam; const int prevReadAllyTeam = lhs->readAllyTeam; const int prevSelectTeam = lhs->selectTeam; // parse the new access if (lua_isnumber(L, 1)) { const int teamID = lua_toint(L, 1); if ((teamID < MinSpecialTeam) || (teamID >= teamHandler->ActiveTeams())) { luaL_error(L, "Bad teamID in SetCtrlTeam"); } // ctrl lhs->ctrlTeam = teamID; lhs->fullCtrl = (lhs->ctrlTeam == CEventClient::AllAccessTeam); // read lhs->readTeam = teamID; lhs->readAllyTeam = (teamID < 0) ? teamID : teamHandler->AllyTeam(teamID); lhs->fullRead = (lhs->readAllyTeam == CEventClient::AllAccessTeam); activeFullRead = lhs->fullRead; activeReadAllyTeam = lhs->readAllyTeam; // select lhs->selectTeam = teamID; } else if (lua_istable(L, 1)) { const int table = 1; for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { if (!lua_israwstring(L, -2) || !lua_isnumber(L, -1)) { continue; } const string key = lua_tostring(L, -2); const int teamID = lua_toint(L, -1); if ((teamID < MinSpecialTeam) || (teamID >= teamHandler->ActiveTeams())) { luaL_error(L, "Bad teamID in SetCtrlTeam"); } if (key == "ctrl") { lhs->ctrlTeam = teamID; lhs->fullCtrl = (lhs->ctrlTeam == CEventClient::AllAccessTeam); } else if (key == "read") { lhs->readTeam = teamID; lhs->readAllyTeam = (teamID < 0) ? teamID : teamHandler->AllyTeam(teamID); lhs->fullRead = (lhs->readAllyTeam == CEventClient::AllAccessTeam); activeFullRead = lhs->fullRead; activeReadAllyTeam = lhs->readAllyTeam; } else if (key == "select") { lhs->selectTeam = teamID; } } } else { luaL_error(L, "Incorrect arguments to CallAsTeam()"); } // call the function const int funcArgs = lua_gettop(L) - 2; // protected call so that the permissions are always reverted const int error = lua_pcall(lhs->L, funcArgs, LUA_MULTRET, 0); // revert the permissions lhs->fullCtrl = prevFullCtrl; lhs->fullRead = prevFullRead; lhs->ctrlTeam = prevCtrlTeam; lhs->readTeam = prevReadTeam; lhs->readAllyTeam = prevReadAllyTeam; lhs->selectTeam = prevSelectTeam; activeFullRead = prevFullRead; activeReadAllyTeam = prevReadAllyTeam; if (error != 0) { logOutput.Print("error = %i, %s, %s\n", error, "CallAsTeam", lua_tostring(L, -1)); lua_error(L); } return lua_gettop(L) - 1; // the teamID/table is still on the stack }