AgendaDuring the course of this article I will illustrate a way to implement the following:
Scripted functions will simply allow your scripts to become more modular, while externally called functions will allow your scripting system to interact with an existing code-base. The second method is how we will create a system that can grab an interface of function pointers from something like a game engine, and then be able to operate this system by calling scripts. Improving the StackAs I said at the end of the last article, the tools for expressing the higher-level concept of functions in scripts have already been provided. Although this is true, the ScopeStack can be enhanced to make things easier for us when trying to pass parameters to a new scope without having to hack around the system. One way to do this is to introduce a second parameter to its PushScope() member which will allow the new scope pushed to include variables pushed onto the stack during the previous scope. In ScopeStack "would" be: void PushScope(size_t scopeSize, size_t numParams) { assert(_stack.size()>=numParams); // make sure our stack is as large as we think _indices.push_back(_stackBase); // store the current index _stackBase = _stack.size()-numParams; // get new scope offset _stack.resize(_stackBase+scopeSize); // accommodate new scope data } This is good, except that it could cause problems later on if we are not careful. The danger is hinted at by the assertion. The problem here is that if the proper number of variables intended to be parameters aren't pushed onto the stack before calling PushScope(), the new scope will actually leak into data that is strictly intended for use by the old scope. Furthermore, when this scope is popped off the stack, the amount of data the scope had leaked into will be lost. So let's fix the inherent problem in the ScopeStack to deal with this issue. We will make use of an additional data member in the ScopeStack to store the next base offset for the stack in the event of a scope being pushed: size_t _nextBase; A few changes to the scope interface's member functions will now determine the starting offset for the next scope to be pushed onto the stack initially, and every time a new scope is pushed. We have succeeded in squashing this possible bug preemptively. Actual ScopeStack code: // scope interface void SetInitialScope(size_t scopeSize) { assert(_stack.empty() && _indices.empty()); // initial scope should only be set once _nextBase = _stackBase+scopeSize; // preemptively set the next offset _stack.resize(_nextBase); // accommodate initial scope data _indices.push_back(_stackBase); // register the base offset } void PushScope(size_t scopeSize) { _stackBase = _nextBase; // get new scope offset _indices.push_back(_stackBase); // register this offset _nextBase = _stackBase+scopeSize; // store the next index _stack.resize(_nextBase); // accommodate new scope data } void PopScope() { assert(!_indices.empty()); // don't try to pop a non-existent scope _stack.resize(_stackBase); // shrink back to the end of the former scope _nextBase = _stackBase; // store what the next index would be _indices.pop_back(); // take that index off the index stack _stackBase = _indices.back(); // grab the base index for the former scope } With the changes to our stack, we will need to modify the execution's function to expose its stack state. The difference is that the end of the current scope is now found using the next depth, rather than the current one. If there is no next depth, the end of the stack is used. There was also a bug where the value output was not being taken relative to the first depth (index 0), as it should have been. Well, no harm done. Here is the modified function: void VirtualMachine::Execution::ExposeStackState(ostream& out) const { size_t numScopes = _stack.ScopeDepth(); size_t stackSize = _stack.Size(); size_t index, label, curDepth = 0, curEnd = 0; for (index = 0, label = 0; index < stackSize; ++index, ++label) // for the entire stack { if (index == curEnd) // for every new scope in the stack { label = 0; out << "Scope Depth: " << curDepth << endl; // output the scope depth if (curDepth < numScopes) { if (curDepth+1 < numScopes) curEnd = _stack.GetScopeIndex(curDepth+1); // get end of this scope else curEnd = _stack.Size(); // end of stack ++curDepth; // advance to next scope depth } else curEnd = 0; } // output each value out << " " << label << ": " << static_cast<int>(_stack.GetAtDepth(index, 0)) << endl; } } To help during debugging, I have also introduced an instruction that will expose the stack state at our whim. It simply calls ExposeStackState() with the std::cout object as the output stream: // expose state case op_expose_state: ExposeStackState(cout); ++_instr; break; |
|