package logo.lang; import java.util.Stack; import java.util.StringTokenizer; import java.util.Vector; import java.util.Hashtable; import java.util.Enumeration; import java.awt.Color; import java.lang.Thread; import logo.lang.VarContext; import logo.lang.rLogoPrimitive; import logo.lang.rLogoHelp; import logo.lang.rLogoException; public abstract class Interpreter extends Thread { VarContext vc; //holds any variables & their values SubTable st; //holds any subroutines & their definitions rLogoPrimitive r = new rLogoPrimitive(); //holds all of the rLogo primitives Vector queue = new Vector(); //holds pending commands private boolean rLogoBreak=false; //This could have imported logo.ui.TurtleGraphics, and supplied the //default behavior for these functions, but TurtleControlPanel needs //to override most of them to reflect state changes (for instance, //setColor changes the state of the ColorChooser, HideTurtle changes //the state of the "Turtle is Hidden" button, etc.) It seemed //there would be a slight performance gain in TurtleControlPanel if //calls to 'super.someMethod()' were avoided in the overridden functions, //so they are declared as 'abstract' and are fully implemented in //TurtleControlPanel. This also serves to better separate the 'language' //objects from the 'graphical' objects. public final static String rLOGO_BATCH = "_rLogo_Batch_"; public final static String rLOGO_DUMP = "_rLogo_Dump_"; public abstract void wrapString(String s); public abstract void forward(int i); public abstract void backward(int i); public abstract void turnLeft(int i); public abstract void turnRight(int i); public abstract void penUp(); public abstract void penDown(); public abstract void showTurtle(); public abstract void hideTurtle(); public abstract void clearScreen(); public abstract void home(); public abstract void setColor(Color c); public abstract void setBackColor(Color c); public abstract void edit(LogoSub s); public abstract void resetMode(); public abstract void drawString(String s); public abstract void setRepaint(boolean b); public abstract void paint(); public Interpreter() { vc=new VarContext(); st=new SubTable(); } public String popString(LogoList l) throws rLogoException { String result = l.pop(); if (result==null) { throw(new rLogoException("Error: Not enough parameters!")); } return result; } public int popInt(LogoList l) throws rLogoException { Expression e = new Expression(); Integer result = new Integer(0); String s = l.pop(); if ( s==null ) { throw(new rLogoException("Error: Not enough parameters!")); } else { try { result = new Integer(e.evaluate(s,vc)); } catch (NumberFormatException x) { throw(new rLogoException("Error: Bad Number")); } } return result.intValue(); } public void queue(String command) { //queues a command for later processing queue.addElement(command); } public void run() { while (true) { if ( !queue.isEmpty() ) { try { execute( (String)queue.firstElement() ); } catch (rLogoBreakException x) { wrapString( x.getMessage() ); queue.removeAllElements(); } catch (rLogoException x) { wrapString( x.getMessage() ); } if (!queue.isEmpty()) { queue.removeElementAt(0); } } else { if (rLogoBreak) rLogoBreak=false; } try { sleep(100); } catch (InterruptedException e) {;} } } public final void Break() { rLogoBreak=true; } public void execute(String command) throws rLogoException { LogoList l; l = new LogoList(command); Expression e = new Expression(); String cmdString; int condition; Color c; while ( !l.empty() ) { if (rLogoBreak) { rLogoBreak=false; throw new rLogoBreakException("interrupted"); } String nextCommand = popString(l); nextCommand=nextCommand.toLowerCase(); if ( nextCommand.startsWith("[") ) { //(command block) execute(nextCommand.substring(1,nextCommand.length()-1)); } else switch ( r.lookup(nextCommand) ) { case rLogoPrimitive.COMMAND_NOT_FOUND : //perhaps it's a subroutine. LogoSub subroutine=st.findSub(nextCommand); if (subroutine!=null) { cmdString = subroutine.getValue(); int param=0; while ( param0 ) { if ( r.lookup(subName.toLowerCase())==rLogoPrimitive.COMMAND_NOT_FOUND ) { LogoSub sub=st.findSub(subName); if ( sub==null ) { sub=new LogoSub(subName,""); st.setObject(sub); } edit(sub); } else { wrapString("Error: Can't redefine primitives"); return; } } else { wrapString("('to' must be followed by a name)"); return; } break; case rLogoPrimitive.FORWARD : //forward (expression) forward(popInt(l)); break; case rLogoPrimitive.BACK : //back (expression) backward(popInt(l)); break; case rLogoPrimitive.RIGHT : //right (expression) turnRight(popInt(l)); break; case rLogoPrimitive.LEFT : //left (expression) turnLeft(popInt(l)); break; case rLogoPrimitive.PEN_UP : //Lifts the Turtle's Pen penUp(); break; case rLogoPrimitive.PEN_DOWN : //Lowers the Turtle's Pen penDown(); break; case rLogoPrimitive.SHOW_TURTLE : //Exposes the Turtle showTurtle(); break; case rLogoPrimitive.HIDE_TURTLE : //Makes the Turtle Disappear hideTurtle(); break; case rLogoPrimitive.CLEAR_SCREEN : //Erases the Screen clearScreen(); break; case rLogoPrimitive.AUTO : //resumes Automatic repaints setRepaint(true); paint(); break; case rLogoPrimitive.MANUAL : // Disables automatic screen refreshes setRepaint(false); break; case rLogoPrimitive.PAINT : // Force a Screen Refresh paint(); break; case rLogoPrimitive.HOME : // Returns the Turtle to its Home Position home(); break; case rLogoPrimitive.SET_PEN_COLOR : // Changes the Drawing Color c = new Color(popInt(l),popInt(l),popInt(l)); setColor(c); break; case rLogoPrimitive.SET_BACK_COLOR : // Changes the Background (Screen) Color c = new Color(popInt(l),popInt(l),popInt(l)); setBackColor(c); break; case rLogoPrimitive.WRITE : // Writes a message to the Screen drawString(e.evaluate(popString(l),vc)); break; case rLogoPrimitive.PAUSE : // Pauses the specified number of seconds try { sleep(popInt(l)); } catch (InterruptedException exception) {;} break; case rLogoPrimitive.BATCH : // Batch Loader LogoSub batch = new LogoSub(rLOGO_BATCH,""); edit(batch); break; case rLogoPrimitive.DUMP : // Batch Dump (i.e. Save) LogoSub dump = new LogoSub(rLOGO_DUMP,""); dump.setValue(dump()); edit(dump); break; case rLogoPrimitive.PARAMS : // Ignore this and the rest of the line //ignore it. popString(l); break; case rLogoPrimitive.END : //do nothing; (most likely, the user pasted the output //of 'dump()' into a new function.) break; case rLogoPrimitive.HELP : // Request for on-line help if (l.empty()) wrapString(getHelp("")); else wrapString(getHelp(popString(l))); break; default : wrapString("I don't know how to "+nextCommand); return; } } } //The rLogoHelp class is relatively large (~10K), //so override this method for a leaner class: String getHelp(String topic) { rLogoHelp help = new rLogoHelp(); return help.topic(topic); } public String dump() { //"dumps" all procedures and variables into the editor, in a //format that is suitable for later processing by "load()" //It would be a trivial task to make this save out to a //file in a non-secure application... String result=new String(); LogoVar nextVar = vc.First(); while ( nextVar!=null ) { result+="make "+nextVar.getName()+" "+nextVar.getValue()+"\n"; nextVar=vc.Next(); } LogoSub nextSub = st.First(); while ( nextSub!=null ) { result+="to "+nextSub.getName()+"\n"+nextSub.getValue()+"\nend\n"; nextSub=st.Next(); } if ( result.length()==0 ) { result="Nothing to Dump."; } return result; } public int load(String file) { //"loads" a series of procedures and variables into the subTable //and varContext; returns the number of lines loaded. //Converting this to load from a file would be very simple (in an //application) int result=0; int stop; String nextLine; String nextSubName = null; String nextSub = null; while ( file.length()>0 ) { result++; stop=file.indexOf("\n"); if (stop<0) stop=file.length(); nextLine=file.substring(0,stop); nextLine.toLowerCase(); nextLine.trim(); if ( ++stop>file.length() ) file=""; else file=file.substring(stop); if ( nextSubName!=null ) { //we're in the middle of loading a subroutine if ( nextLine.startsWith("end") ) { //close it up... st.setValue(nextSubName,nextSub); nextSubName=null; nextSub=null; } else nextSub=nextSub.concat(nextLine+"\n"); //add another line } else if ( nextLine.startsWith("to ") ) { //a new subroutine... nextSubName=nextLine.substring(3); nextSub=new String(); } else { //execute it... queue(nextLine); } } return result; } } class Expression { public final String SYMBOLS=new String("~*/+-<>="); //should this be a static method? //should this make use of class StringBuffer() ? public String replace(String expr, String oldString, String newString) { //replaces 1st occurrence of in with String result; int start = expr.indexOf(oldString); if ( start>-1 ) { result = new String(); result+=expr.substring(0,start); result+=newString; result+=expr.substring(start+oldString.length()); } else result=expr; return result; } int getTerm(int i, Vector v) throws rLogoException { //extracts term #i from expression held in v int result=0; try { result=new Integer((String)v.elementAt(i)).intValue(); } catch (NumberFormatException e) { throw new rLogoException("Syntax Error: Bad Number: "+(String)v.elementAt(i)); } catch (ArrayIndexOutOfBoundsException e) { throw new rLogoException("Syntax Error: Unable to Evaluate Expression"); } return result; } int getTerm1(int i, Vector v) throws rLogoException { return getTerm(i-1,v); } int getTerm2(int i, Vector v) throws rLogoException { return getTerm(i+1,v); } public String evaluate(String expr, VarContext context) throws rLogoException { //Variable substitutions //Simplify equations //Precedence: () , *, /, +, -, <, >, =, if (expr==null) throw new rLogoException("Syntax Error: Bad Expression (received null)"); expr.trim(); if ( expr.length()==0 ) throw new rLogoException("Syntax Error: Received Empty Expression"); //remove leading & trailing parentheses, if they exist (e.g. '(expr)' becomes 'expr') if ( expr.startsWith("(") && expr.endsWith(")") ) { expr=expr.substring(1,expr.length()-1); } //recursively evaluate all parenthetical sub-expressions: while (expr.indexOf("(")>-1) { String subExpr=extractParentheticalExpression(expr); expr=replace(expr,"("+subExpr+")",evaluate(subExpr,context)); } Vector v = new Vector(); StringTokenizer st = new StringTokenizer(expr,SYMBOLS,true); while (st.hasMoreTokens()) { v.addElement(st.nextToken()); } //first, lookup values of any variables: for (int i=0; i=0) ) { int term = getTerm2(i,v); v.setElementAt(Integer.toString(-1*term),i); v.removeElementAt(i+1); } } } //next, evaluate the ~'s (logical negation): for (int i=0; i'); evaluateSubExpression(v,'<'); evaluateSubExpression(v,'='); if (v.size()>1) throw (new rLogoException("Syntax Error: Unable to Evaluate Expression")); return (String) v.elementAt(0); } private final void evaluateSubExpression(Vector v, char symbol) throws rLogoException { String nextString; for (int i=1; i' : result=(term1>term2) ? 1 : 0; break; case '<' : result=(term1