package arithmetic;
import asd.*;

import java.io.*;
import java.util.*;
import java.awt.*;       // Font
import java.awt.event.*; // ActionEvent, ActionListener,
                         // WindowAdapter, WindowEvent
import javax.swing.*;    // JFrame, JPanel

/**
   An instance of Evaluator permits a user to evaluate arithmetic
   expressions.  It provides a graphical user interface that permits
   initialization of a parse, stepping the parse, and displaying
   the phrase structure and semantic value as the parse proceeds.
<BR><BR>
   Command-line usage:
   <BR>In an MS-Windows command-line window:
   <BR><tt><b> java -cp asddigraphs.jar;. arithmetic/Evaluator</b></tt>
   <BR>Or under UNIX:
   <BR><tt><b> java -cp asddigraphs.jar:. arithmetic/Evaluator</b></tt>
   <BR>OR if asddigraphs.jar and asdx.jar have been put in the system
   classpath:
   <BR><tt><b> java arithmetic/Evaluator</b></tt>
   <BR>
   <BR>An optional command-line parameter can be used to specify the maximum
   <BR>number of parsing advance steps before pausing.
   <BR>Example:  java arithmetic/Evaluator 50000
   <BR>(The default maximum number of advance steps between pauses is 10000.)

   @author James A. Mason
   @version 1.01  2005 Aug
 */
public class Evaluator
{  public static void main(String[] args)
   {  int maxSteps = 0;
      if (args.length == 0)
         maxSteps = MAXSTEPS;
      else
         maxSteps = Integer.parseInt(args[0]);

      Evaluator thisEvaluator = new Evaluator(maxSteps);
   }

   Evaluator(int maxSteps)
   {  window = new EvaluatorWindow(this);
      window.setTitle(
         "Arithmetic Expression Evaluator - version " + VERSION);
      window.setVisible(true);
      maximumSteps = maxSteps;
      parser = new ASDParser(this); // This Evaluator instance is
         // the object in which the parser is to invoke functions
         // that correspond to semanticAction and semanticValue
         // fields in the grammar being used.
      useGrammar(GRAMMARFILE);
      window.setGrammarFile(GRAMMARFILE);
      expectedTypes = new ArrayList();
      expectedTypes.add(EXPECTEDTYPE);

   }

   void advance()
   {  if (!grammarLoaded)
      {  JOptionPane.showMessageDialog(window,
            "No grammar file is currently loaded.");
         window.clearGrammarFileNameField();
         return;
      }
      if (utterance == null || utterance.equals("")) return;
      window.clearValueField();  // attempting to compute a new value
      String advanceResult = parser.advance();
      if (advanceResult.equals(parser.QUIT))
      {  window.getOutputPane().append(
            "\nParse quit after " + stepsThisTry + " new and "
            + steps + " total advance steps,\n"
            + "and " + backupStepsThisTry + " new and "
            + backupSteps + " total backup steps, leaving structure:\n");
         showPhraseStructure();
         showSemanticValue();
         // Re-initialize the parse without clearing the value field:
         initializeParse(false);
      }
      else if (advanceResult.equals(parser.SUCCEED))
      {  steps++;
         stepsThisTry++;
         stepsSincePause++;
         window.getOutputPane().append(
            "\n" + steps + " - parse advanced to:\n");
         showPhraseStructure();
         if (parser.done())
         {  showExpressionValue();
            showExpressionValueField();
            window.getOutputPane().append(
               "\nSuccessful parse after " + stepsThisTry + " new and "
               + steps  + " total advance steps,\n"
               + "and " + backupStepsThisTry + " new and "
               + backupSteps + " total backup steps.\n");
            // prepare for an attempt at an alternative parse
            stepsThisTry = 0;
            backupStepsThisTry = 0;
            stepsSincePause = 0;
         }
         else
            showSemanticValue();
      }
      else if (advanceResult.equals(parser.NOADVANCE))
      {  if (parser.backup())
         {  ++backupSteps; ++backupStepsThisTry;
            window.getOutputPane().append("\nparse backed up to:\n");
            showPhraseStructure();
            showSemanticValue();
         }
         else
         {  window.getOutputPane().append(
               "\nParse failed after " + stepsThisTry + " new and "
            + steps + " total advance steps,\n"
            + "and " + backupStepsThisTry + " new and "
            + backupSteps + " total backup steps, leaving structure:\n");
            showPhraseStructure();
            showSemanticValue();
            // prepare for an attempt at an alternative parse
            stepsThisTry = 0;
            backupStepsThisTry = 0;
            stepsSincePause = 0;
         }
      }
      else  // this shouldn't occur
         window.getOutputPane().append(
            "\nInvalid result of ASDParser advance(maxSteps):"
            + advanceResult + "\n");
   } // end advance

