/* * Program.java - One BASIC program, ready to roll. * * Copyright (c) 1996 Chuck McManis, All Rights Reserved. * * Permission to use, copy, modify, and distribute this software * and its documentation for NON-COMMERCIAL purposes and without * fee is hereby granted provided that this copyright notice * appears in all copies. * * CHUCK MCMANIS MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE * SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. CHUCK MCMANIS * SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT * OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. */ package basic; import java.io.PrintStream; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.FileOutputStream; import java.util.Enumeration; import java.util.Random; import util.RedBlackTree; import java.util.Stack; import java.util.Vector; /** * This class instantiates a BASIC program. A valid program is one that is * parsed and ready to run. You can run it by invoking the run() method. * The standard input and output of the running basic program can either * be passed into the run method, or they can be presumed to be the * in and out streams referenced by the System class. * * This class uses Red-Black trees to hold the parsed statements and the * symbol table. * * @author Chuck McManis * @version 1.1 * @see CommandInterpreter * */ public class Program implements Runnable { // this tree holds all of the statements. private RedBlackTree stmts = new RedBlackTree(new NumberCompare()); // this tree holds all of the variables. private RedBlackTree vars = new RedBlackTree(); private Stack stmtStack = new Stack(); Vector dataStore = new Vector(); int dataPtr = 0; Random r = new Random(0); String myName; boolean traceState = false; PrintStream traceFile = null; void trace(boolean a) { traceState = a; } void trace(boolean a, String f) { if (traceFile == null) { try { traceFile = new PrintStream(new FileOutputStream(f)); } catch (IOException e) { System.out.println("Couldn't open trace file."); traceFile = null; } } trace(a); } Random getRandom() { return r; } void randomize(double seed) { r = new Random((long) seed); } void randomize() { r = new Random(); // uses the clock } /** * There are two ways to create a new program object, you can load one from * an already open stream or you can pass in a file name and load one from * the file system. */ public static Program load(InputStream source, PrintStream out) throws IOException, BASICSyntaxError { DataInputStream dis = null; dis = new DataInputStream(new BufferedInputStream(source)); char data[] = new char[256]; LexicalTokenizer lt = new LexicalTokenizer(data); String lineData; Statement s; Token t; Program result = new Program(); while (true) { // read a line of our BASIC program. lineData = dis.readLine(); // if EOF simply return. if (lineData == null) return result; // if the line was blank, ignore it. if (lineData.length() == 0) continue; lt.reset(lineData); t = lt.nextToken(); if (t.typeNum() != Token.CONSTANT) { throw new BASICSyntaxError("Line failed to start with a line number."); } try { s = ParseStatement.statement(lt); } catch (BASICSyntaxError bse) { out.println("Syntax error: "+bse.getMsg()); out.println(lt.showError()); throw bse; } s.addText(lineData); s.addLine((int) t.numValue()); result.add((int) t.numValue(), s); } } /** * Load the specified file and parse the basic statements it contains. * @throws IOException when the filename cannot be located or opened. * @throws BASICSyntaxError when the file does not contain a properly formed * BASIC program. */ public static Program load(String source, PrintStream out) throws IOException, BASICSyntaxError { // XXX this needs to use the SourceManager class // FileInputStream fis = new FileInputStream(source); Program r = null; try { r = load(fis, out); } catch (BASICSyntaxError e) { fis.close(); throw e; } fis.close(); return r; } /** * Write the basic program out to the passed output stream. Conceptually * this is identical to doing a list operation. */ public void save(OutputStream out) throws IOException { PrintStream p = new PrintStream(out); list(p); } /** * Write the program out to the file named in output. */ public void save(String output) throws IOException { FileOutputStream fos = new FileOutputStream(output); save(fos); fos.flush(); fos.close(); } /** * This method starts this program running in its own thread. */ void start() { Thread t = new Thread(this); if (myName != null) t.setName(myName + " execution."); t.start(); } /** * Add a statement to the current program. Statements are indexed * by line number. If the add fails for some reason this method * returns false. */ boolean add(int line, Statement s) { Integer ln = new Integer(line); Object z = stmts.put(ln, s); return true; } /** * Delete a statement from the current program. Statements are * indexed by line numbers. If the statement specified didn't * exist, then this method returns false. */ boolean del(int line) { if (stmts.remove(new Integer(line)) == null) return false; return true; } /** * Compute the indices based on the expressions in the variable * object. */ private int[] getIndices(Variable v) throws BASICRuntimeError { int result[] = new int[v.numExpn()]; for (int i=0; i < result.length; i++) { result[i] = (int) v.expn(i).value(this); } return result; } /** * Return the numeric value of a variable in the symbol table. * @throws BASICRuntimeError if the variable isn't defined. * */ double getVariable(Variable v) throws BASICRuntimeError { Variable vi = (Variable) vars.get(v.name); if (vi == null) { throw new BASICRuntimeError("Undefined variable '"+v.name+"'"); } if (! vi.isArray()) return vi.numValue(); int ii[] = getIndices(v); return vi.numValue(ii); } /** * Return the contents of the string variable named name. If * the variable has not yet been declared (ie used) this method throws * a BASICRuntime error. */ String getString(Variable v) throws BASICRuntimeError { Variable vi = (Variable) vars.get(v.name); if (vi == null) throw new BASICRuntimeError("Variable "+v.name+" has not been initialized."); if (! v.isArray()) return vi.stringValue(); int ii[] = getIndices(v); return vi.stringValue(ii); } /** * Set the numeric variable name to have value value. If * this is the first time we have seen the variable, create a place for * it in the symbol table. */ void setVariable(Variable v, double value) throws BASICRuntimeError { Variable vi = (Variable) vars.get(v.name); if (vi == null) { if (v.isArray()) throw new BASICRuntimeError("Array must be declared in a DIM statement"); vi = new Variable(v.name); vars.put(v.name, vi); } if (! vi.isArray()) { vi.setValue(value); return; } int ii[] = getIndices(v); vi.setValue(value, ii); } void setRandom(long seed) { } /** * Set the string variable named name to have the value value. * If this is the first use of the variable it is created. */ void setVariable(Variable v, String value) throws BASICRuntimeError { Variable vi = (Variable) vars.get(v.name); if (vi == null) { if (v.isArray()) throw new BASICRuntimeError("Array must be declared in a DIM statement"); vi = new Variable(v.name); vars.put(v.name, vi); } if (! vi.isArray()) { vi.setValue(value); return; } int ii[] = getIndices(v); vi.setValue(value, ii); } /** * This method is used by the DIM statement to DECLARE arrays. Given * the nature of arrays we force them to be declared before they can * be used. This is common to most BASIC implementations. */ void declareArray(Variable v) throws BASICRuntimeError { Variable vi; int ii[] = getIndices(v); vi = new Variable(v.name, ii); Variable xx = (Variable) vars.put(v.name, vi); } /** * Compute and return the next program statement to be executed. * The policy is, if the current statement has another statement hanging * off its nxt pointer use that one, otherwise use the next one * in the program numerically. */ Statement nextStatement(Statement s) { if (s == null) { return null; } else if (s.nxt != null) { return s.nxt; } return ((Statement) stmts.next(new Integer(s.line))); } /** * Return the statment whose line number is line */ Statement getStatement(int line) { Statement s = (Statement) stmts.get(new Integer(line)); return s; } /** * List program lines from start to end out to the * PrintStream p. Note that due to a bug in the Windows * implementation of PrintStream this method is forced to append * a to the file. */ void list(int start, int end, PrintStream p) { for (Enumeration e = stmts.elements(); e.hasMoreElements(); ) { Statement s = (Statement) e.nextElement(); if ((s.lineNo() >= start) && (s.lineNo() <= end)) { p.print(s.asString()); p.print("\r"); p.println(); // for Windows clients } } } /** * Dump the symbol table */ void dump(PrintStream p) { for (Enumeration e = vars.elements(); e.hasMoreElements(); ) { Variable v = (Variable) e.nextElement(); p.println(v.unparse()+" = "+(v.isString() ? "\""+v.stringValue()+"\"" : ""+v.numValue())); } } /** * This is the first variation on list, it simply list from the starting * line to the the end of the program. */ void list(int start, PrintStream p) { list(start, Integer.MAX_VALUE, p); } /** * This second variation on list will list the entire program to the passed * PrintStream object. */ void list(PrintStream p) { list(0, p); } /** * This final variant of the list method will list the program on System.out. */ void list() { list(System.out); } /** * Run the program and use the passed in streams as its input and output streams. * * Prior to running the program the statement stack is cleared, and the data fifo * is also cleared. Thus re-running a stopped program will always work correctly. * * @throws BASICRuntimeError if an error occurs while running. */ public void run(InputStream in, OutputStream out) throws BASICRuntimeError { PrintStream pout; Enumeration e = stmts.elements(); stmtStack = new Stack(); // assume no stacked statements ... dataStore = new Vector(); // ... and no data to be read. dataPtr = 0; Statement s; vars = new RedBlackTree(); // if the program isn't yet valid. if (! e.hasMoreElements()) return; if (out instanceof PrintStream) { pout = (PrintStream) out; } else { pout = new PrintStream(out); } /* First we load all of the data statements */ while (e.hasMoreElements()) { s = (Statement) e.nextElement(); if (s.keyword == Statement.DATA) { s.execute(this, in, pout); } } e = stmts.elements(); s = (Statement) e.nextElement(); do { int yyy; /* While running we skip Data statements. */ try { yyy = in.available(); } catch (IOException ez) { yyy = 0; } if (yyy != 0) { pout.println("Stopped at :"+s); push(s); break; } if (s.keyword != Statement.DATA) { if (traceState) { s.trace(this, (traceFile != null) ? traceFile : pout); } s = s.execute(this, in, pout); } else s = nextStatement(s); } while (s != null); } /** * This package private version of run() is used by the command interpreter * to run a "single" statement in the context of this program. Single is * in quotes because if the statement has additional statements chained off * its next pointer, these will be run as well. Further if one of them is * a GOTO or GOSUB or IF and they cause a tranfer to a numbered statement then * exectution will start at that statement. This can be useful for debugging * but unpredictable since not all variables will be declared if their assignment * statements have not yet been executed. * * Unlike its sibling method above, it does NOT clear the statement stack or * data FIFO. This is so the command interpreter can debug stopped programs * using the immediate execution feature. */ void run(Statement s, InputStream in, OutputStream out) throws BASICRuntimeError { // if the program isn't yet valid. PrintStream pout; Enumeration e = stmts.elements(); if (! e.hasMoreElements()) return; if (out instanceof PrintStream) { pout = (PrintStream) out; } else { pout = new PrintStream(out); } do { s = s.execute(this, in, pout); } while (s != null); } /** * This final version of run is used to implement the Runnable interface. * It will run the program using System.in and System.out as the standard I/O * streams for the program and it does *NOT* throw BASICRuntimeError. Instead * it catches it and prints an error message to standard out. */ public void run() { try { run(System.in, System.out); } catch (BASICRuntimeError e) { System.out.println("Error Running program: "+e.getMsg()); } } /** * This method resumes a program that has been stopped. If the program * wasn't really stopped it throws a BASICRuntimeError. * * @throws BASICRuntimeError - Program wasn't in a stopped state. */ void resume(InputStream in, PrintStream pout) throws BASICRuntimeError { Statement s; s = pop(); if ((s == null) || (s.keyword != Statement.STOP)) { throw new BASICRuntimeError("This program was not previously stopped."); } s = nextStatement(s); do { s = s.execute(this, in, pout); } while (s != null); } void cont(InputStream in, PrintStream pout) throws BASICRuntimeError { Statement s; int yyy; s = pop(); do { /* While running we skip Data statements. */ try { yyy = in.available(); } catch (IOException e) { yyy = 0; } if (yyy != 0) { pout.println("Stopped at :"+s); push(s); break; } if (s.keyword != Statement.DATA) { if (traceState) { s.trace(this, (traceFile != null) ? traceFile : pout); } s = s.execute(this, in, pout); } else s = nextStatement(s); } while (s != null); } /* * These methods deal with pushing and popping statements from the statement * stack, and data items from the data stack. */ /** * Push this statement on the stack (one of FOR, GOSUB, or STOP) */ void push(Statement s) { stmtStack.push(s); } /** * Pop the next statement off the stack, return NULL if the stack is * empty. */ Statement pop() { if (stmtStack.isEmpty()) return null; return (Statement) stmtStack.pop(); } /** * Add a token to the data FIFO. */ void pushData(Token t) { dataStore.addElement(t); } /** * Get the next token in the FIFO, return null if the * FIFO is empty. */ Token popData() { if (dataPtr > (dataStore.size() - 1)) return null; return (Token) dataStore.elementAt(dataPtr++); } /** * Reset the data FIFO back to the beginning. */ void resetData() { dataPtr = 0; } }