// Tiny Java Logo 1.0 // a simple interpreter for the "Logo" programming language // (C) Copyright 1996 by Troy N. Stephens, All Rights Reserved. // http://homepage.mac.com/troy_stephens/TinyJavaLogo/ import java.awt.*; import java.applet.*; import java.util.Hashtable; import java.util.Vector; public class TinyJavaLogo extends Applet { LogoScanner scanner = new LogoScanner(); TextField messageArea; TurtleGraphicsPane turtleGraphicsPane; CommandEntryField inputArea; LogoToken token; Hashtable dictionary = new Hashtable(); Hashtable colorTable = new Hashtable(); Color penColor; public void init() { setLayout( new BorderLayout() ); add( "North", messageArea = new TextField() ); add( "South", inputArea = new CommandEntryField( this ) ); add( "Center", turtleGraphicsPane = new TurtleGraphicsPane() ); messageArea.setEditable( false ); messageArea.setText( "Welcome to Tiny Java Logo!" ); inputArea.requestFocus(); colorTable.put( "blue", Color.blue ); colorTable.put( "cyan", Color.cyan ); colorTable.put( "darkgray", Color.darkGray ); colorTable.put( "gray", Color.gray ); colorTable.put( "green", Color.green ); colorTable.put( "lightgray", Color.lightGray ); colorTable.put( "magenta", Color.magenta ); colorTable.put( "orange", Color.orange ); colorTable.put( "pink", Color.pink ); colorTable.put( "red", Color.red ); colorTable.put( "white", Color.white ); colorTable.put( "yellow", Color.yellow ); turtleGraphicsPane.setPenColor( penColor = Color.green ); } public boolean gotFocus( Event evt, Object what ) { inputArea.requestFocus(); return false; } void message( String messageText ) { messageArea.setText( messageText ); } void error( String messageText ) { message( "Error: " + messageText ); } void parseCommandBlock( int nestLevel ) { String commandName; String code; token = scanner.getToken(); while (token.kind != LogoToken.END_OF_INPUT && token.kind != LogoToken.INVALID_TOKEN) { switch (token.kind) { case LogoToken.FORWARD: token = scanner.getToken(); if (token.kind == LogoToken.FLOAT || token.kind == LogoToken.INTEGER) { turtleGraphicsPane.forward( token.value ); token = scanner.getToken(); } else { error( "FORWARD requires distance" ); return; } break; case LogoToken.BACK: token = scanner.getToken(); if (token.kind == LogoToken.FLOAT || token.kind == LogoToken.INTEGER) { turtleGraphicsPane.back( token.value ); token = scanner.getToken(); } else { error( "BACK requires distance" ); return; } break; case LogoToken.LEFT: token = scanner.getToken(); if (token.kind == LogoToken.FLOAT || token.kind == LogoToken.INTEGER) { turtleGraphicsPane.left( token.value ); token = scanner.getToken(); } else { error( "LEFT requires turn angle" ); return; } break; case LogoToken.RIGHT: token = scanner.getToken(); if (token.kind == LogoToken.FLOAT || token.kind == LogoToken.INTEGER) { turtleGraphicsPane.right( token.value ); token = scanner.getToken(); } else { error( "RIGHT requires turn angle" ); return; } break; case LogoToken.PENUP: turtleGraphicsPane.penUp(); token = scanner.getToken(); break; case LogoToken.PENDOWN: turtleGraphicsPane.penDown(); token = scanner.getToken(); break; case LogoToken.HIDETURTLE: turtleGraphicsPane.hideTurtle(); token = scanner.getToken(); break; case LogoToken.SHOWTURTLE: turtleGraphicsPane.showTurtle(); token = scanner.getToken(); break; case LogoToken.CLEARSCREEN: turtleGraphicsPane.clearScreen(); token = scanner.getToken(); break; case LogoToken.REPEAT: token = scanner.getToken(); if (token.kind != LogoToken.INTEGER) { error( "REPEAT requires positive integer count" ); return; } int count = token.intValue; token = scanner.getToken(); if (token.kind != '[') { error( "REPEAT requires block in []" ); return; } int blockStart = scanner.getPosition(); while (count-- > 0) { scanner.setPosition( blockStart ); parseCommandBlock( nestLevel + 1 ); } token = scanner.getToken(); break; case LogoToken.TO: token = scanner.getToken(); if (token.kind != LogoToken.IDENTIFIER) { error( "TO requires name for new definition" ); return; } commandName = token.lexeme; if (dictionary.get( commandName ) != null) { message( "Redefining command " + commandName ); } else { message( "Defining new command " + commandName ); } code = scanner.getRestAsString(); dictionary.put( commandName, code ); token = scanner.getToken(); break; case LogoToken.IDENTIFIER: commandName = token.lexeme; code = (String) dictionary.get( commandName ); if (code == null) { error( "Undefined command " + commandName ); return; } String savedCommand = scanner.getSourceString(); int savedPosition = scanner.getPosition(); scanner.setSourceString( code ); parseCommandBlock( 0 ); scanner.setSourceString( savedCommand ); scanner.setPosition( savedPosition ); token = scanner.getToken(); break; case LogoToken.SETPC: token = scanner.getToken(); Color newPenColor = (Color) colorTable.get( token.lexeme ); if (newPenColor == null) { error( "Unrecognized color name" ); return; } penColor = newPenColor; turtleGraphicsPane.setPenColor( penColor ); token = scanner.getToken(); break; case '[': token = scanner.getToken(); break; case ']': if (nestLevel == 0) { error( "] without matching [" ); token = scanner.getToken(); return; } return; default: error( "Unrecognized symbol in input" ); return; } } } public void doCommandLine( String commandLine ) { message( commandLine ); scanner.setSourceString( commandLine ); parseCommandBlock( 0 ); inputArea.setText( "" ); } } class LogoToken { public final static int END_OF_INPUT = 256; public final static int INVALID_TOKEN = 257; public final static int IDENTIFIER = 258; public final static int FLOAT = 259; public final static int INTEGER = 270; public final static int FORWARD = 260; public final static int BACK = 261; public final static int LEFT = 262; public final static int RIGHT = 263; public final static int PENUP = 264; public final static int PENDOWN = 265; public final static int HIDETURTLE = 266; public final static int SHOWTURTLE = 267; public final static int CLEARSCREEN = 268; public final static int REPEAT = 269; public final static int TO = 271; public final static int SETPC = 272; public int kind; public String lexeme; public float value; public int intValue; } class LogoScanner { Hashtable keywordTable; char sourceString[]; int sourceLength; int i; public LogoScanner() { keywordTable = new Hashtable(); keywordTable.put( "forward", new Integer(LogoToken.FORWARD) ); keywordTable.put( "fd", new Integer(LogoToken.FORWARD) ); keywordTable.put( "back", new Integer(LogoToken.BACK) ); keywordTable.put( "bk", new Integer(LogoToken.BACK) ); keywordTable.put( "right", new Integer(LogoToken.RIGHT) ); keywordTable.put( "rt", new Integer(LogoToken.RIGHT) ); keywordTable.put( "left", new Integer(LogoToken.LEFT) ); keywordTable.put( "lt", new Integer(LogoToken.LEFT) ); keywordTable.put( "penup", new Integer(LogoToken.PENUP) ); keywordTable.put( "pu", new Integer(LogoToken.PENUP) ); keywordTable.put( "pendown", new Integer(LogoToken.PENDOWN) ); keywordTable.put( "pd", new Integer(LogoToken.PENDOWN) ); keywordTable.put( "hideturtle", new Integer(LogoToken.HIDETURTLE) ); keywordTable.put( "ht", new Integer(LogoToken.HIDETURTLE) ); keywordTable.put( "showturtle", new Integer(LogoToken.SHOWTURTLE) ); keywordTable.put( "st", new Integer(LogoToken.SHOWTURTLE) ); keywordTable.put( "clearscreen",new Integer(LogoToken.CLEARSCREEN) ); keywordTable.put( "cs", new Integer(LogoToken.CLEARSCREEN) ); keywordTable.put( "repeat", new Integer(LogoToken.REPEAT) ); keywordTable.put( "rep", new Integer(LogoToken.REPEAT) ); keywordTable.put( "to", new Integer(LogoToken.TO) ); keywordTable.put( "setpc", new Integer(LogoToken.SETPC) ); keywordTable.put( "pc", new Integer(LogoToken.SETPC) ); } public int getPosition() { return i; } public void setPosition( int newPosition ) { if (i >= 0 && i <= sourceLength) i = newPosition; else i = sourceLength; } public void setSourceString( String newSourceString ) { sourceLength = newSourceString.length(); sourceString = newSourceString.concat("\0").toCharArray(); i = 0; } public String getSourceString() { return new String( sourceString ); } public String getRestAsString() { skipWhitespace(); String rest = new String( sourceString, i, sourceLength - i + 1 ); i = sourceLength; return rest; } void skipWhitespace() { char c; do { c = sourceString[i++]; } while (c == ' ' || c == '\t'); --i; } public LogoToken getToken() { LogoToken token = new LogoToken(); StringBuffer lexeme = new StringBuffer(); char c; if (i >= sourceLength) { token.kind = LogoToken.END_OF_INPUT; return token; } // Skip whitespace. skipWhitespace(); c = sourceString[i++]; // Now figure out what kind of token we've got. if (c == '[' || c == ']') { token.kind = c; token.lexeme = String.valueOf( c ); } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { do { lexeme.append( c ); c = sourceString[i++]; } while ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); --i; token.lexeme = lexeme.toString(); token.kind = LogoToken.IDENTIFIER; Integer keyword = (Integer)keywordTable.get( token.lexeme ); if (keyword != null) { token.kind = keyword.intValue(); } } else if (c >= '0' && c <= '9') { do { lexeme.append( c ); c = sourceString[i++]; } while (c >= '0' && c <= '9'); boolean hasDecimalPart = false; if (c == '.') { do { lexeme.append( c ); c = sourceString[i++]; } while (c >= '0' && c <= '9'); hasDecimalPart = true; } --i; token.lexeme = lexeme.toString(); token.value = Float.valueOf( token.lexeme ).floatValue(); if (hasDecimalPart) { token.kind = LogoToken.FLOAT; } else { token.kind = LogoToken.INTEGER; token.intValue = Integer.valueOf( token.lexeme ).intValue(); } } else if (c == 0) { --i; token.kind = LogoToken.END_OF_INPUT; } else { --i; token.kind = LogoToken.INVALID_TOKEN; } return token; } } class CommandEntryField extends TextField { TinyJavaLogo parentApplet; public CommandEntryField( TinyJavaLogo newParentApplet ) { parentApplet = newParentApplet; } public boolean keyDown( Event evt, int key ) { if (key == 13 || key == 10) { parentApplet.doCommandLine( getText() ); return true; } return false; } } class TurtleGraphicsPane extends Canvas { float x = 0f; float y = 0f; float turtleDirection = 90f; int turtleHideLevel = 0; int vx[] = new int[4]; int vy[] = new int[4]; int centerx = 100; int centery = 100; boolean penIsDown = true; Color penColor = Color.green; Vector lines = new Vector(50,50); Vector colors = new Vector(50,50); public TurtleGraphicsPane() { setBackground( Color.black ); calcTurtleOutline(); } float dtor( float degrees ) { return (float)(degrees * Math.PI / 180.0); } void calcTurtleOutline() { // Calculate turtle outline vertices. float angle = dtor( turtleDirection ); vx[0] = centerx + (int)(x + 15f * Math.cos( angle )); vy[0] = centery - (int)(y + 15f * Math.sin( angle )); angle += 2f * Math.PI / 3f; vx[1] = centerx + (int)(x + 10f * Math.cos( angle )); vy[1] = centery - (int)(y + 10f * Math.sin( angle )); angle += 2f * Math.PI / 3f; vx[2] = centerx + (int)(x + 10f * Math.cos( angle )); vy[2] = centery - (int)(y + 10f * Math.sin( angle )); vx[3] = vx[0]; vy[3] = vy[0]; } void drawTurtle( Graphics g ) { g.setXORMode( Color.black ); g.setColor( Color.white ); g.drawPolygon( vx, vy, 4 ); g.setPaintMode(); } public void showTurtle() { if (turtleHideLevel > 0) { if (--turtleHideLevel == 0) { Graphics g = getGraphics(); if (g != null) { drawTurtle( g ); } } } } public void hideTurtle() { if (turtleHideLevel == 0) { Graphics g = getGraphics(); if (g != null) { drawTurtle( g ); } } ++turtleHideLevel; } public void setPenColor( Color newPenColor ) { penColor = newPenColor; } public void clearScreen() { x = 0f; y = 0f; turtleDirection = 90f; calcTurtleOutline(); lines.removeAllElements(); colors.removeAllElements(); repaint(); } void addLine( Graphics g, float x1, float y1, float x2, float y2 ) { Rectangle r; lines.addElement( r = new Rectangle( (int)x1, (int)y1, (int)x2, (int)y2 ) ); colors.addElement( penColor ); g.setColor( penColor ); g.drawLine( centerx + r.x, centery - r.y, centerx + r.width, centery - r.height ); } public void forward( float distance ) { float angle = dtor( turtleDirection ); float newX = (float)(x + distance * Math.cos( angle )); float newY = (float)(y + distance * Math.sin( angle )); hideTurtle(); if (penIsDown) { Graphics g = getGraphics(); if (g != null) { addLine( g, x, y, newX, newY ); } x = newX; y = newY; calcTurtleOutline(); } else { x = newX; y = newY; calcTurtleOutline(); } showTurtle(); } public void back( float distance ) { forward( -distance ); } public void left( float turnAngle ) { Graphics g = getGraphics(); if (g != null) { hideTurtle(); turtleDirection += turnAngle; while (turtleDirection > 360f) { turtleDirection -= 360f; } while (turtleDirection < 0f) { turtleDirection += 360f; } calcTurtleOutline(); showTurtle(); } } public void right( float turnAngle ) { left( -turnAngle ); } public void penUp() { penIsDown = false; } public void penDown() { penIsDown = true; } void paintCredits( Graphics g ) { FontMetrics fm = g.getFontMetrics(); int y = 4 + fm.getAscent(); g.setColor( Color.orange ); g.drawString( "Tiny Java Logo 1.0, by Troy Stephens", 4, y ); } public void paint( Graphics g ) { super.paint( g ); int n = lines.size(); for (int i = 0; i < n; i++) { Color color = (Color) colors.elementAt( i ); g.setColor( color ); Rectangle r = (Rectangle) lines.elementAt( i ); g.drawLine( centerx + r.x, centery - r.y, centerx + r.width, centery - r.height ); } paintCredits( g ); if (turtleHideLevel == 0) drawTurtle( g ); } public synchronized void reshape( int x, int y, int width, int height ) { super.reshape( x, y, width, height ); centerx = width / 2; centery = height / 2; calcTurtleOutline(); repaint(); } }