   boolean completeParse()
   {  if (!grammarLoaded)
      {  JOptionPane.showMessageDialog(window,
            "No grammar file is currently loaded.");
         window.clearGrammarFileNameField();
         return false;
      }
      if (utterance == null || utterance.equals(""))
         return false;
      window.clearValueField();  // attempting to compute a new value
      String advanceResult; // SUCCEED, NOADVANCE, or QUIT
      while(stepsSincePause < maximumSteps)
      {  advanceResult = parser.advance();
         if (advanceResult.equals(parser.QUIT))
         {  window.getOutputPane().append(
               "\nParse quit after " + stepsThisTry + " new and "
               + steps + " total advance steps,\n"
               + "and " + backupStepsThisTry + " new and "
               + backupSteps
               + " total backup steps, leaving structure:\n");
            showPhraseStructure();
            showSemanticValue();
            // Re-initialize the parse without clearing the value field:
            initializeParse(false);
            return false;
         }
         else if (advanceResult.equals(parser.SUCCEED))
         {  ++steps;
            ++stepsThisTry;
            ++stepsSincePause;
            if (parser.done())
            {  window.getOutputPane().append(
                  "\nSuccessful parse in " + stepsThisTry + " new and "
                  + steps  + " total advance steps,\n"
                  + "and " + backupStepsThisTry + " new and "
                  + backupSteps + " total backup steps.\n");
               window.displaySemanticValue();
               showPhraseStructure();
               showExpressionValue();
               showExpressionValueField();
               // prepare for an attempt at an alternative parse
               stepsThisTry = 0;
               backupStepsThisTry = 0;
               stepsSincePause = 0;
               return true;
            }
         }
         else if (advanceResult.equals(parser.NOADVANCE))
         {  if (parser.backup())
            {  ++backupSteps; ++backupStepsThisTry;
            }
            else
            {  window.getOutputPane().append(
                  "\nParse failed after " + stepsThisTry + " new and "
                  + steps + " total advance steps,\n"
                  + "and " + backupStepsThisTry + " new and "
                  + backupSteps + " total backup steps, leaving structure:\n");
               showPhraseStructure();
               showSemanticValue();
               // prepare for an attempt at an alternative parse
               stepsThisTry = 0;
               backupStepsThisTry = 0;
               stepsSincePause = 0;
               return false;
            }
         }
         else  // this shouldn't happen
         {  window.getOutputPane().append(
               "Invalid result of ASDParser advance(maxSteps):"
               + steps);
            return false;
         }
      }
      window.getOutputPane().append(
         "\nAttempt to Complete parse paused after advance step "
         + steps + ",\n" + maximumSteps
         + " advance steps since start or last pause."
         +"\nIt can be resumed by menu selection.\n");
      stepsSincePause = 0;
      return false;
   } // end completeParse

   ASDParser getParser() { return parser; }

   String getUtterance() { return utterance; }

   boolean initializeParse(boolean shouldClearValueField)
   {  if (!grammarLoaded)
      {  JOptionPane.showMessageDialog(window,
            "No grammar file is currently loaded.");
         window.clearGrammarFileNameField();
         return false;
      }
      if (utterance == null || utterance.length() == 0)
      {  JOptionPane.showMessageDialog(window,
            "The expression to be parsed must not be empty.");
         return false;
      }
      if (expectedTypes == null || expectedTypes.size() == 0)
      {  JOptionPane.showMessageDialog(window,
            "The list of expected phrase types must not be empty.");
         return false;
      }
      utterance = scanInputString(utterance);
      parser.initialize(utterance, expectedTypes);
      phraseInitialized = true;
      steps = 0;
      stepsThisTry = 0;
      stepsSincePause = 0;
      backupSteps = 0;
      backupStepsThisTry = 0;
      if (shouldClearValueField)
         window.clearValueField();
      window.getOutputPane().append("\nInitialized expression structure:\n");
      showPhraseStructure();
      return true;
   } // end initializeParse

