// the basic instruction
class Instruction
{
public:
Instruction(opcode code) : _code(code), _datsize(0), _data(0) {}
Instruction(opcode code, const char* data, size_t dataSize)
: _code(code), _datsize(dataSize), _data(new char[dataSize])
{ memcpy(_data, data, dataSize); } // we must store our own internal copy
Instruction(const Instruction& instr)
: _code(instr._code), _datsize(instr._datsize), _data(new char[_datsize])
{ memcpy(_data, instr._data, _datsize); }
void operator=(const Instruction& instr)
{ delete[] _data;
_size = instr._size;
_data = new char[_size];
memcpy(_data, instr._data, _datsize); }
~Instruction() { delete[] _data; } // and we must then clean up after it
opcode Code() const { return _code; }
const char* Data() const { return _data; }
private:
opcode _code;
size_t _datsize;
char* _data; // additional data
};
// data buffer to encapsulate copy-on-write functionality
class Buffer
{
public:
Buffer() : _size(0), _data(0) {}
Buffer(const char* data, size_t size)
: _size(size), _data(new char[_size])
{ memcpy(_data, data, _size); }
Buffer(const Buffer& buf) : _size(buf._size), _data(new char[_size])
{ memcpy(_data, buf._data, _size); }
~Buffer() { delete[] _data; }
void operator=(const Buffer& buf)
{
delete[] _data;
_size = buf._size;
_data = new char[_size];
memcpy(_data, buf._data, _size);
}
const char* Data() const { return _data; }
private:
size_t _size;
char* _data;
};
// the basic instruction
class Instruction
{
public:
Instruction(opcode code) : _code(code) {}
Instruction(opcode code, const char* data, size_t dataSize)
: _code(code), _data(data, dataSize) {}
opcode Code() const { return _code; }
const char* Data() const { return _data.Data(); }
private:
opcode _code;
Buffer _data; // additional data
};
const int BUFFER_SIZE = 256;
int main()
{
char buffer[BUFFER_SIZE];
// begin reading text lines from user
cout << "Input script instructions:" << endl;
cin.getline(buffer, BUFFER_SIZE);
// an input processing loop will follow once we can scan the input
return 0;
}
opcode arg1 arg2 ... argN
opcode1 arg1 arg2 ... argN
opcode2 arg1 arg2 ... argN
...and so on
typedef std::vector 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
};
int main()
{
VirtualMachine vm;
Scanner scanner;
vector 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(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));
. . .
. . .
// 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;
}
- if line contains no content, break out prematurely
- scan an opcode
- for the entire length of the line:
- skip over white space
- scan in next numerical value
private:
bool SkipSpacing(const std::string& line);
bool ScanCode(const std::string& line);
bool ScanNum(const std::string& line);
private:
CharArray _tokBuffer;
size_t _offset;
An offset into the string "line" and an array of tokens will be initialized at the start of ScanLine() and will be used and modified throughout by the specialized utility functions just described.
Now we can put some code into ScanLine():
bool Scanner::ScanLine(const std::string& line)
{
// reset offset and token buffer
_offset = 0;
_tokBuffer.clear();
// check for an empty line
if (line.empty())
return false;
// check for valid line content
if (!SkipSpacing(line))
return false;
// check for a valid opcode
if (!ScanCode(line))
return false;
size_t len = line.length();
while (_offset < len) // scan args until the end of line
{
if (!SkipSpacing(line)) // get to next arg
return true; // unless we're done
if (!ScanNum(line))
return false;
}
return true;
}
bool SkipSpacing(const std::string& line)
{
while (isspace(line.c_str()[_offset]))
++_offset;
if (line.c_str()[_offset] == 0)
return false;
return true;
}
bool ScanCode(const std::string& line)
{
size_t begin = _offset;
while (isalpha(line.c_str()[_offset]))
++_offset;
return MatchCode(std::string(line, begin, _offset-begin));
}
bool ScanNum(const std::string& line)
{
size_t begin = _offset;
while (isdigit(line.c_str()[_offset]))
++_offset;
if (_offset == begin) // were any digits scanned?
return false;
std::string number(line, begin, _offset-begin);
_tokBuffer.push_back(static_cast(atoi(number.c_str())));
return true;
}
typedef std::vector StringArray;
. . .
private:
StringArray _codeNames;
. . .
bool MatchCode(const std::string& str)
{
char codeVal;
StringArray::iterator itr = _codeNames.begin();
for (codeVal = 0; itr != _codeNames.end(); ++itr, ++codeVal)
{
if (str == *itr)
{
_tokBuffer.push_back(codeVal);
return true;
}
}
return false;
}
public:
Scanner(const StringArray& codeNames) : _codeNames(codeNames) {}
Scanner(const std::string* codeNames, size_t nameCount)
{
_codeNames.resize(nameCount);
const std::string* end = codeNames+nameCount;
std::copy(codeNames, end, _codeNames.begin());
}
int main()
{
string codeList[num_codes] =
{
"talk",
"print",
"set",
"inc",
"dec",
"add",
"end"
};
VirtualMachine vm;
Scanner scanner(codeList, num_codes);
. . .
// enumerations describing the bytecode instructions the VM is able to process
enum opcode
{
op_talk=0,
op_print, // our new printing code
// variable manipulators
op_set, // char, char : destination index, value to set
op_inc, // char : index to increment
op_dec, // char : index to decrement
op_add, // char, char, char : dest index, srce index1, srce index2
op_end,
// not an opcode
num_codes // value is the number of opcodes if first opcode has value of 0
};
class File
{
public:
bool Close() { assert(Good()); return (fclose(_fp) == 0); }
bool Good() const { return (_fp != 0); }
bool Bad() const { return (_fp == 0); }
// file detection
static bool Exists(const std::string& fileName) { . . . }
// file length (only use before reading/writing)
long Length() { /* use fseek() to determine file length */ }
protected:
File() : _fp(0) {} // this class should not be directly instantiated
~File() { if (_fp) fclose(_fp); }
. . .
FILE* _fp;
};
class ReaderFile : public File
{
public:
ReaderFile() {}
ReaderFile(const std::string& fileName) { Open(fileName); }
bool Open(const std::string& fileName) { . . . }
// reading
int Read(char* dstBuf, size_t len) { /* return an fread() call here */ }
// for many basic variable types
int Read(particular_type& val) { /* typecast particular_type and call Read() with proper size*/ }
};
class WriterFile : public File
{
public:
WriterFile() {}
WriterFile(const std::string& fileName) { Open(fileName); }
WriterFile(const std::string& fileName, bool append) { Open(fileName, append); }
bool Open(const std::string& fileName) { /* Truncates an existing file, rather than appending. */ }
bool Open(const std::string& fileName, bool append) { . . . }
// writing
int Write(const char* srcBuf, size_t len) { /* return an fwrite() call here */ }
// for many basic variable types
int Write(particular_type val) { /* typecast particular_type and call Write() with proper size*/ }
};
class TextReaderFile : public File
{
public:
TextReaderFile() {}
TextReaderFile(const std::string& fileName) { Open(fileName); }
bool Open(const std::string& fileName) { . . . }
// reading
int Read(char* buffer, size_t len) { /* call fread() */ }
int Read(std::string& str, size_t len) { /* call fread() */ }
};
class TextWriterFile : public File
{
public:
TextWriterFile() {}
TextWriterFile(const std::string& fileName) { Open(fileName); }
TextWriterFile(const std::string& fileName, bool append) { Open(fileName, append); }
bool Open(const std::string &fileName) { /* Truncates an existing file. */ }
bool Open(const std::string& fileName, bool append) { . . . }
// writing
int Write(const char* str) { /* call fwrite() */ }
int Write(const std::string& str) { /* call fwrite() */ }
};
// prompt user to enter a filename
cout << "Enter path/name of script file: ";
cin.getline(buffer, BUFFER_SIZE);
// attempt to open the file
TextReaderFile file(buffer);
if (file.Bad())
{
cout << "Could not open file!" << endl;
return 0;
}
// read in file data
string fileData;
fileData.resize(file.Length());
file.Read(fileData, fileData.length());
size_t begin = 0, end = 0, lineNum = 1, varCount = NUM_VARIABLES;
// feed data into scanner and build instructions
while (end != string::npos)
{
// grab a line from the file data
end = fileData.find_first_of("\n", begin);
string line(fileData, begin, end-begin);
begin = end+1; // move past '\n' character
// scan the line
if (scanner.ScanLine(line))
{
const CharArray& tokens = scanner.GetTokens();
opcode code = static_cast(tokens[0]);
instrList.push_back(Instruction(code, &tokens[1], tokens.size()-1));
}
else
{
cout << "Invalid Instruction!" << endl;
cout << "Line number: " << lineNum << endl;
cout << "Line: " << line << endl;
}
++lineNum;
}
// as a safety precaution, it couldn't hurt to have redundant ends
instrList.push_back(Instruction(op_end));
Variables: 4
... list of instructions ...
size_t . . . varCount = NUM_VARIABLES;
[bquote][font="Courier New"][color="#000080"]talk
print 72 101 108 108 111 32 87 111 114 108 100
set 0 12
set 1 10
dec 0
inc 1
end[/color][/font][/bquote]
You'll notice of course that the final "end" isn't actually needed, as we automatically provide one after scanning. However, as I commented, it's safe to add redundant ends. This script should have output that looks like this:
[bquote][font="Courier New"][color="#000080"]I am talking.
Hello World[/color][/font][/bquote]
You'll notice that the values provided to "print" in the script are the ASCII codes for the corresponding characters of the string it displays. The variable state output of the script for 0 and 1 respectively should be:
[bquote][font="Courier New"][color="#000080"]0: 11
1: 11[/color][/font][/bquote]
[size="5"]Exercises for the Reader
There are many possible improvements to be made to what has been given as example so far. A few improvements stand out, and I leave these for the reader to solve (optionally of course). With a little thought, none of these should be too difficult.
- Outputting the variable state to a file instead of the console.
- Creating an instruction to display a particular variable state.
- Scanning in the desired variable count from a script file to allow it to be determined at run-time.
- Implementing instructions to allow the system to drive something you've written in the past.