1 ///////////////////////////////////////////////////////////////////////////////
2 //            Copyright (C) 2004-2010 by The Allacrost Project
3 //                         All Rights Reserved
4 //
5 // This code is licensed under the GNU GPL version 2. It is free software
6 // and you may modify it and/or redistribute it under the terms of this license.
7 // See http://www.gnu.org/copyleft/gpl.html for details.
8 ///////////////////////////////////////////////////////////////////////////////
9 
10 /** ****************************************************************************
11 *** \file    script_modify.cpp
12 *** \author  Daniel Steuernol - steu@allacrost.org,
13 ***          Tyler Olsen - roots@allacrost.org
14 *** \brief   Source file for the ModifyScriptDescriptor class.
15 *** ***************************************************************************/
16 
17 #include "script.h"
18 
19 #include "script_modify.h"
20 #include "script_read.h"
21 #include "script_write.h"
22 
23 using namespace std;
24 using namespace luabind;
25 
26 using namespace hoa_utils;
27 
28 namespace hoa_script {
29 
~ModifyScriptDescriptor()30 ModifyScriptDescriptor::~ModifyScriptDescriptor() {
31 	if (IsFileOpen()) {
32 		if (SCRIPT_DEBUG)
33 			cerr << "SCRIPT WARNING: ModifyScriptDescriptor destructor was called when file was still open: "
34 				<< _filename << endl;
35 		CloseFile();
36 	}
37 
38 	_filename = "";
39 	_access_mode = SCRIPT_CLOSED;
40 	_error_messages.clear();
41 	_open_tables.clear();
42 }
43 
44 //-----------------------------------------------------------------------------
45 // File Access Functions
46 //-----------------------------------------------------------------------------
47 
OpenFile(const std::string & file_name)48 bool ModifyScriptDescriptor::OpenFile(const std::string& file_name) {
49 	if (ScriptManager->IsFileOpen(file_name) == true) {
50 		if (SCRIPT_DEBUG)
51 			cerr << "SCRIPT WARNING: ModifyScriptDescriptor::OpenFile() attempted to open file that is already opened: "
52 				<< file_name << endl;
53 		return false;
54 	}
55 
56 	// Check if this file was opened previously.
57 	if ((this->_lstack = ScriptManager->_CheckForPreviousLuaState(file_name)) == NULL)
58 	{
59 		// Increases the global stack size by 1 element. That is needed because the new thread will be pushed in the
60 		// stack and we have to be sure there is enough space there.
61 		lua_checkstack(ScriptManager->GetGlobalState(),1);
62 		_lstack = lua_newthread(ScriptManager->GetGlobalState());
63 
64 		// Attempt to load and execute the Lua file.
65 		if (luaL_loadfile(_lstack, file_name.c_str()) != 0 || lua_pcall(_lstack, 0, 0, 0)) {
66 			cerr << "SCRIPT ERROR: ModifyScriptDescriptor::OpenFile() could not open the file " << file_name << endl;
67 			_access_mode = SCRIPT_CLOSED;
68 			return false;
69 		}
70 	}
71 
72 	// Write out some global stuff
73 	_filename = file_name;
74 	_access_mode = SCRIPT_MODIFY;
75 	ScriptManager->_AddOpenFile(this);
76 	return true;
77 } // bool ModifyScriptDescriptor::OpenFile(std::string file_name)
78 
79 
80 
OpenFile()81 bool ModifyScriptDescriptor::OpenFile() {
82 	if (_filename == "") {
83 		if (SCRIPT_DEBUG)
84 			cerr << "SCRIPT ERROR: ModifyScriptDescriptor::OpenFile(), could not open file "
85 				<< "because of an invalid file name (empty string)." << endl;
86 		return false;
87 	}
88 
89 	return OpenFile(_filename);
90 }
91 
92 
93 
CloseFile()94 void ModifyScriptDescriptor::CloseFile() {
95 	if (IsFileOpen() == false) {
96 		if (SCRIPT_DEBUG)
97 			cerr << "SCRIPT ERROR: ModifyScriptDescriptor::CloseFile() could not close the "
98 				<< "file because it was not open." << endl;
99 		return;
100 	}
101 
102 	if (SCRIPT_DEBUG && IsErrorDetected()) {
103 		cerr << "SCRIPT WARNING: In ModifyScriptDescriptor::CloseFile(), the file " << _filename
104 			<< " had error messages remaining. They are as follows:" << endl;
105 		cerr << _error_messages.str() << endl;
106 	}
107 
108 	_lstack = NULL;
109 	_error_messages.clear();
110 	_open_tables.clear();
111 	_access_mode = SCRIPT_CLOSED;
112 	ScriptManager->_RemoveOpenFile(this);
113 }
114 
115 
116 //-----------------------------------------------------------------------------
117 // Commit Function Definitions
118 //-----------------------------------------------------------------------------
119 
CommitChanges(bool leave_closed)120 void ModifyScriptDescriptor::CommitChanges(bool leave_closed) {
121 	WriteScriptDescriptor file; // The file to write the modified Lua state out to
122 	string temp_filename = _filename.substr(0, _filename.find_last_of('.')) + "_TEMP" + _filename.substr(_filename.find_last_of('.'));
123 
124 	if (file.OpenFile(temp_filename) == false) {
125 		if (SCRIPT_DEBUG)
126 			_error_messages << "* ModifyScriptDescriptor::CommitChanges() failed because it could not open "
127 				<< "the file to write the modifications to" << endl;
128 		return;
129 	}
130 
131 	// setup the iterator
132 	_open_tables_iterator = _open_tables.begin();
133 
134 	// Write the global tables to the file. This in turn will write all other tables that are members of
135 	// the global tables, or members of those tables, and so on.
136 	object globals(luabind::from_stack(_lstack, LUA_GLOBALSINDEX));
137 	_CommitTable(file, globals);
138 
139 	file.CloseFile(); // Close the temporary file we were writing to
140 	CloseFile(); // Close this file as well as it is about to be over-written
141 
142 	// Now overwrite this file with the temporary file written, remove the temporary file, and re-open the new file
143 
144 	if (MoveFile(temp_filename, _filename) == false) {
145 		_error_messages << "* ModifyScriptDescriptor::CommitChanges() failed because after writing the temporary file "
146 			<< temp_filename << ", it could not be moved to overwrite the original filename " << _filename << endl;
147 	}
148 
149 	if (leave_closed == false)
150 		OpenFile();
151 } // void ModifyScriptDescriptor::CommitChanges(bool leave_closed)
152 
153 
154 
_CommitTable(WriteScriptDescriptor & file,const luabind::object & table)155 void ModifyScriptDescriptor::_CommitTable(WriteScriptDescriptor& file, const luabind::object& table) {
156 	bool key_is_numeric;  // Set to true when a variable's key is not a string
157 	int32 num_key = 0;    // Holds the current numeric key
158 	string str_key = "";  // Holds the current string key
159 
160 	for (luabind::iterator it(table), end; it != end; ++it) {
161 		try {
162 			num_key = object_cast<int32>(it.key());
163 			key_is_numeric = true;
164 		} catch (...) {
165 			str_key = object_cast<string>(it.key());
166 			key_is_numeric = false;
167 		}
168 
169 		if (key_is_numeric && _open_tables_iterator == _open_tables.end())
170 		{
171 			cerr << "ModifyScriptDescriptor::_CommitTable: reached numeric key before writing out open tables" << endl;
172 			return;
173 		}
174 		else if (!key_is_numeric && _open_tables_iterator != _open_tables.end())
175 		{
176 			if (str_key == (*_open_tables_iterator))
177 			{
178 				file.BeginTable(str_key);
179 				_open_tables_iterator++;
180 				_CommitTable(file, object(*it));
181 				file.EndTable();
182 				return;
183 			}
184 			continue;
185 		}
186 
187 		// Check for _G table and do not write it out, causes
188 		// infinite recursion.
189 		if (!key_is_numeric)
190 			if (str_key == "_G")
191 				continue;
192 		switch (luabind::type(*it)) {
193 			case LUA_TBOOLEAN:
194 				if (key_is_numeric)
195 					file.WriteBool(num_key, object_cast<bool>(*it));
196 				else
197 					file.WriteBool(str_key, object_cast<bool>(*it));
198 				break;
199 			case LUA_TNUMBER:
200 				try {
201 					if (key_is_numeric)
202 						file.WriteFloat(num_key, object_cast<float>(*it));
203 					else
204 						file.WriteFloat(str_key, object_cast<float>(*it));
205 				} catch (...) {
206 					if (key_is_numeric)
207 						file.WriteInt(num_key, object_cast<int32>(*it));
208 					else
209 						file.WriteInt(str_key, object_cast<int32>(*it));
210 				}
211 				break;
212 			case LUA_TSTRING:
213 				if (key_is_numeric)
214 					file.WriteString(num_key, object_cast<string>(*it));
215 				else
216 					file.WriteString(str_key, object_cast<string>(*it));
217 				break;
218 			case LUA_TTABLE:
219 				if (key_is_numeric)
220 					file.BeginTable(num_key);
221 				else
222 					file.BeginTable(str_key);
223 				_CommitTable(file, object(*it));
224 				file.EndTable();
225 				break;
226 			case LUA_TNIL:
227 			case LUA_TFUNCTION:
228 			case LUA_TUSERDATA:
229 			case LUA_TLIGHTUSERDATA:
230 			case LUA_TTHREAD:
231 			default:
232 				if (SCRIPT_DEBUG) {
233 					_error_messages << "* ModifyScriptDescriptor::_CommitTable() detected a ";
234 					if (type(*it) == LUA_TNIL) {
235 						_error_messages << "nil value ";
236 					} else if (type(*it) == LUA_TFUNCTION) {
237 						_error_messages << "function ";
238 					} else if (type(*it) == LUA_TFUNCTION) {
239 						_error_messages << "function ";
240 					} else if (type(*it) == LUA_TUSERDATA) {
241 						_error_messages << "user data ";
242 					} else if (type(*it) == LUA_TLIGHTUSERDATA) {
243 						_error_messages << "light user data ";
244 					} else if (type(*it) == LUA_TTHREAD) {
245 						_error_messages << "thread ";
246 					} else {
247 						_error_messages << "unknown data type ";
248 					}
249 
250 					if (key_is_numeric)
251 						_error_messages << "key: " << num_key;
252 					else
253 						_error_messages << str_key;
254 					_error_messages << ". It was not written to the modified file." << endl;
255 				}
256 				break;
257 		} // switch (luabind::type(*it))
258 	} // for (luabind::iterator it(table), end; it != end; ++it)
259 } // void ModifyScriptDescriptor::_CommitTable(WriteScriptDescriptor& file, luabind::object& table)
260 
261 } // namespace hoa_script
262