   String scanInputString(String expression)
   {  String result = "";
      StringTokenizer tokenizer = new StringTokenizer(expression,
         parser.SPACECHARS + parser.SPECIALCHARS + "-x", true);
      // Punctuation marks must be separated from words before
      // the EnglishWord.processApostrophe method is applied.
      while(tokenizer.hasMoreTokens())
      {  String token = tokenizer.nextToken().trim();
         if (token.length() == 1) // punctuation or one-letter word
         {  if ("(".equals(token))
               result += " LPAREN";
            else if (")".equals(token))
               result += " RPAREN";
            else
               result += " " + token;
         }
         else if (token.length() > 1)
            result += " " + token;
         // whitespace tokens are ignored
      }
      return result;
   } // end scanInputString

   void setUtterance(String newUtterance)
   {  if (!grammarLoaded)
      {  JOptionPane.showMessageDialog(window,
            "No grammar file is currently loaded.");
         window.clearGrammarFileNameField();
         window.clearExpressionField();
         return;
      }
      utterance = newUtterance;
      if (utterance == null || utterance.length() == 0)
      {  JOptionPane.showMessageDialog(window,
            "The expression to be parsed must not be empty.");
         return;
      }
      initializeParse(true);  // also clears the value field
   } // end setUtterance

   void setUtteranceNull() { utterance = null; }

   void showAboutInfo()
   {  // responds to About Evaluator choice in Help menu
      JOptionPane.showMessageDialog(window,
         "Evaluator version " + VERSION +
         "\nAuthor: James A. Mason" +
         "\nEmail: jmason@yorku.ca" +
         "\nhttp://www.yorku.ca/jmason/");
   }

   void showExpressionValue()
   {  window.getOutputPane().append("\n");
      window.getOutputPane().append("Value of the expression is "
         + parser.phraseStructure().nextNode().value() + "\n");
   }

   void showExpressionValueField()
   {  window.setValueField(
         parser.phraseStructure().nextNode().value() + "");
   }

   void showPhraseStructure()
   {  if (!phraseInitialized)
         JOptionPane.showMessageDialog(window,
            "No expression has been entered.");
      else
      {  ASDPhraseNode head = parser.phraseStructure();
         ASDPhraseNode currentNode = parser.currentNode();
         window.showTree(head, currentNode);
      }
   } // end showPhraseStructure

   void showSemanticValue()
   {  window.getOutputPane().append("\n");
      window.getOutputPane().append("Value of current node is: "
         + parser.currentNode().value() + "\n");
   }

   void showSemanticValueField()
   {  window.setValueField(parser.currentNode().value() + "");
   }

   boolean useGrammar(String fileName)
   {  if (parser.useGrammar(fileName))
      {  grammarLoaded = true;
         return true;
      }
      else
      {  JOptionPane.showMessageDialog(window,
            "Grammar file with that name could not be loaded.");
         grammarLoaded = false;
         return false;
      }
   } // end useGrammar

   public String currentWord()
   {  return parser.currentNode().word();
   }

   public Object get(String featureName)
   {  return parser.get(featureName);
   }

   public Object nodeValue()
   {  return parser.currentNode().value();
   }

   public void set(String featureName, Object featureValue)
   {  parser.set(featureName, featureValue);
   }

/* Functions to compute semantic actions and values of grammar nodes
    in expression.grm:
  */

   public Object expression_$$_1_v()
   {  return get("previous");
   }

   public Object expression_$$_2_v()
   {  return get("previous");
   }

   public Object expression_$$_3_v()
   {  String integerPart = (String) get("integerPart");
      return new Double(integerPart);
   }

   public String expression_DECIMALPOINT_2()
   {  set("integerPart", "0");
      return null;
   }

   public String expression_DIGITSTRING_1()
   {  set("integerPart", nodeValue());
      return null;
   }

   public Object expression_DIGITSTRING_2_v()
   {  String integerPart = (String) get("integerPart");
      String fractionPart = (String) nodeValue();
      return new Double(integerPart + "." + fractionPart);
   }

   public String expression_divided_by_1()
   {  set("operator", currentWord());
      return null;
   }

   public String expression_EXPRESSION()
   {  set("value", nodeValue());
      return null;
   }

