// FILE: Argus.java // Written by Michael Main in a two-day rush, Tuesday, Jan 19. // I know there are a lot of inefficiencies, some likely bugs, and no // documentation. // That's what happens when you write something quickly. // On the other hand, I was able to make nice use of Java's Hashtable // (for a table of identifiers) and Stack (for evaluating Boolean // expressions). import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.Enumeration; import java.util.Hashtable; import java.util.Stack; public class Argus extends Frame { protected Argus parent = this; protected World world = new World( ); protected TextArea program = new TextArea(8, 70); protected TextArea feedback = new TextArea(3, 70); protected Scrollbar speed = new Scrollbar(Scrollbar.HORIZONTAL, 9000, 0, 100, 7500); protected Checkbox recursion = new Checkbox("Allow Recursion"); protected String filename; protected String dirname; protected Hashtable definitions; protected Interpreter interpreter; private Button forward = new Button("forward"); private Button turn = new Button("turn"); private Button bark = new Button("bark"); private Button pickup = new Button("pickup"); private Button drop = new Button("drop"); private Button load = new Button("load"); private Button save = new Button("save"); private Button run = new Button("run"); private Button pause = new Button("pause"); private Button reset = new Button("reset"); private Button stop = new Button("stop"); public Argus( ) { super("Argus Interactive Environment"); final int WORLD_WIDTH = 120; final int WORLD_HEIGHT = 90; Canvas sep1 = new Canvas( ); Canvas sep2 = new Canvas( ); GridBagLayout lo = new GridBagLayout( ); Insets tight = new Insets(0, 0, 0, 0); Insets loose = new Insets(0, 5, 5, 5); world.setSize(WORLD_WIDTH, WORLD_HEIGHT); world.setBackground(Color.cyan); world.setFrame(parent); sep1.setSize(2, WORLD_HEIGHT); sep1.setBackground(Color.black); sep2.setSize(2, WORLD_HEIGHT); sep2.setBackground(Color.black); program.setFont(new Font("Courier", Font.PLAIN, 12)); feedback.setEditable(false); message("MESSAGES FROM ARGUS APPEAR HERE:"); recursion.setState(true); speed.setUnitIncrement(speed.getMaximum( )/30); speed.setBlockIncrement(speed.getMaximum( )/6); commandMode( ); setLayout(lo); put(lo, 0, 0, 1, 1, tight, new Label("The World of Argus") ); put(lo, 0, 1, 1, 3, loose, world ); put(lo, 0, 4, 9, 1, tight, new Label("Your Argus program:") ); put(lo, 0, 5, 9, 1, loose, program ); put(lo, 0, 6, 9, 1, loose, feedback ); put(lo, 1, 0, 2, 1, tight, new Label("Commands:") ); put(lo, 3, 1, 1, 3, loose, sep1 ); put(lo, 4, 0, 1, 1, tight, new Label("Other:") ); put(lo, 5, 1, 1, 3, loose, sep2 ); put(lo, 6, 0, 2, 1, tight, new Label("Execution:") ); put(lo, 1, 1, 1, 1, loose, forward); put(lo, 2, 1, 1, 1, loose, turn); put(lo, 1, 3, 1, 1, loose, bark); put(lo, 4, 3, 1, 1, loose, reset); put(lo, 1, 2, 1, 1, loose, pickup); put(lo, 2, 2, 1, 1, loose, drop); put(lo, 4, 1, 1, 1, loose, load); put(lo, 4, 2, 1, 1, loose, save); put(lo, 7, 1, 2, 1, loose, recursion); put(lo, 8, 3, 1, 1, tight, speed); put(lo, 7, 3, 1, 1, loose, new Label("Speed:") ); put(lo, 6, 1, 1, 1, loose, run); put(lo, 6, 2, 1, 1, loose, pause); put(lo, 6, 3, 1, 1, loose, stop); forward.addActionListener(new ForwardListener( ) ); turn.addActionListener(new TurnListener( ) ); bark.addActionListener(new BarkListener( ) ); pickup.addActionListener(new PickupListener( ) ); drop.addActionListener(new DropListener( ) ); load.addActionListener(new LoadListener( ) ); save.addActionListener(new SaveListener( ) ); run.addActionListener(new RunListener( ) ); pause.addActionListener(new PauseListener( ) ); reset.addActionListener(new ResetListener( ) ); stop.addActionListener(new StopListener( ) ); addWindowListener(new QuitListener( )); setSize(900, 600); show( ); } public static String strip(String s) { int i; StringBuffer answer = new StringBuffer( ); for (i = 0; i < s.length( ); ++i) { if (s.charAt(i) != '\r') answer.append(s.charAt(i)); } return answer.toString(); } private void put( GridBagLayout layout, int x, int y, int width, int height, Insets i, Component what ) { GridBagConstraints rules = new GridBagConstraints( ); rules.anchor = GridBagConstraints.NORTHWEST; rules.gridx = x; rules.gridy = y; rules.gridwidth = width; rules.gridheight = height; rules.insets = i; if (what instanceof Scrollbar) { if (((Scrollbar)what).getOrientation( ) == Scrollbar.VERTICAL) rules.fill = GridBagConstraints.VERTICAL; else rules.fill = GridBagConstraints.HORIZONTAL; } if (what instanceof TextComponent) rules.fill = GridBagConstraints.HORIZONTAL; layout.setConstraints(what, rules); add(what); } protected void message(String s) { int start, finish; if (s.length( ) == 0) return; start = feedback.getText( ).length( ); if (Character.isWhitespace(s.charAt(0))) start++; feedback.append(s); finish = feedback.getText( ).length( ); feedback.select(start, finish); } class ForwardListener implements ActionListener { public void actionPerformed(ActionEvent event) { world.forward( ); } } class TurnListener implements ActionListener { public void actionPerformed(ActionEvent event) { world.turn( ); } } class BarkListener implements ActionListener { public void actionPerformed(ActionEvent event) { world.bark( ); } } class PickupListener implements ActionListener { public void actionPerformed(ActionEvent event) { world.pickup( ); } } class DropListener implements ActionListener { public void actionPerformed(ActionEvent event) { world.drop( ); } } class LoadListener implements ActionListener { public void actionPerformed(ActionEvent event) { String userName; String userDir; String wholeName; FileDialog fd = new FileDialog(parent, null, FileDialog.LOAD); FileReader fr; File file; StringBuffer buffer = new StringBuffer( ); int c; fd.setFilenameFilter(new ArgusName( )); fd.show( ); parent.setEnabled(false); userName = fd.getFile( ); userDir = fd.getDirectory( ); wholeName = userDir + File.separatorChar + userName; if (userName == null) { parent.setEnabled(true); return; } file = new File(wholeName); if (!file.canRead( ) || !file.isFile( )) { message("\nCannot read " + wholeName + "."); parent.setEnabled(true); return; } try { fr = new FileReader(file); while ((c = fr.read( )) != -1) { if (c != '\r') buffer.append((char) c); } fr.close( ); program.setText(buffer.toString( )); filename = userName; dirname = userDir; } catch (FileNotFoundException e) { message("\nFile " + wholeName + " was not found."); parent.setEnabled(true); return; } catch (IOException e) { message("\nIOException reading " + wholeName + "."); return; } message("\nFile " + wholeName + " was loaded."); parent.setEnabled(true); } } class SaveListener implements ActionListener { public void actionPerformed(ActionEvent event) { String userName; String userDir; String wholeName; FileDialog fd = new FileDialog(parent, null, FileDialog.SAVE); FileWriter fw; File file; String buffer; fd.setFilenameFilter(new ArgusName( )); if (filename != null) fd.setFile(filename); if (dirname != null) fd.setDirectory(dirname); fd.show( ); parent.setEnabled(false); userName = fd.getFile( ); userDir = fd.getDirectory( ); wholeName = userDir + File.separatorChar + userName; if (userName == null) { parent.setEnabled(true); return; } file = new File(wholeName); try { buffer = Argus.strip(program.getText( )); fw = new FileWriter(file); fw.write(buffer, 0, buffer.length( )); fw.close( ); filename = userName; dirname = userDir; } catch (FileNotFoundException e) { message("\nCannot write " + wholeName + "."); parent.setEnabled(true); return; } catch (IOException e) { message("\nIOException writing " + wholeName + "."); return; } message("\nFile " + wholeName + " was saved."); parent.setEnabled(true); } } class RunListener implements ActionListener { public void actionPerformed(ActionEvent event) { Parser parser = new Parser(parent); runningMode( ); definitions = parser.parseProgram( ); if (definitions != null) { interpreter = new Interpreter(parent); interpreter.start( ); // When the interpreter finishes, it will reset to commandMode } else commandMode( ); } } class PauseListener implements ActionListener { public void actionPerformed(ActionEvent event) { if (pause.getLabel( ).equals("pause")) { interpreter.suspend( ); pause.setLabel("resume"); } else { interpreter.resume( ); pause.setLabel("pause"); } } } class StopListener implements ActionListener { public void actionPerformed(ActionEvent event) { if (interpreter.isAlive( )) { interpreter.stop( ); } while (interpreter.isAlive( )) { try { Thread.sleep(50); } catch (InterruptedException e) { // Resume execution } } pause.setLabel("pause"); commandMode( ); } } class ResetListener implements ActionListener { public void actionPerformed(ActionEvent event) { world.reset( ); } } class QuitListener extends WindowAdapter { public void windowClosing(WindowEvent event) { System.exit(0); } } private void runningMode( ) { program.setEditable(false); forward.setEnabled(false); turn.setEnabled(false); pickup.setEnabled(false); drop.setEnabled(false); bark.setEnabled(false); load.setEnabled(false); save.setEnabled(false); run.setEnabled(false); reset.setEnabled(false); pause.setEnabled(true); stop.setEnabled(true); } protected void commandMode( ) { forward.setEnabled(true); turn.setEnabled(true); pickup.setEnabled(true); drop.setEnabled(true); bark.setEnabled(true); load.setEnabled(true); save.setEnabled(true); run.setEnabled(true); reset.setEnabled(true); pause.setEnabled(false); stop.setEnabled(false); program.setEditable(true); } public static void main(String[ ] args) { new Argus( ); } } class ArgusName implements FilenameFilter { public boolean accept(File dir, String name) { return(name.endsWith(".argus")); } } class World extends Canvas { public static final int HEIGHT = 90; public static final int WIDTH = 120; public static final int NORTH = 1; public static final int EAST = 2; public static final int SOUTH = 3; public static final int WEST = 4; private int x = 3; private int y = 2; private int direction = EAST; private int bonex = 4; private int boney = 1; private boolean door = true; private boolean dropped = true; private boolean crashed = false; private Argus argus; public boolean atBone( ) { return (!crashed && bonex == x && boney == y); } public boolean atDoor( ) { if (crashed) return false; if (y != 2) return false; if ((x==1) && (direction==EAST)) return true; if ((x==2) && (direction==WEST)) return true; return false; } public boolean atX(int value) { return (!crashed) && (x == value); } public boolean atY(int value) { return (!crashed) && (y == value); } public void bark( ) { if (crashed) return; if (!atDoor( )) { argus.message("\nArgus can bark only when he's facing the door."); crashed = true; } else if (hasBone( )) { argus.message("\nArgus cannot bark with the bone in his mouth."); crashed = true; } else { door = !door; if (door) argus.message("\nWoof! The door is closed."); else argus.message("\nWoof! The door is open."); } repaint( ); } public void drop( ) { if (crashed) return; if (!dropped) { dropped = true; argus.message("\nArgus has dropped the bone."); } else { argus.message("\nArgus cannot drop the bone because it's not in his mouth."); crashed = true; } repaint( ); } public boolean facing(int value) { return (!crashed) && (direction == value); } public void fail(String s) { if (crashed) return; argus.message(s); crashed = true; repaint( ); } public void forward( ) { if (crashed) return; if (isClear( )) { switch (direction) { case NORTH: y -= 1; break; case EAST: x += 1; break; case SOUTH: y += 1; break; case WEST: x -= 1; break; } argus.message("\nArgus moved to x=" + x + " and y=" + y + "."); } else { argus.message("\nOuch! Argus just ran into a wall."); crashed = true; } if (!dropped) { bonex = x; boney = y; } repaint( ); } public boolean hasBone( ) { return (!crashed) && (!dropped); } public Image image( ) { int width = getSize( ).width; int height = getSize( ).height; int row; int col; int[ ] spotCol = new int[5]; int[ ] spotRow = new int[4]; Image im = createImage(width, height); Graphics g = im.getGraphics( ); g.setColor(Color.black); int i; // Calculate the coordinate spots for (row = 1; row <= 3; row++) spotRow[row] = (height / 6) * (2*row - 1); for (col = 1; col <= 4; col++) spotCol[col] = (width / 8) * (2*col - 1); for (row = 1; row <= 3; row++) for (col = 1; col <= 4; col++) g.fillOval(spotCol[col], spotRow[row], 3, 3); // Draw the walls g.drawRect(0, 0, width-1, height-1); g.drawRect(1, 1, width-3, height-3); g.drawRect((spotCol[1]+spotCol[2])/2, (spotRow[1]+spotRow[2])/2, width/2, height/3); if (door) g.setColor(Color.lightGray); else g.setColor(getBackground( )); g.drawLine( (spotCol[1]+spotCol[2])/2, (spotRow[1]+spotRow[2])/2, (spotCol[1]+spotCol[2])/2, (spotRow[1]+spotRow[2])/2 + height/3); g.drawLine( (8+spotCol[1]+spotCol[2])/2, (spotRow[1]+spotRow[2])/2, (8+spotCol[1]+spotCol[2])/2, (spotRow[1]+spotRow[2])/2 + height/3); if (dropped || (direction == EAST) || (direction == WEST)) { imageBone(g, spotCol[bonex], spotRow[boney], spotCol[1], spotRow[1]/3); imageArgus(g, spotCol[x], spotRow[y], spotCol[1], spotRow[1]); } else { imageBone(g, spotCol[bonex], spotRow[boney], spotCol[1]/3, spotRow[1]); imageArgus(g, spotCol[x], spotRow[y], spotCol[1], spotRow[1]); } if (crashed) { g.setColor(Color.red); Polygon p = new Polygon( ); p.addPoint((spotCol[1]+spotCol[2])/2-3, spotRow[1]+3); p.addPoint((spotCol[1]+spotCol[2])/2+3, spotRow[1]-3); p.addPoint((spotCol[3]+spotCol[4])/2+3, spotRow[3]-3); p.addPoint((spotCol[3]+spotCol[4])/2-3, spotRow[3]+3); g.fillPolygon(p); Polygon q = new Polygon( ); q.addPoint((spotCol[3]+spotCol[4])/2-3, spotRow[1]-3); q.addPoint((spotCol[3]+spotCol[4])/2+3, spotRow[1]+3); q.addPoint((spotCol[1]+spotCol[2])/2+3, spotRow[3]+3); q.addPoint((spotCol[1]+spotCol[2])/2-3, spotRow[3]-3); g.fillPolygon(q); } return im; } public void imageArgus(Graphics g, int x, int y, int width, int height) { g.setColor(Color.yellow); x -= width/2; y -= height/2; switch(direction) { case NORTH: g.fillArc(x, y, width, height, 135, 270); break; case EAST: g.fillArc(x, y, width, height, 45, 270); break; case SOUTH: g.fillArc(x, y, width, height, 315, 270); break; case WEST: g.fillArc(x, y, width, height, 225, 270); break; } } public void imageBone(Graphics g, int x, int y, int width, int height) { if (dropped) g.setColor(Color.lightGray); else g.setColor(Color.black); x -= width/2; y -= height/2; g.fillOval(x, y, width, height); } public boolean isCrashed( ) { return crashed; } public boolean isInside( ) { return (!crashed) && (y == 2) && (x >= 2) && (x <= 3); } public boolean isClear( ) { if (!crashed) switch (direction) { case NORTH: if (y == 1) return false; else return (x < 2) || (x > 3); case EAST: if (x == 1) return (y != 2) || (!door); else if (x == 4) return false; else return (x != 3) || (y != 2); case SOUTH: if (y == 3) return false; else return (x < 2) || (x > 3); case WEST: if (x == 2) return (y != 2) || (!door); else if (x == 1) return false; else return (x != 4) || (y != 2); } return false; } public void paint(Graphics g) { g.drawImage(image( ), 0, 0, null); } public void pickup( ) { if (crashed) return; if (!atBone( )) { argus.message("\nArgus must be on top of the bone to pick it up."); crashed = true; } else if (!dropped) { argus.message("\nArgus cannot pick up the bone--he already had it."); crashed = true; } else { argus.message("\nArgus has picked up the bone."); dropped = false; } repaint( ); } public void reset( ) { x = 3; y = 2; direction = EAST; bonex = 4; boney = 1; door = true; dropped = true; crashed = false; argus.message("\nArgus has been reset."); repaint( ); } public void setFrame(Argus a) { argus = a; } public void turn( ) { if (crashed) return; direction = (direction % 4) + 1; switch(direction) { case NORTH: argus.message("\nArgus turned North."); break; case EAST: argus.message("\nArgus turned East."); break; case SOUTH: argus.message("\nArgus turned South."); break; case WEST: argus.message("\nArgus turned West."); break; } repaint( ); } public void update(Graphics g) { paint(g); } } class Parser { private final int ERROR = -1; private final int IDENTIFIER = 0; private final int EOF = 1; private final int COMMAND = 2; private final int PREDICATE = 3; private final int ASSERT = 4; private final int SEMICOLON = 5; private final int IF = 7; private final int ELSE = 8; private final int WHILE = 9; private final int PROCEDURE = 10; private final int OPEN_PAREN = 11; private final int CLOSE_PAREN = 12; private final int OPEN_BRACKET = 13; private final int CLOSE_BRACKET = 14; private final int BINARY = 15; private final int UNARY = 16; private Argus argus; private String text; private int next; private String token; private int index; private int type; private String currentProcedure; private Hashtable identifiers = new Hashtable( ); private Hashtable definitions = new Hashtable( ); public Parser(Argus a) { argus = a; } public Hashtable parseProgram( ) { Enumeration names; text = new String(argus.program.getText( )); text = Argus.strip(text); next = 0; identifiers.clear( ); definitions.clear( ); argus.message("\nParsing program..."); while((lex( ) != EOF)) { unlex( ); if (!parseProcedure( )) { argus.program.requestFocus( ); argus.program.select(index, index+token.length( )); return null; } } names = identifiers.keys( ); while (names.hasMoreElements( )) { token = (String) names.nextElement( ); if (!definitions.containsKey(token)) { argus.message("\nUnknown identifier: " + token + "."); index = ((Integer)identifiers.get(token)).intValue( ); argus.program.requestFocus( ); argus.program.select(index, index+token.length( )); return null; } } argus.message("...parsing successful."); return definitions; } private boolean parseProcedure( ) { if (lex( ) != PROCEDURE) { argus.message("\nKeyword 'procedure' is expected."); return false; } if ((lex( ) != IDENTIFIER)) { argus.message("\nProcedure name expected."); return false; } if (definitions.containsKey(token)) { argus.message("\nDuplicate identifier."); return false; } else { definitions.put(token, new Integer(index)); currentProcedure = token; } if (lex( ) != OPEN_BRACKET) { argus.message("\nOpen bracket expected."); return false; } unlex( ); return parseBlock( ); } private boolean parseBlock( ) { if (lex( ) != OPEN_BRACKET) { argus.message("\nOpen bracket expected."); return false; } while (lex( ) != CLOSE_BRACKET) { unlex( ); if (!parseStatement( )) return false; } return true; } private boolean parseStatement( ) { int control = lex( ); unlex( ); switch(control) { case OPEN_BRACKET: return parseBlock( ); case COMMAND: case IDENTIFIER: return parseSimple( ); case SEMICOLON: lex( ); return true; case ASSERT: return parseAssert( ); case IF: return parseIf( ); case WHILE: return parseWhile( ); } argus.message("\nStart of statement or block expected."); return false; } private boolean parseAssert( ) { if (lex( ) != ASSERT) { argus.message("\nKeyword 'assert' is expected."); return false; } if (lex( ) != OPEN_PAREN) { argus.message("\nOpening parenthesis is expected after 'assert'."); return false; } if (!parseBoolean( )) return false; if (lex( ) != SEMICOLON) { argus.message("\nSemi-colon expected after assertion."); return false; } return true; } private boolean parseWhile( ) { if (lex( ) != WHILE) { argus.message("\nKeyword 'while' is expected."); return false; } if (lex( ) != OPEN_PAREN) { argus.message("\nOpening parenthesis is expected after 'while'."); return false; } if (!parseBoolean( )) return false; return parseStatement( ); } private boolean parseIf( ) { if (lex( ) != IF) { argus.message("\nKeyword 'if' is expected."); return false; } if (lex( ) != OPEN_PAREN) { argus.message("\nOpening parenthesis is expected after 'if'."); return false; } if (!parseBoolean( )) return false; if (!parseStatement( )) return false; if (lex( ) == ELSE) return parseStatement( ); else unlex( ); return true; } private boolean parseBoolean( ) { // Parses Boolean expression -- an opening paren has already been read. switch (lex( )) { case UNARY: return parseBoolean( ); case OPEN_PAREN: if (!parseBoolean( )) return false; return parseRest( ); case PREDICATE: return parseRest( ); } argus.message("\nBoolean expression is expected"); return false; } private boolean parseRest( ) { // Parses the rest of a Boolean expression, after "( xxx" switch (lex( )) { case CLOSE_PAREN: return true; case BINARY: return parseBoolean( ); } argus.message("\nClosing parenthesis is expected."); return false; } private boolean parseSimple( ) { int control = lex( ); if (control == SEMICOLON) return true; else if (control == COMMAND) { if (lex( ) != SEMICOLON) { argus.message("\nSemi-colon expected."); return false; } return true; } else if (control == IDENTIFIER) { if (!argus.recursion.getState( )) { if (token.equals(currentProcedure)) { argus.message("\nRecusion is not allowed."); return false; } if (!definitions.containsKey(token)) { argus.message("\nProcedure " + token + " must be defined before use."); return false; } } if (!identifiers.containsKey(token)) identifiers.put(token, new Integer(index)); if (lex( ) != SEMICOLON) { argus.message("\nSemi-colon expected."); return false; } return true; } else { argus.message("\nStart of statement expected."); return false; } } private void unlex( ) { next = index; } private int lex( ) { final String WHITE = " \t\n\r"; final String EOLN = "\n\r"; int length; // Skip white space while ((next < text.length( )) && (WHITE.indexOf(text.charAt(next)) >= 0)) next++; // Check for EOF token = ""; index = next; if (next >= text.length( )) return (type = EOF); // Check for comment if (text.charAt(next) == '/') { next++; if ((next < text.length( )) && (text.charAt(next) == '/')) { // Read and discard the rest of the line. while ((next < text.length( )) && (EOLN.indexOf(text.charAt(next)) == -1)) next++; return lex( ); } else { // Illegal slash / in the code token = "/"; return (type = ERROR); } } // Check for identifier if (Character.isLetter(text.charAt(next))) { length = 0; do { next++; length++; } while ((next < text.length( )) && (Character.isLetterOrDigit(text.charAt(next)))); token = text.substring(index, index+length); if (token.equals("atBone")) return (type = PREDICATE); if (token.equals("atDoor")) return (type = PREDICATE); if (token.equals("hasBone")) return (type = PREDICATE); if (token.equals("atX1")) return (type = PREDICATE); if (token.equals("atX2")) return (type = PREDICATE); if (token.equals("atX3")) return (type = PREDICATE); if (token.equals("atX4")) return (type = PREDICATE); if (token.equals("atY1")) return (type = PREDICATE); if (token.equals("atY2")) return (type = PREDICATE); if (token.equals("atY3")) return (type = PREDICATE); if (token.equals("true")) return (type = PREDICATE); if (token.equals("false")) return (type = PREDICATE); if (token.equals("facingNorth")) return (type = PREDICATE); if (token.equals("facingEast")) return (type = PREDICATE); if (token.equals("facingSouth")) return (type = PREDICATE); if (token.equals("facingWest")) return (type = PREDICATE); if (token.equals("isInside")) return (type = PREDICATE); if (token.equals("isClear")) return (type = PREDICATE); if (token.equals("assert")) return (type = ASSERT); if (token.equals("if")) return (type = IF); if (token.equals("else")) return (type = ELSE); if (token.equals("while")) return (type = WHILE); if (token.equals("procedure")) return (type = PROCEDURE); if (token.equals("forward")) return (type = COMMAND); if (token.equals("turn")) return (type = COMMAND); if (token.equals("drop")) return (type = COMMAND); if (token.equals("pickup")) return (type = COMMAND); if (token.equals("bark")) return (type = COMMAND); return (type = IDENTIFIER); } // Check for binary operations if ((text.charAt(next) == '&') || (text.charAt(next) == '|')) { next++; if ((next < text.length( )) && (text.charAt(next) == text.charAt(index))) { next++; token = text.substring(index, index+2); return (type = BINARY); } else { token = text.substring(index, index+1); return (type = ERROR); } } // Check for special characters token = text.substring(index, index+1); next++; switch(token.charAt(0)) { case ';' : return (type = SEMICOLON); case '!' : return (type = UNARY); case '(' : return (type = OPEN_PAREN); case ')' : return (type = CLOSE_PAREN); case '{' : return (type = OPEN_BRACKET); case '}' : return (type = CLOSE_BRACKET); } return (type = ERROR); } } class Interpreter extends Thread { static private final int ERROR = -1; static private final int IDENTIFIER = 0; static private final int EOF = 1; static private final int COMMAND = 2; static private final int PREDICATE = 3; static private final int ASSERT = 4; static private final int SEMICOLON = 5; static private final int IF = 7; static private final int ELSE = 8; static private final int WHILE = 9; static private final int PROCEDURE = 10; static private final int OPEN_PAREN = 11; static private final int CLOSE_PAREN = 12; static private final int OPEN_BRACKET = 13; static private final int CLOSE_BRACKET = 14; static private final int BINARY = 15; static private final int UNARY = 16; static private final Character NOT = new Character('!'); static private final Character AND = new Character('&'); static private final Character OR = new Character('|'); static private final Character OPEN = new Character('('); private Argus argus; private String text; private int next; private String token; private int index; private int type; private boolean highlighting; public Interpreter(Argus a) { super( ); argus = a; } public void run( ) { text = new String(argus.program.getText( )); text = Argus.strip(text); if (!argus.definitions.containsKey("main")) { argus.world.fail("Program is missing 'main' procedure."); return; } highlighting = true; runProcedure("main"); argus.commandMode( ); } private void delay( ) { try { Thread.sleep(argus.speed.getMaximum( )-argus.speed.getValue( )); } catch (InterruptedException e) { // Resume execution } } private boolean runProcedure(String name) { getStart(name); // At this point, lex is about to read the procedure name. lex( ); // name delay( ); return runBlock( ); } private boolean runBlock( ) { lex( ); // { while (lex( ) != CLOSE_BRACKET) { unlex( ); if (!runStatement( )) return false; } return true; } private boolean runStatement( ) { int control = lex( ); unlex( ); switch(control) { case OPEN_BRACKET: return runBlock( ); case COMMAND: case IDENTIFIER: return runSimple( ); case SEMICOLON: lex( ); return true; case ASSERT: return runAssert( ); case IF: return runIf( ); case WHILE: return runWhile( ); } return false; } private boolean runAssert( ) { lex( ); // assert delay( ); lex( ); // ( if (!evalBoolean( )) { argus.world.fail("Assertion failed."); return false; } lex( ); // ; return true; } private boolean runWhile( ) { int start; lex( ); // while delay( ); lex( ); // ( start = next; while (evalBoolean( )) { if (!runStatement( )) return false; next = start; } highlighting = false; skipStatement( ); highlighting = true; return true; } private boolean runIf( ) { boolean eval; lex( ); // if delay( ); lex( ); // ( eval = evalBoolean( ); if (eval) { if (!runStatement( )) return false; } else { highlighting = false; skipStatement( ); highlighting = true; } highlighting = false; if (lex( ) != ELSE) { unlex( ); highlighting = true; return true; } if (!eval) { highlighting = true; if (!runStatement( )) return false; } else { skipStatement( ); highlighting = true; } return true; } private void evalStackTops(Stack operations, Stack values) { boolean value, value1, value2; switch (((Character)operations.pop( )).charValue( )) { case '!': value = ((Boolean)values.pop( )).booleanValue( ); values.push(new Boolean(!value)); break; case '&': value2 = ((Boolean)values.pop( )).booleanValue( ); value1 = ((Boolean)values.pop( )).booleanValue( ); values.push(new Boolean(value1 && value2)); break; case '|': value2 = ((Boolean)values.pop( )).booleanValue( ); value1 = ((Boolean)values.pop( )).booleanValue( ); values.push(new Boolean(value1 || value2)); break; } } private boolean evalBoolean( ) { // Evaluates Boolean expression -- an opening paren has already been read. Stack operations = new Stack( ); Stack values = new Stack( ); boolean value = false; operations.push(OPEN); // The opening paren that's already read while (!operations.isEmpty( )) { switch (lex( )) { case UNARY: operations.push(NOT); break; case BINARY: if (token.charAt(0) == '&') { while ( (!operations.isEmpty( )) && (operations.peek( ) != OPEN) && (operations.peek( ) != OR) ) evalStackTops(operations, values); operations.push(AND); } else { while ( (!operations.isEmpty( )) && (operations.peek( ) != OPEN) ) evalStackTops(operations, values); operations.push(OR); } break; case OPEN_PAREN: operations.push(OPEN); break; case CLOSE_PAREN: // Pop stack until reaching the corresponding OPEN while (operations.peek( ) != OPEN) evalStackTops(operations, values); operations.pop( ); break; case PREDICATE: if (token.equals("atBone")) value = argus.world.atBone( ); else if (token.equals("atDoor")) value = argus.world.atDoor( ); else if (token.equals("hasBone")) value = argus.world.hasBone( ); else if (token.equals("atX1")) value = argus.world.atX(1); else if (token.equals("atX2")) value = argus.world.atX(2); else if (token.equals("atX3")) value = argus.world.atX(3); else if (token.equals("atX4")) value = argus.world.atX(4); else if (token.equals("atY1")) value = argus.world.atY(1); else if (token.equals("atY2")) value = argus.world.atY(2); else if (token.equals("atY3")) value = argus.world.atY(3); else if (token.equals("true")) value = true; else if (token.equals("false")) value = false; else if (token.equals("facingNorth")) value = argus.world.facing(World.NORTH); else if (token.equals("facingEast")) value = argus.world.facing(World.EAST); else if (token.equals("facingSouth")) value = argus.world.facing(World.SOUTH); else if (token.equals("facingWest")) value = argus.world.facing(World.WEST); else if (token.equals("isInside")) value = argus.world.isInside( ); else if (token.equals("isClear")) value = argus.world.isClear( ); values.push(new Boolean(value)); break; } } return ((Boolean)values.pop( )).booleanValue( ); } private boolean runSimple( ) { int returnLocation; delay( ); switch(lex( )) { case SEMICOLON: return true; case COMMAND: if (token.equals("forward")) argus.world.forward( ); else if (token.equals("turn")) argus.world.turn( ); else if (token.equals("bark")) argus.world.bark( ); else if (token.equals("drop")) argus.world.drop( ); else if (token.equals("pickup")) argus.world.pickup( ); lex( ); // The semi-colon return (!argus.world.isCrashed( )); case IDENTIFIER: returnLocation = next; if (!runProcedure(token)) return false; next = returnLocation; return true; } return false; } private void getStart(String name) { next = ((Integer)argus.definitions.get(name)).intValue( ); } private void highlight( ) { int begin, finish; finish = next; while ((finish < text.length( )) && (text.charAt(finish) != '\n')) finish++; begin = next; while ((begin > 0) && (text.charAt(begin-1) != '\n')) begin--; argus.program.select(begin, finish); } private void unlex( ) { next = index; } private int lex( ) { final String WHITE = " \t\n\r"; final String EOLN = "\n\r"; int length; // Skip white space while ((next < text.length( )) && (WHITE.indexOf(text.charAt(next)) >= 0)) next++; // Check for EOF token = ""; index = next; if (next >= text.length( )) return (type = EOF); // Check for comment if (text.charAt(next) == '/') { next++; if ((next < text.length( )) && (text.charAt(next) == '/')) { // Read and discard the rest of the line. while ((next < text.length( )) && (EOLN.indexOf(text.charAt(next)) == -1)) next++; return lex( ); } else { // Illegal slash / in the code token = "/"; return (type = ERROR); } } if (highlighting) highlight( ); // Check for identifier if (Character.isLetter(text.charAt(next))) { length = 0; do { next++; length++; } while ((next < text.length( )) && (Character.isLetterOrDigit(text.charAt(next)))); token = text.substring(index, index+length); if (token.equals("atBone")) return (type = PREDICATE); if (token.equals("atDoor")) return (type = PREDICATE); if (token.equals("hasBone")) return (type = PREDICATE); if (token.equals("atX1")) return (type = PREDICATE); if (token.equals("atX2")) return (type = PREDICATE); if (token.equals("atX3")) return (type = PREDICATE); if (token.equals("atX4")) return (type = PREDICATE); if (token.equals("atY1")) return (type = PREDICATE); if (token.equals("atY2")) return (type = PREDICATE); if (token.equals("atY3")) return (type = PREDICATE); if (token.equals("true")) return (type = PREDICATE); if (token.equals("false")) return (type = PREDICATE); if (token.equals("facingNorth")) return (type = PREDICATE); if (token.equals("facingEast")) return (type = PREDICATE); if (token.equals("facingSouth")) return (type = PREDICATE); if (token.equals("facingWest")) return (type = PREDICATE); if (token.equals("isInside")) return (type = PREDICATE); if (token.equals("isClear")) return (type = PREDICATE); if (token.equals("assert")) return (type = ASSERT); if (token.equals("if")) return (type = IF); if (token.equals("else")) return (type = ELSE); if (token.equals("while")) return (type = WHILE); if (token.equals("procedure")) return (type = PROCEDURE); if (token.equals("forward")) return (type = COMMAND); if (token.equals("turn")) return (type = COMMAND); if (token.equals("drop")) return (type = COMMAND); if (token.equals("pickup")) return (type = COMMAND); if (token.equals("bark")) return (type = COMMAND); return (type = IDENTIFIER); } // Check for binary operations if ((text.charAt(next) == '&') || (text.charAt(next) == '|')) { next++; if ((next < text.length( )) && (text.charAt(next) == text.charAt(index))) { next++; token = text.substring(index, index+2); return (type = BINARY); } else { token = text.substring(index, index+1); return (type = ERROR); } } // Check for special characters token = text.substring(index, index+1); next++; switch(token.charAt(0)) { case ';' : return (type = SEMICOLON); case '!' : return (type = UNARY); case '(' : return (type = OPEN_PAREN); case ')' : return (type = CLOSE_PAREN); case '{' : return (type = OPEN_BRACKET); case '}' : return (type = CLOSE_BRACKET); } return (type = ERROR); } private void skipBlock( ) { lex( ); // Opening bracket while (lex( ) != CLOSE_BRACKET) { unlex( ); skipStatement( ); } } private void skipStatement( ) { int control = lex( ); unlex( ); switch(control) { case OPEN_BRACKET: skipBlock( ); return; case COMMAND: case IDENTIFIER: skipSimple( ); return; case SEMICOLON: lex( ); return; case ASSERT: skipAssert( ); return; case IF: skipIf( ); return; case WHILE: skipWhile( ); return; } } private void skipAssert( ) { lex( ); // assert lex( ); // Open parenthesis skipBoolean( ); lex( ); // semicolon } private void skipWhile( ) { lex( ); // while lex( ); // Open parenthesis skipBoolean( ); skipStatement( ); } private void skipIf( ) { lex( ); // if lex( ); // Open parenthesis skipBoolean( ); skipStatement( ); if (lex( ) == ELSE) skipStatement( ); else unlex( ); } private void skipBoolean( ) { // Skips Boolean expression -- an opening paren has already been read. switch (lex( )) { case UNARY: skipBoolean( ); return; case OPEN_PAREN: skipBoolean( ); skipRest( ); return; case PREDICATE: skipRest( ); return; } } private void skipRest( ) { // Skip the rest of a Boolean expression, after "( xxx" switch (lex( )) { case CLOSE_PAREN: return; case BINARY: skipBoolean( ); return; } } private void skipSimple( ) { if (lex( ) != SEMICOLON) lex( ); } }