Loading From TextWe now must decide how we are going to process scripts from a simple text-based format. The format will use a syntax similar to the one illustrated in examples from the previous articles. The only real difference is a lack of commas for additional simplicity. A single instruction will have syntax of the form: opcode arg1 arg2 ... argN Where the opcode and possible arguments are delimited by white space. A script would then be a list of such instructions, with each instruction on a new line: opcode1 arg1 arg2 ... argN opcode2 arg1 arg2 ... argN ...and so on In order to utilize the information present in this text-based format, we will have to construct a simple parser. The first part of this parser will scan a line of text and create a list of tokens. For the sake of simplicity, these tokens will simply be of type char (like our instruction and variable data), so that they can correspond directly to a value. An interface for the Scanner: typedef std::vector<char> CharArray; // an array of char tokens class Scanner { public: bool ScanLine(const std::string& line); // tokenize a line and return success const CharArray& GetTokens() const; // get tokens from most recent scan }; With this interface in hand, we can now outline our test. After every successful scan, the tokens will be used to create an instruction, which will be placed at the end of a list of instructions. Any failure to scan a line will notify the user and not create an instruction for that particular scan. To complete the entering of a list of instructions, the user inputs "end" and a final op_end instruction will be appended to the list, to ensure a script based off these instructions will not run beyond its own length. int main() { VirtualMachine vm; Scanner scanner; vector<Instruction> instrList; char buffer[BUFFER_SIZE]; string input; // begin reading text lines from user cout << "Input script instructions:" << endl; while (input != "end") { cin.getline(buffer, BUFFER_SIZE); input = buffer; // may as well use the string if (scanner.ScanLine(input)) { const CharArray& tokens = scanner.GetTokens(); opcode code = static_cast<opcode>(tokens[0]); instrList.push_back(Instruction(code, &tokens[1], tokens.size()-1)); } else cout << "Invalid Instruction!" << endl; } // as a safety precaution, it couldn't hurt to have redundant ends instrList.push_back(Instruction(op_end)); . . . The user will then be prompted to provide the number of variables the script will need to store in order to run properly. With the instruction list and the variable count, the script will be created, loaded, executed, and have its variable state exposed. . . . // obtain a variable count size_t varCount; cout << "Input required variables count: "; cin >> varCount; Script script(instrList, varCount); // load the script and save the id size_t scriptID = vm.Load(script); // execute script by its id cout << endl << "***EXECUTING SCRIPT***" << endl; vm.Execute(scriptID); // check out the variable states cout << endl << "***VARIABLE STATES***" << endl; vm.ShowVariableState(); return 0; } This test puts a bit of faith in the user's ability to identify the number of variables required by a particular set of instructions. Without a mechanism for analyzing the tokens scanned for correctness, it will also fail if the user does not provide sufficient data with a particular opcode. As a result, this is definitely not suitable for a final interface for scripting, but will suffice for now to test the scanner. Now we need to complete the scanner's implementation.
|