As I discussed last month, the primary class for the interpreter is a public class with a factory method to create executable
programs. I chose to implement this load method in a class named basic.Program. (For the rest of the article we'll assume the class names are all preceded by the "basic" package name without expressly
calling it out.) Program exports a static method whose signature is:
public static Program load(InputStream source, PrintStream out)
throws IOException, BASICSyntaxError { ... }
This method is responsible for getting the text in an InputStream parsed and collected into something that can be interpreted. It returns an instance of a Program object, which is the parsed program. The other important public method in Program is run, whose signature is as follows:
public void run(InputStream in, OutputStream out) throws BASICRuntimeError {
PrintStream pout;
The run method is not static; it actually runs the instance of the program using the input and output streams that are passed for
doing character I/O.
As you can see, both of these methods throw exceptions. The first throws an IOException if an I/O error occurs while reading the source code from the input stream. The load method also can throw a BASICSyntaxError exception if the source code it is reading fails to parse. The second method, run, throws the exception BASICRuntimeError when an error occurs during the execution of the program. The kinds of situations that would throw this error include dividing
by zero or reading from an uninitialized variable.
These two methods, load and run, define the two halves of any interpreter, loading and executing.
load method in the Program class begins as follows:public static Program load(InputStream source, PrintStream out) throws IOException, BASICSyntaxError {
DataInputStream dis = null;
char data[] = new char[256];
LexicalTokenizer lt = new LexicalTokenizer(data);
String lineData;
Statement s;
Token t;
Program result = new Program();
if (! (source instanceof DataInputStream))
dis = new DataInputStream(new BufferedInputStream(source));
else
dis = source;
In the code above, the variables for the parsing section of the interpreter are initialized. These include a lexical analyzer
(in the form of the LexicalTokenizer class) and a DataInputStream. There is a simple optimization that checks to see whether or not the method was passed an instance of a DataInputStream. The data array is an array of characters the lexical analyzer will use while reading in the source code. Note that it puts
a limit of about 256 characters on the input line.