   public String expression_FACTOR_1()
   {  Double previous = (Double) get("previous");
      String operator = (String) get("operator");
      Double current = (Double) nodeValue();
      if (previous == null) // first factor
         previous = current;
      else
      {  double resultDouble = 0;
         if ("*".equals(operator))
            resultDouble = previous.doubleValue() * current.doubleValue();
         else if ("/".equals(operator))
         {  if (current.doubleValue() == 0)
            {  window.getOutputPane().append(
                  "\n*** Attempt to divide by 0 ***\n");
               window.setValueField("*** Attempt to divide by 0 ***");
               return parser.QUIT;
            }
            resultDouble = previous.doubleValue() / current.doubleValue();
         }
         else // shouldn't happen
            System.out.println("Invalid operator " + operator);
         previous = new Double(resultDouble);
      }
      set("previous", previous);
      return null;
   }

   public Object expression_FACTOR_2_v()
   {  Double factor = (Double) nodeValue();
      return new Double(- factor.doubleValue());
   }

   public Object expression_NUMBER_1_v()
   {  return currentWord();
   }

   public String expression_minus_1()
   {  set("operator", currentWord());
      return null;
   }

   public String expression_plus_1()
   {  set("operator", currentWord());
      return null;
   }

   public Object expression_RIGHT_BRACKET_v()
   {  return get("value");
   }

   public String expression_TERM_1()
   {  Double previous = (Double) get("previous");
      String operator = (String) get("operator");
      Double current = (Double) nodeValue();
      if (previous == null) // first term
         previous = current;
      else
      {  double resultDouble = 0;
         if ("+".equals(operator))
            resultDouble = previous.doubleValue() + current.doubleValue();
         else if ("-".equals(operator))
            resultDouble = previous.doubleValue() - current.doubleValue();
         else // shouldn't happen
            System.out.println("Invalid operator " + operator);
         previous = new Double(resultDouble);
      }
      set("previous", previous);
      return null;
   }

   public String expression_times_1()
   {  set("operator", "*");
      return null;
   }

   static final String GRAMMARFILE = "expression.grm";
   static final String EXPECTEDTYPE = "EXPRESSION";
   static final int MAXSTEPS = 10000;
   static final String VERSION = "1.01";
   static final Font PLAINFONT
      = new Font("Monospaced", Font.PLAIN, 14);
   static final Font BOLDFONT
      = new Font("Monospaced", Font.BOLD, 14);
   private EvaluatorWindow window;
   private ASDParser parser;
   private boolean grammarLoaded = false;
   private boolean phraseInitialized = false;
   private ArrayList expectedTypes;
   private int steps = 0;  // total advance steps since phrase initialization
   private int stepsSincePause = 0;  // steps since last pause
   private int stepsThisTry = 0;  // advance steps since phrase
                                  // initialization or last successful parse
   private int backupSteps = 0;  // total backup steps since phrase init.
   private int backupStepsThisTry = 0; // since init. or last succ. parse
   private int maximumSteps = MAXSTEPS;
   private String utterance = null;

   private class EvaluatorWindow extends JFrame
   {  EvaluatorWindow(Evaluator givenTester)
      {  evaluator = givenTester;
         grammarFileNameField = new JTextField(40);
         grammarFileNameField.addActionListener(
            new GrammarFileNameFieldListener(this));
         expressionField = new JTextField(40);
         expressionField.addActionListener(
            new ExpressionFieldListener(this));
         valueField = new JTextField(40);
         JPanel pane = new JPanel();
         pane.setLayout(
            new BoxLayout(pane, BoxLayout.Y_AXIS));
         JTextArea description = new JTextArea("\n"
            + "   This application evaluates arithmetic expressions like"
            + "   2 x -[3.5x37 - (0.64 / 4 + 2.)] + .001\n"
            + "   To use it, just enter a desired expression in the"
            + " expression line below, press the Enter key,\n "
            + "   and use the Action menu or the menu in the pane"
            + " below to parse and evaluate it.\n", 5, 80
            );
         description.setMaximumSize(new Dimension(800, 70));
         description.setFont(new Font("Times Roman", Font.BOLD, 16));
         pane.add(description);
         pane.add(
            new LabeledTextField("Grammar file: ", grammarFileNameField));
         pane.add(
            new LabeledTextField("Expression:   ", expressionField));
         pane.add(
            new LabeledTextField("Value:        ", valueField));
         outputPane = new JTextArea();
         outputPane.setMinimumSize(new Dimension(DEFAULT_WIDTH,
            DEFAULT_HEIGHT));
         outputPane.setFont(Evaluator.PLAINFONT);
         OutputPaneMenu menu = new OutputPaneMenu(outputPane, evaluator);
         MouseListener popupListener = new PopupListener(menu);
         outputPane.addMouseListener(popupListener);

         pane.add(new JScrollPane(outputPane));
         getContentPane().add(pane, BorderLayout.CENTER);
         addWindowListener(new WindowCloser(this));
            // listens for window closing events (see below)
         setDefaultCloseOperation(DISPOSE_ON_CLOSE);
         setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

         JMenuBar menuBar = new JMenuBar();
         setJMenuBar(menuBar);
         ActionMenu aMenu = new ActionMenu(this);
         aMenu.setMnemonic(KeyEvent.VK_A);
         menuBar.add(aMenu);
         HelpMenu hMenu = new HelpMenu(this);
         hMenu.setMnemonic(KeyEvent.VK_H);
         menuBar.add(hMenu);
      } // end EvaluatorWindow(Evaluator givenTester)

