// ReorderCode.java // Copyright (C) 1996, 1999 J Dana Eckart // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 1, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with CELLULAR; see the file COPYING. If not, write to the // Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ////////////////////////////////////////////////////////////////////////////// // This Java program implements a Ada code reordering applet. The idea is // to give beginning student in programming an easier tool to master so that // they can focus on the programming and language aspects rather than on // all the intermediate steps (such as editing) that are often necessary. import java.applet.*; import java.awt.*; import java.awt.event.*; import java.net.*; // This class provides the three buttons at the bottom of the applet. // class Controls extends Panel implements ActionListener { CodeCanvas canvas; ReorderCode applet; Button stepButton; public Controls (CodeCanvas canvas, ReorderCode applet) { this.canvas = canvas; this.applet = applet; add(stepButton = new Button("Step")); stepButton.addActionListener(this); Button tmpButton; add(tmpButton = new Button("Restart")); tmpButton.addActionListener(this); add(tmpButton = new Button("Reset")); tmpButton.addActionListener(this); add(tmpButton = new Button("Help")); tmpButton.addActionListener(this); } public void addStep() { stepButton.setVisible(true); } public void removeStep() { stepButton.setVisible(false); } public void actionPerformed(ActionEvent evt) { if (evt.getSource() instanceof Button) { String label = ((ActionEvent) evt).getActionCommand(); if (label.equals("Step")) canvas.doStep(this); else if (label.equals("Restart")) { canvas.doRestart(); addStep(); } else if (label.equals("Reset")) { canvas.doReset(); addStep(); } else if (label.equals("Help")) applet.doHelp(); } } } // The "main" applet, which simply glues the pieces together. // public class ReorderCode extends java.applet.Applet { Controls buttons; CodeCanvas code; public void init() { String att = getParameter("backgrnd"); Color backGrnd = new Color(Integer.valueOf(att, 16).intValue()); setBackground(backGrnd); att = getParameter("nonmoveable"); Color nonmoveable = new Color(Integer.valueOf(att, 16).intValue()); att = getParameter("moveable"); Color moveable = new Color(Integer.valueOf(att, 16).intValue()); att = getParameter("selected"); Color selected = new Color(Integer.valueOf(att, 16).intValue()); int nColors = (1 << (Toolkit.getDefaultToolkit().getColorModel().getPixelSize()-1)) + 1; setLayout(new BorderLayout()); add("North", code = new CodeCanvas(backGrnd, nonmoveable, moveable, selected, nColors)); buttons = new Controls(code, this); code.init(buttons); add("South", buttons); setSize(code.getSize().width, code.getSize().height + buttons.getSize().height); } public void start() { code.setEnabled(true); buttons.setEnabled(true); } public void stop() { code.setEnabled(false); buttons.setEnabled(false); } public void doHelp() { try { URL helpURL = new URL(getDocumentBase(), "help.html"); getAppletContext().showDocument(helpURL); } catch (MalformedURLException e) { System.out.println("Bad URL for help.\n"); } } } // Holds the information associated with each statement in the program // that the applet simulates. // class Statement { public String print; public boolean canMove; public int linesToEnd; public int level; public int nextStatement; public int jumpOnFalse = -1; public boolean isElse; public boolean isLoop; public boolean isExit; public Statement(String text, boolean moveable, int length, int nesting) { print = text; canMove = moveable; linesToEnd = length; level = nesting; nextStatement = 1; isElse = false; isLoop = false; isExit = false; } } // This class provides the real guts of the applet. It organizes and // displays the statements of the program as well as performing the // actions of each statement and showing the results of their execution // in the "Program Output" and "Variable Values" sections. // class CodeCanvas extends Canvas implements MouseListener { // These values will need to be altered for different programs. // However, they must always be positive values and satisfy: // lines = preLines + postLines + lines in program body int lines = 17, preLines = 5, postLines = 1; Statement states[] = new Statement[lines]; int order[] = new int[lines]; Color backGrnd; Color nonmoveable, moveable, selected; boolean monochrome = false; Font nonmoveableFont, moveableFont, selectedFont; boolean moving = false; int movedLine; int lineHeight; // The height of a line, including spacing. int charWidth; int indent; int outputTop; // Points to the top line of the output area. // Variables for building a separate image for the "Program Output" // so that it can be double buffered. // Image outputImage; Graphics outputGraphics; // The top-left corner of the "Program Output". // int leftMargin = 500; int outputWidth; public CodeCanvas(Color backGrnd, Color nonmoveable, Color moveable, Color selected, int nColors) { this.backGrnd = backGrnd; this.nonmoveable = nonmoveable; this.moveable = moveable; this.selected = selected; if (nColors < 4) monochrome = true; nonmoveableFont = new Font("FixedWidth", Font.PLAIN, 14); moveableFont = new Font("TimesRoman", Font.ITALIC, 14); selectedFont = new Font("FixedWidth", Font.BOLD, 14); if (monochrome) charWidth = 50*nonmoveableFont.getSize()/100; else charWidth = 46*nonmoveableFont.getSize()/100; lineHeight = 7*nonmoveableFont.getSize()/5; indent = 2*lineHeight; } Controls buttons; public void init(Controls buttons) { this.buttons = buttons; setSize(810, 360); reset(); outputWidth = this.getSize().width - leftMargin - indent; outputImage = createImage(outputWidth-1, (lines-2)/2*lineHeight+lineHeight/2-2); outputGraphics = outputImage.getGraphics(); clearOutput(); this.addMouseListener(this); } // Clear the Program Output portion of the screen. // void clearOutput() { outputGraphics.setColor(Color.white); outputGraphics.fillRect(0, 0, outputWidth-1, (lines-2)/2*lineHeight+lineHeight/2-2); putCursor(outputGraphics); getGraphics().drawImage(outputImage, leftMargin+1, outputTop+1, this); } // Clear the code portion of the screen. // void clearCode(Graphics g) { g.clearRect(0, 0, leftMargin, this.getSize().height); } // Write out the code portion of the screen. // void writeCode(Graphics g) { for (int i = 0; i < lines; i++) { // Paint the moveable statements a different color if (states[order[i]].canMove) setMoveable(g); else setNonMoveable(g); if (programCounter-1 == i) setSelected(g); g.drawString(states[order[i]].print, (states[order[i]].level+1)*indent, (i+1)*lineHeight); } } void setMoveable(Graphics g) { if (monochrome) { g.setFont(moveableFont); g.setColor(nonmoveable); } else g.setColor(moveable); } void setNonMoveable(Graphics g) { if (monochrome) { g.setFont(nonmoveableFont); g.setColor(nonmoveable); } else g.setColor(nonmoveable); } void setSelected(Graphics g) { if (monochrome) { g.setFont(selectedFont); g.setColor(nonmoveable); } else g.setColor(selected); } int programCounter = preLines, previousPC = preLines; public void paint(Graphics g) { writeCode(g); setNonMoveable(g); // Draw the area for displaying program results (output). // g.drawString("Program Output", leftMargin + outputWidth/2 - 7*(3*nonmoveableFont.getSize()/5), lineHeight); g.drawRect(leftMargin, lineHeight+lineHeight/2, outputWidth, (lines-2)/2*lineHeight+lineHeight/2-1); outputTop = lineHeight+lineHeight/2; g.drawImage(outputImage, leftMargin+1, outputTop+1, this); // Draw the area for displaying variable values. // setNonMoveable(g); g.drawString("Variable Values", leftMargin + outputWidth/2 - 6*(3*nonmoveableFont.getSize()/5), (lines-2)/2*lineHeight+7*lineHeight/2); g.drawRect(leftMargin, (lines-2)/2*lineHeight+4*lineHeight, outputWidth, 4*lineHeight); showVariables(g); setNonMoveable(g); g.drawString("Program Status:", leftMargin, lines*lineHeight - lineHeight/2); showStatus(g); } public void mouseClicked(MouseEvent e) { ; } public void mouseEntered(MouseEvent e) { ; } public void mouseExited(MouseEvent e) { ; } public void mousePressed(MouseEvent evt) { int x = evt.getX(); int y = evt.getY(); int i = (y-lineHeight/4)/lineHeight; if (indent*(1+states[order[i]].level) <= x && x <= leftMargin && states[order[i]].canMove) { Graphics g = getGraphics(); rewriteCodeLine(programCounter-1, false); rewriteCodeLine(previousPC, false); // Erase the text first if monochrome. // if (monochrome) for (int j = i; j <= i+states[order[i]].linesToEnd; j++) { if (states[order[i]].canMove) setMoveable(g); else setNonMoveable(g); g.setColor(backGrnd); g.drawString(states[order[j]].print, (states[order[j]].level+1)*indent, (j+1)*lineHeight); } // Highlight the text to be moved. // setSelected(g); for (int j = i; j <= i+states[order[i]].linesToEnd; j++) g.drawString(states[order[j]].print, (states[order[j]].level+1)*indent, (j+1)*lineHeight); moving = true; movedLine = i; } } void updateBody(int location, int amount) { // Update the values for each of the statements in a // posibly compound statement, by the given amount. // for (int i = location; i < location+states[order[location]].linesToEnd+1; i++) states[order[i]].level += amount; } // Whenever code gets moved, ALL exit statements need (potentially) // to be updated, so that they jump to the possiblly changed location // at the end of the loop. // void updateExits() { for (int i = lines-postLines; i >= preLines; i--) if (states[order[i]].isExit) for (int j = i-1; j >= preLines; j--) if (states[order[j]].isLoop && j+states[order[j]].linesToEnd > i) { if (states[order[i]].jumpOnFalse > 0) states[order[i]].jumpOnFalse = states[order[j]].linesToEnd + j - i + 1; else states[order[i]].nextStatement = states[order[j]].linesToEnd + j - i + 1; break; } } void updateBefore(int movedLine) { Statement movedState = states[order[movedLine]]; // Remove the statements from containing structures. // for (int i = movedLine-1; i >= 0 ; i--) { Statement state = states[order[i]]; // Statement is before an else (moved is after else). // if (state.nextStatement > 1) if (i+state.nextStatement >= movedLine) state.nextStatement -= (movedState.linesToEnd+1); if (i+state.linesToEnd >= movedLine && state.canMove) { updateBody(movedLine, -1); if (state.jumpOnFalse > 0 && i+state.jumpOnFalse >= movedLine) state.jumpOnFalse -= (movedState.linesToEnd+1); if (states[order[i+state.linesToEnd]].nextStatement < 0) // It's a loop // states[order[i+state.linesToEnd]].nextStatement += (movedState.linesToEnd+1); state.linesToEnd -= (movedState.linesToEnd+1); } } // The moved statement is either a loop or if and is // just before an else. // if (movedState.linesToEnd > 0 && states[order[movedLine+movedState.linesToEnd+1]].isElse) { states[order[movedLine-1]].nextStatement = movedState.jumpOnFalse - movedState.linesToEnd; movedState.jumpOnFalse = movedState.linesToEnd+1; } // The moved statement is right before an else. // else if (states[order[movedLine+1]].isElse) { states[order[movedLine-1]].nextStatement += (movedState.nextStatement-1); movedState.nextStatement = 1; } } void updateAfter(int newSpot) { Statement movedState = states[order[newSpot]]; // Add the statements to containing structures. // for (int i = newSpot-1; i >= 0 ; i--) { Statement state = states[order[i]]; // Statement is after an else. // if (state.nextStatement > 1 && i+1 != newSpot) if (i+state.nextStatement >= newSpot) state.nextStatement += (movedState.linesToEnd+1); if (i+state.linesToEnd >= newSpot && state.canMove) { updateBody(newSpot, 1); state.linesToEnd += (movedState.linesToEnd+1); if (state.jumpOnFalse >= 0 && i+state.jumpOnFalse >= newSpot) state.jumpOnFalse += (movedState.linesToEnd+1); if (states[order[i+state.linesToEnd]].nextStatement < 0) // It's a loop // states[order[i+state.linesToEnd]].nextStatement -= (movedState.linesToEnd+1); } } // Statement was moved to just before an else, so make // the statement just above it do a simple jump ahead // one, and have this one jump around the else part. // if (states[order[newSpot+movedState.linesToEnd+1]].isElse) { if (states[order[newSpot+movedState.linesToEnd]].nextStatement < 0) // Moved statement IS a loop. // movedState.jumpOnFalse += (states[order[newSpot-1]].nextStatement-1); else if (movedState.jumpOnFalse >= 0) // Moved statement IS an if. // states[order[newSpot+movedState.jumpOnFalse]].linesToEnd += (states[order[newSpot-1]].nextStatement-1); else // Moved statement is NOT an if or a loop. // movedState.nextStatement += (states[order[newSpot-1]].nextStatement-1); states[order[newSpot-1]].nextStatement = 1; } } int moveLines(int movedLine, int newSpot) { int stateLength = states[order[movedLine]].linesToEnd+1; int oldOrder[] = new int[stateLength]; for (int i = 0; i < stateLength; i++) oldOrder[i] = order[i+movedLine]; // Reposition the lines. // if (newSpot > movedLine) { newSpot -= (stateLength-1); for (int i = movedLine; i < newSpot; i++) order[i] = order[i+stateLength]; } else for (int i = movedLine; i > newSpot; i--) order[i+stateLength-1] = order[i-1]; for (int i = 0; i < stateLength; i++) order[i+newSpot] = oldOrder[i]; return newSpot; } boolean inLoop(int newLine) { for (int i = newLine; i >= preLines; i--) if (states[order[i]].isLoop && i+states[order[i]].linesToEnd > newLine) return true; return false; } public void mouseReleased(MouseEvent evt) { int x = evt.getX(); int y = evt.getY(); if (moving) { // Reorder the statements, and redisplay the program. // int newSpot = y/lineHeight; if (newSpot == lines || newSpot > movedLine) newSpot--; if (preLines > newSpot || newSpot > lines-postLines-1) { moving = false; rewriteCodeLine(movedLine, false); } if (newSpot < 0 || newSpot > lines || (newSpot > movedLine && newSpot <= movedLine + states[order[movedLine]].linesToEnd) || x < indent || x > leftMargin) newSpot = movedLine; // If the statement being moved is an exit statement, // then make sure that it is moved to the inside of // another loop. if (states[order[movedLine]].isExit && ((newSpot >= movedLine && !inLoop(newSpot)) || (newSpot < movedLine && !inLoop(newSpot-1)))) { moving = false; rewriteCodeLine(movedLine, false); } updateBefore(movedLine); newSpot = moveLines(movedLine, newSpot); updateAfter(newSpot); updateExits(); moving = false; doRestart(); buttons.addStep(); Graphics g = getGraphics(); clearCode(g); writeCode(g); } } int charPosition = 1, linePosition = 1; int lastCursorChar = 1, lastCursorLine = 1; int cursorChar = 1, cursorLine = 1; void eraseCursor(Graphics g) { g.setColor(Color.white); g.drawLine(lastCursorChar*charWidth, lastCursorLine*lineHeight+lineHeight/4, lastCursorChar*charWidth, lastCursorLine*lineHeight-lineHeight/2); } void putCursor(Graphics g) { cursorChar = charPosition; cursorLine = linePosition; g.setColor(selected); g.drawLine(cursorChar*charWidth, cursorLine*lineHeight+lineHeight/4, cursorChar*charWidth, cursorLine*lineHeight-lineHeight/2); lastCursorChar = cursorChar; lastCursorLine = cursorLine; } // Reset the Program Output area. // void resetOutput() { charPosition = linePosition = 1; lastCursorChar = lastCursorLine = 1; cursorChar = cursorLine = 1; clearOutput(); } // Writes out a line of code, erasing what was there before hand if // necessary. // void rewriteCodeLine(int line, boolean select) { Graphics g = getGraphics(); // Redraw the statement being run. if (monochrome) { // Erase it first if monochrome. if (!select) setSelected(g); else setMoveable(g); g.setColor(backGrnd); g.drawString(states[order[line]].print, (states[order[line]].level+1)*indent, (line+1)*lineHeight); } if (states[order[line]].canMove) setMoveable(g); else setNonMoveable(g); if (select) setSelected(g); g.drawString(states[order[line]].print, (states[order[line]].level+1)*indent, (line+1)*lineHeight); } // Performs the actions for the "restart" button. // public void doRestart() { rewriteCodeLine(previousPC, false); programCounter = previousPC = preLines; rewriteCodeLine(programCounter-1, true); resetVariables(); resetOutput(); showStatus(getGraphics()); } // Performs the actions for the "reset" button. // public void doReset() { reset(); doRestart(); Graphics g = getGraphics(); clearCode(g); writeCode(g); buttons.addStep(); } public void doStep(Controls panel) { Graphics g = getGraphics(); // UNhighlight the previous Ada statement. // if (previousPC == programCounter) rewriteCodeLine(previousPC-1, false); else rewriteCodeLine(previousPC, false); rewriteCodeLine(programCounter, true); previousPC = programCounter; if (programCounter+1 >= lines) { programCounter = lines; showStatus(g); panel.removeStep(); return; } // Perform the java statement(s) corresponding to the Ada code. // eraseCursor(outputGraphics); doAction(outputGraphics); putCursor(outputGraphics); g.drawImage(outputImage, leftMargin+1, outputTop+1, this); } // Puts the statements back into their original ordering with // their original values. // void reset() { setStatements(); for (int i = 0; i < lines; i++) order[i] = i; } boolean conditionIsTrue = true; // Show the program status. // void showStatus(Graphics g) { g.clearRect(leftMargin+17*charWidth, (lines-1)*lineHeight, this.getSize().width - leftMargin - 17*charWidth, lineHeight); setSelected(g); Statement state = states[order[previousPC]]; String status; if (programCounter == lines) status = "Execution of program complete"; else if (previousPC == programCounter) status = "Ready to step"; else if (state.jumpOnFalse > 0 && conditionIsTrue) status = "Boolean condition is true"; else if (state.jumpOnFalse > 0 && !conditionIsTrue) status = "Boolean condition is false"; else if (state.isElse) status = "Begin default action(s) for if"; else if (state.isExit) status = "Quitting the loop"; else status = "Continue to step"; g.drawString(status, leftMargin+17*charWidth, lines*lineHeight - lineHeight/2); } //////////////////////////////////////////////////////////////////////////// // // Except for line 171 above, the below code is all that need be changed // in order to have a the applet present a different program. // //////////////////////////////////////////////////////////////////////////// // Ada variable declarations. // int previous_ada_N = 5, ada_N = 5; boolean new_ada_N = false; // Performs the Java equivalent of each Ada statement. // void doAction(Graphics g) { String var_val; // Make all previous values the same before performing action. // previous_ada_N = ada_N; new_ada_N = false; setNonMoveable(g); switch (order[programCounter++]) { case 5: g.drawString("Let's begin:", charPosition*charWidth, linePosition*lineHeight); charPosition = 1; linePosition += 1; break; case 6: break; case 7: conditionIsTrue = false; if (ada_N == 1) { programCounter += (states[7].jumpOnFalse-1); conditionIsTrue = true; } break; case 8: conditionIsTrue = true; if (ada_N % 2 != 0) { programCounter += (states[8].jumpOnFalse-1); conditionIsTrue = false; } break; case 9: ada_N = ada_N / 2; new_ada_N = true; break; case 10: break; case 11: ada_N = 3 * ada_N + 1; new_ada_N = true; break; case 12: break; case 13: var_val = String.valueOf(ada_N); g.drawString(var_val, charPosition*charWidth, linePosition*lineHeight); charPosition += var_val.length(); break; case 14: charPosition = 1; linePosition += 1; break; case 15: break; default: } showVariables(getGraphics()); showStatus(getGraphics()); if (states[order[programCounter-1]].nextStatement != 1) programCounter += (states[order[previousPC]].nextStatement-1); } // Reset the (global) variable values to the appropriate defaults. // void resetVariables() { previous_ada_N = ada_N = 5; new_ada_N = false; showVariables(getGraphics()); } // Show the values of the program variables. Variables and values // that have changed since last statement are printed in selected // form, otherwise they are printed as non-moveable. // void showVariables(Graphics g) { g.clearRect(leftMargin+1, (lines-2)/2*lineHeight+4*lineHeight + 1, outputWidth-1, 4*lineHeight-1); String var_val; // Show "N". // if (new_ada_N) { setSelected(g); var_val = new String("N = " + ada_N + " (" + previous_ada_N + ")"); } else { setNonMoveable(g); var_val = new String("N = " + ada_N); } g.drawString(var_val, leftMargin+indent, (lines-2)/2*lineHeight+5*lineHeight); } // These are the Ada code statements, given in their original // ordering. The design of this applet does NOT support the // use of input, subprograms, exceptions, or packages. // void setStatements() { // The Ada code. states[0] = new Statement("with Text_IO;", false, 0, 0); states[1] = new Statement("procedure Exercise is", false, 15, 0); states[2] = new Statement("package Integer_IO is new Text_IO.Integer_IO (Num => Integer);", false, 0, 1); states[3] = new Statement("N : Integer := 5;", false, 0, 1); states[4] = new Statement("begin", false, 0, 0); states[5] = new Statement("Text_IO.Put_Line (Item => \"Let's begin:\");", true, 0, 1); states[6] = new Statement("loop", true, 9, 1); states[6].isLoop = true; states[7] = new Statement("exit when N = 1;", true, 0, 2); states[7].isExit = true; states[7].jumpOnFalse = 9; states[8] = new Statement("if N rem 2 = 0 then", true, 4, 2); states[8].jumpOnFalse = 2; states[9] = new Statement("N := N / 2;", true, 0, 3); states[9].nextStatement = 3; states[10] = new Statement("else", false, 0, 2); states[10].isElse = true; states[11] = new Statement("N := 3 * N + 1;", true, 0, 3); states[12] = new Statement("end if;", false, 0, 2); states[13] = new Statement("Integer_IO.Put (Item => N, Width => 1);", true, 0, 2); states[14] = new Statement("Text_IO.New_Line;", true, 0, 2); states[15] = new Statement("end loop;", false, 0, 1); states[15].nextStatement = -9; states[16] = new Statement("end Exercise;", false, 0, 0); } }