      void clearValueField() { valueField.setText(""); }
      void clearExpressionField() { expressionField.setText(""); }
      void clearGrammarFileNameField() { grammarFileNameField.setText(""); }

      JTextField getValueField() { return valueField; }
      JTextField getGrammarFileNameField() { return grammarFileNameField; }
      JTextArea getOutputPane() { return outputPane; }
      Evaluator getEvaluator() { return evaluator; }
      JTextField getExpressionField() { return expressionField; }

      void displaySemanticValue()
      {
      }

      void grammarFileNameFieldChanged()
      {  clearValueField();
         if (!evaluator.useGrammar(grammarFileNameField.getText().trim()))
            // grammar file was not loaded
            // Note: the grammarFileNameField is intentionally NOT
            // reset to empty here, so the user can edit the incorrect
            // file name if desired.
            return;
         outputPane.append(
            "\nNew grammar file has been loaded.\n");
         String utterance = evaluator.getUtterance();
         if (utterance != null && utterance.length() > 0)
            evaluator.initializeParse(true); // also clears the value field
      } // end grammarFileNameChanged

      void expressionFieldChanged()
      {  evaluator.setUtterance(expressionField.getText().trim());
      }

      void setGrammarFile(String fileName)
      {  grammarFileNameField.setText(fileName);
      }

      void setValueField(String value)
      {  valueField.setText(value);
      }

      /**
         Displays the tree rooted at the given head node,
         with node currentNode indicated by an asterisk and an arrow.
         @param head the header node of the phrase structure
         @param currentNode the current node at the top level
         in the phrase structure
       */
      void showTree(ASDPhraseNode head, ASDPhraseNode currentNode)
      {  showTreeMark(head, "", currentNode);
         outputPane.append("\n");
      } // end showTree

      /**
         Displays the portion of the tree starting at the
         given node and indented with the given indentString as
         prefix for each line that does not represent a top-
         level node.  Top-level nodes are prefixed with three
         blanks or, in the case of the given aNode, an asterisk
         and an arrow whose purpose is to indicate the node
         which is the current node during a parse.
         @param indentString prefix for indenting of the
         current subtree
         @param aNode the node to be marked with an arrow
       */
      private void showTreeMark(ASDPhraseNode givenNode, String indentString,
                               ASDPhraseNode markNode)
      {  outputPane.append("\n");
         if (givenNode == markNode)
            outputPane.append("*->");
         else
            outputPane.append("   ");
         outputPane.append(indentString + givenNode.word() + " ");
         if (givenNode.instance() != null)
            outputPane.append(givenNode.instance().instance());
         else
            outputPane.append("nil");
         if (givenNode.subphrase() != null)
            showTreeMark(givenNode.subphrase(),indentString + "   ",
               markNode);
         if (givenNode.nextNode() != null)
            showTreeMark(givenNode.nextNode(), indentString, markNode);
      } // end showTreeMark

      static final int DEFAULT_WIDTH = 800;  // window width
      static final int DEFAULT_HEIGHT = 600; // window height
      private Evaluator evaluator;
      private JTextField grammarFileNameField;
      private JTextField valueField;
      private JTextField expressionField;
      private JTextArea outputPane;
   } // end class EvaluatorWindow

   private class LabeledTextField extends JPanel
   {  LabeledTextField(String labelText, JTextField textField)
      {  setMaximumSize(new Dimension(800,10));
         setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
         JLabel label = new JLabel(labelText);
         label.setFont(Evaluator.BOLDFONT);
         textField.setFont(Evaluator.PLAINFONT);
         this.add(label);
         this.add(textField);
      }
   } // end class LabeledTextField

   /**
      An instance defines what should happen when a window
      closes.
    */
   class WindowCloser extends WindowAdapter
   {  WindowCloser(EvaluatorWindow w)
      {  window = w;
      }

      public void windowClosing(WindowEvent e)
      {
         System.exit(0);        // stop the program
      }

      EvaluatorWindow window;
   } // end class WindowCloser

   private class GrammarFileNameFieldListener implements ActionListener
   {
      GrammarFileNameFieldListener(EvaluatorWindow w)
      {  window = w;
      }

      public void actionPerformed(ActionEvent e)
      {  window.grammarFileNameFieldChanged();
      }

      private EvaluatorWindow window;
   } // end class GrammarFileNameFieldListener

   private class ExpressionFieldListener implements ActionListener
   {
      ExpressionFieldListener(EvaluatorWindow w)
      {  window = w;
      }

      public void actionPerformed(ActionEvent e)
      {  window.expressionFieldChanged();
      }

      private EvaluatorWindow window;
   } // end class ExpressionFieldListener

private class OutputPaneMenu extends JPopupMenu implements ActionListener
   {  OutputPaneMenu(JTextArea p, Evaluator t)
      {  pane = p;
         evaluator = t;
         setInvoker(pane);

         JMenuItem initializeItem = new JMenuItem("Initialize parse");
         initializeItem.addActionListener(this);
         add(initializeItem);
         addSeparator();
         JMenuItem advanceItem = new JMenuItem("Advance one step");
         advanceItem.addActionListener(this);
         add(advanceItem);
         JMenuItem completeParseItem = new JMenuItem("Complete parse");
         completeParseItem.addActionListener(this);
         add(completeParseItem);
         addSeparator();
         JMenuItem showTreeItem = new JMenuItem("Show expression structure tree");
         showTreeItem.addActionListener(this);
         add(showTreeItem);
         addSeparator();
         JMenuItem selectAllItem = new JMenuItem("Select all");
         selectAllItem.addActionListener(this);
         add(selectAllItem);
         JMenuItem copyItem = new JMenuItem("Copy selection");
         copyItem.addActionListener(this);
         add(copyItem);
         addSeparator();
         JMenuItem clearItem = new JMenuItem("Erase output pane");
         clearItem.addActionListener(this);
         add(clearItem);
      } // end OutputPaneMenu(JTextArea p, Evaluator t)

      public void actionPerformed(ActionEvent e)
      {  if (evaluator == null) return;
         String command = e.getActionCommand();
         if (command.equals("Initialize parse"))
            evaluator.initializeParse(true); // also clears the value field
         else if (command.equals("Advance one step"))
            evaluator.advance();
         else if (command.equals("Complete parse"))
            evaluator.completeParse();
         else if (command.equals("Show expression structure tree"))
            evaluator.showPhraseStructure();
         else if (command.equals("Select all"))
         {  pane.requestFocus();
            pane.selectAll();
         }
         else if (command.equals("Copy selection"))
            pane.copy();
         else if (command.equals("Erase output pane"))
            pane.setText("");
      } // end actionPerformed

      Evaluator evaluator;
      JTextArea pane; // the pane to which the menu is attached.
   } // end class OutputPaneMenu

   class ActionMenu extends JMenu implements ActionListener
   {  ActionMenu(EvaluatorWindow w)
      {  super("Action");
         window = w;
         evaluator = window.getEvaluator();
         outputPane = window.getOutputPane();
         JMenuItem initializeMenuItem = new JMenuItem("Initialize parse",
            KeyEvent.VK_I);
         initializeMenuItem.setAccelerator(KeyStroke.getKeyStroke(
            KeyEvent.VK_I, ActionEvent.ALT_MASK));
         add(initializeMenuItem);
         initializeMenuItem.addActionListener(this);
         JMenuItem advanceMenuItem = new JMenuItem("Advance one Step",
            KeyEvent.VK_S);
         advanceMenuItem.setAccelerator(KeyStroke.getKeyStroke(
            KeyEvent.VK_S, ActionEvent.ALT_MASK));
         add(advanceMenuItem);
         advanceMenuItem.addActionListener(this);
         JMenuItem completeParseMenuItem = new JMenuItem("Complete Parse",
            KeyEvent.VK_P);
         completeParseMenuItem.setAccelerator(KeyStroke.getKeyStroke(
            KeyEvent.VK_P, ActionEvent.ALT_MASK));
         add(completeParseMenuItem);
         completeParseMenuItem.addActionListener(this);
         addSeparator();
         JMenuItem showTreeMenuItem = new JMenuItem(
            "Show expression structure tree", KeyEvent.VK_T);
         showTreeMenuItem.setAccelerator(KeyStroke.getKeyStroke(
            KeyEvent.VK_T, ActionEvent.ALT_MASK));
         showTreeMenuItem.addActionListener(this);
         add(showTreeMenuItem);
         addSeparator();
         JMenuItem copyAllMenuItem = new JMenuItem(
            "Select All of output pane", KeyEvent.VK_A);
         copyAllMenuItem.setAccelerator(KeyStroke.getKeyStroke(
            KeyEvent.VK_A, ActionEvent.CTRL_MASK));
         copyAllMenuItem.addActionListener(this);
         add(copyAllMenuItem);
         JMenuItem copySelectionMenuItem = new JMenuItem("Copy Selection",
            KeyEvent.VK_C);
         copySelectionMenuItem.setAccelerator(KeyStroke.getKeyStroke(
            KeyEvent.VK_C, ActionEvent.CTRL_MASK));
         copySelectionMenuItem.addActionListener(this);
         add(copySelectionMenuItem);
         addSeparator();
         JMenuItem eraseMenuItem = new JMenuItem("Erase output pane",
            KeyEvent.VK_E);
         eraseMenuItem.setAccelerator(KeyStroke.getKeyStroke(
            KeyEvent.VK_E, ActionEvent.CTRL_MASK));
         eraseMenuItem.addActionListener(this);
         add(eraseMenuItem);
      }

      /**
        Listens for menu item events.
       */
      public void actionPerformed(ActionEvent e)
      {  String command = e.getActionCommand();
         if (command.equals("Initialize parse"))
            evaluator.initializeParse(true); // also clears the value field
         else if (command.equals("Advance one Step"))
            evaluator.advance();
         else if (command.equals("Complete Parse"))
            evaluator.completeParse();
         else if (command.equals("Show expression structure tree"))
            evaluator.showPhraseStructure();
         else if (command.equals("Select All of output pane"))
         {  outputPane.requestFocus();
            outputPane.selectAll();
         }
         else if (command.equals("Copy Selection"))
            outputPane.copy();
         else if (command.equals("Erase output pane"))
            outputPane.setText("");
      }

      Evaluator evaluator;
      EvaluatorWindow window;
      JTextArea outputPane;
   } // end class ActionMenu

   class HelpMenu extends JMenu implements ActionListener
   {  HelpMenu(EvaluatorWindow w)
      {  super("Help");
         window = w;
         evaluator = window.getEvaluator();
         JMenuItem aboutMenuItem = new JMenuItem("About Evaluator",
            KeyEvent.VK_A);
         add(aboutMenuItem);
         aboutMenuItem.addActionListener(this);
      }

      /**
         Listens for menu item events.
       */
      public void actionPerformed(ActionEvent e)
      {  String command = e.getActionCommand();
         if (command.equals("About Evaluator"))
            evaluator.showAboutInfo();
      }

      Evaluator evaluator;
      EvaluatorWindow window;
   } // end class HelpMenu

} // end class Evaluator

/**
   This class can be used by any others in the arithmetic package,
   to pop up a given menu in response to a right mouse click.
 */
class PopupListener extends MouseAdapter
{  PopupListener(JPopupMenu m)
   {  menu = m;
   }

   public void mousePressed(MouseEvent e)
   {  maybeShowPopup(e);
   }

   public void mouseReleased(MouseEvent e)
   {  maybeShowPopup(e);
   }

   private void maybeShowPopup(MouseEvent e)
   {  if (e.isPopupTrigger())
        menu.show(e.getComponent(), e.getX(), e.getY());
   }

   private JPopupMenu menu;
} // end class PopupListener

