Package qat.parser.qashparser

Source Code of qat.parser.qashparser.QASHParser

package qat.parser.qashparser;

// JDK imports
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.net.ConnectException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.swing.JLabel;

import qat.common.Common;
import qat.common.ProtocolConstants;
import qat.common.Utils;
import qat.parser.AgentInstance;
import qat.parser.HtmlPrintStream;
import qat.parser.ParserInterface;

/**
* This file loads a single QAT file, and will attempt to resolve all keywords in this qat file
* file by first including any .INC statements, and their parent statements etc, until all neccesary files
* have been included.
*
* @author webhiker
* @version 2.3, 17 June 1999
*
*/
public class QASHParser extends Object implements ParserInterface {
  private static final Logger log = Logger.getAnonymousLogger();
  private static String QASHFileName="NOTSET";
  private static final char COMMA = ',';
  private static final char CONCAT_OPERATOR     = '+';
  private static final char DEC_OPERATOR        = '-';
  private static final char SEMICOLON           = ';';
  private static final char LOGICAL_AND         = '&';
  private static final char LOGICAL_OR          = '|';
  private static final char SET_OPERATOR        = '=';
  private static final char SOFTSET_OPERATOR    = '?';
  private static final char COMMENT             = '#';
  private static final char LEFT_PARENTHESIS    = '(';
  private static final char RIGHT_PARENTHESIS   = ')';
  private static final char GREATER_THAN        = '>';
  private static final char LESS_THAN           = '<';
  private static final char NOT_OPERATOR        = '!';

  // the following constants represent single character representations of
  // operators actually encoded as two characters
  private static final char LESS_THAN_EQ        = '['; // <=
  private static final char GREATER_THAN_EQ     = ']'; // >=
  private static final char NOT_EQUAL_OPERATOR  = '~'; // !=
  private static final char EQUALITY_OPERATOR   = '@'; // ==
  private static final char APPEND_OPERATOR     = '*'; // +=

  private static final int LOOP_OP         = 1000;
  private static final int INCLUDE         = 2000;
  private static final int IF              = 3000;
  private static final int THEN            = 4000;
  private static final int ELSE            = 5000;
  private static final int WHILE           = 6000;
  private static final int FOR             = 7000;
  private static final int TO              = 8000;
  private static final int BY              = 9000;
  private static final int DO              = 10000;
  private static final int PRINT           = 11000;
  private static final int END             = 12000;
  private static final int SETAGENT        = 13000;
  private static final int DELAGENT        = 14000;
  private static final int ZIPSEND         = 15000;
  private static final int ZIPCLEAN        = 16000;
  private static final int SETPROP         = 17000;
  private static final int DELPROP         = 18000;
  private static final int CMDSTART        = 19000;
  private static final int AUTOCLEAN_ON    = 20000;
  private static final int CMDSTOP         = 21000;
  private static final int CMDSTATUS       = 22000;
  private static final int CMDGETTRACE     = 23000;
  private static final int CMDCLEAN        = 24000;
  private static final int SLEEP           = 25000;
  private static final int REPORTSTATUS    = 26000;
  private static final int RANDOM          = 27000;
  private static final int GETFILE         = 28000;
  private static final int SENDFILE        = 29000;
  private static final int CHECKFILE       = 30000;
  private static final int CHECKFILE_LOCAL = 31000;
  private static final int CHECKAGENT      = 32000;
  private static final int DELFILE_LOCAL   = 33000;
  private static final int DELFILE         = 34000;
  private static final int MKDIR           = 35000;
  private static final int MKDIR_LOCAL     = 36000;
  private static final int SETSTATIC       = 37000;
  private static final int DELSTATIC       = 38000;
  private static final int GETSTATIC       = 39000;
  private static final int PRINTENV        = 40000;
  private static final int KILLAGENT       = 41000;
  private static final int ENDFUNCTION     = 42000;
  private static final int FUNCTION        = 43000;
  private static final int CALLFUNCTION    = 44000;
  private static final int AUTOCLEAN_OFF   = 45000;
  private static final int ENVTRACECONTAINS= 46000;
  private static final int STDOUTCONTAINS  = 47000;
  private static final int STDERRCONTAINS  = 48000;
  private static final int GETTRACEPATHS   = 49000;
  private static final int DAEMONSTART     = 50000;
  private static final int ZIPCREATE_LOCAL = 51000;

  private static Properties staticVariables;

  // ---- these are the property names expected in a qash or qinc file
  public static final String KEYWORD_TAG              = "qat.test.keywords";
  public static final String TEST_NAME_TAG            = "qat.test.name";
  public static final String TEST_AUTHOR_TAG          = "qat.test.author";
  public static final String TEST_BUGINFO_TAG         = "qat.test.buginfo";
  public static final String TEST_DESCRIPTION_TAG     = "qat.test.description";
  public static final String BUG_INFO                 = "qat.test.buginfo";
  /**
   * For internal use only.
   */
  public static final String INCLUDE_QASH_LIST       = "qash.internal.include_list";
  public static final String INCLUDE_PROPERTIES_LIST = "qash.internal.properties_list";
  public static final String INTERNAL_TRACE_LIST     = "qash.internal.trace_list";


  private QASHToken currToken;
  private List<QASHToken> stack;
  private List<List<QASHToken>> loopStack;
  private int loopStackCount;
  private String fileName;
  private Properties properties;
  private BufferedReader in;
  private StreamTokenizer stream;
  private int currQASHStatus;
  private boolean evaluating;
  private boolean interrupted=false;
  private static boolean statusReported=false;
  private HtmlPrintStream printStream;
  private JLabel parserStatus;
  private QASHProperties qashProperties;

  static {
    staticInit();
  }

  public QASHParser() {
    initParser();
  }

  /**
   * This form of the parser is used for the commandline interface.
   */
  public QASHParser(Properties p,
      QASHProperties qashProperties,
      String testPath,
      PrintStream printStream,
      boolean evaluating) {
    this();
    this.qashProperties = qashProperties;
    setEvaluationMode(evaluating);
    setTestPath(testPath);
    setProperties(p);
    setPrintStream(printStream,true);
  }

  /**
   * This method sets the path to root of the current project.
   */
  public void setProjectRoot(String projectRoot) {
    // we don't need this method
 

  private static void staticInit() {
    staticVariables = new Properties();
  }

  /**
   * This method is called at the beginning of a parser run
   * on one or more QASH files.
   */
  public void prepare(String newProjectResultsDirectory) {
    qashProperties = new QASHProperties();
    qashProperties.setProjectResultsDirectory(newProjectResultsDirectory);
  }

  /**
   * This method is called after a parser run on one
   * or more QASH files.
   */
  public void finish() {
    qashProperties.finish();
  }


  public String[] getSyntaxKeyWords() {
    return QASHConstants.getSyntaxKeywords();
  }

  public void resetVariables() {   
    // set the internal variable for qash script access
    setProperty("qat.qash.filepath",fileName);
    setProperty("qat.qash.path",(new File(fileName)).getParent());
  }

  public void setTestPath(String testFileName) {
    // set the name of the file currently being parsed
    fileName = testFileName;

    // if it's a qash file, remember it.
    // This is only done once per root qash, since a
    // qash may include other qash files
    if ((testFileName.toLowerCase().endsWith(".qash"))&&
        (QASHFileName.equals("NOTSET"))) {
      QASHFileName = testFileName;
    }    
  }


  public PrintStream openPrintStream(String fileName) throws java.io.FileNotFoundException {
    return new HtmlPrintStream(new PrintStream(new FileOutputStream(fileName),true),true)
  }

  public final void setPrintStream(PrintStream printStream, boolean useHtml) {
    if (printStream instanceof HtmlPrintStream) {
      this.printStream = (HtmlPrintStream)printStream;
    }
    else {
      this.printStream = new HtmlPrintStream(printStream,useHtml);
    }
  }

  public void setProperties(Properties p) {
    this.properties = p;
    addToIncludeList(fileName);
  }

  public final void setEvaluationMode(boolean mode) {
    evaluating = mode;
  }

  public final boolean inEvaluationMode() {
    return evaluating;
  }

  private int getTokenID(String token) {
    return QASHConstants.getTokenID(token);
  }

  private int getTokenID(QASHToken token) {
    return getTokenID(token.toString());
  }

  private String getTokenValue(int i) {
    return QASHConstants.getTokenValue(i);
  }

  /**
   * Clean up unused variables from this parse specific
   * to the root qash file.
   */
  private void parseFileClean() {
    String KEYWORD_TAG_VALUE             = getKeyWordsProperty();
    String TEST_NAME_TAG_VALUE           = getTestName();
    String TEST_AUTHOR_TAG_VALUE         = getTestAuthor();
    String TEST_DESCRIPTION_TAG_VALUE    = getTestDescription();
    String BUG_INFO_VALUE                = getProperty(BUG_INFO);
    String INCLUDE_MISC_LIST_VALUE       = getProperty(INCLUDE_QASH_LIST);
    String INCLUDE_PROPERTIES_LIST_VALUE = getProperty(INCLUDE_PROPERTIES_LIST);
    String INTERNAL_TRACE_LIST_VALUE     = getProperty(INTERNAL_TRACE_LIST);
    properties.clear();
    setProperty(KEYWORD_TAG , KEYWORD_TAG_VALUE);
    setProperty(TEST_NAME_TAG , TEST_NAME_TAG_VALUE);
    setProperty(TEST_AUTHOR_TAG , TEST_AUTHOR_TAG_VALUE);
    setProperty(TEST_DESCRIPTION_TAG , TEST_DESCRIPTION_TAG_VALUE);
    setProperty(BUG_INFO , BUG_INFO_VALUE);
    setProperty(INCLUDE_QASH_LIST , INCLUDE_MISC_LIST_VALUE);
    setProperty(INCLUDE_PROPERTIES_LIST , INCLUDE_PROPERTIES_LIST_VALUE);
    setProperty(INTERNAL_TRACE_LIST , INTERNAL_TRACE_LIST_VALUE);
    qashProperties.clearFunctions();
    QASHFileName = "NOTSET";
  }

  /**
   * This method is the main loop for parsing the QASH file.
   * It reads the file token by token, and calls the relevant method for
   * each token type.
   */
  public synchronized int parseFile() throws Exception {
    interrupted=false;
    setStatusReported(false);
    setStatus(internalParseFile());
    // if we are at the end of a parse, we can clear all
    // per-test variables here.
    parseFileClean();
    return getStatus();
  }

  /**
   * This method is the main loop for parsing the QASH file.
   * It reads the file token by token, and calls the relevant method for
   * each token type.
   */
  private synchronized int internalParseFile() throws Exception {
    resetVariables();
    setStatus(ProtocolConstants.PASSED);
    List<QASHToken> leftSide = new ArrayList<QASHToken>();
    int tokenId;
    try {
      in = new BufferedReader(new FileReader(fileName));
      stream = new StreamTokenizer(in);
      setupSyntax();
      resetParser();

      while ((!isInterrupted())&&
          (getNextToken().ttype != QASHToken.TT_EOF)&&
          (!isStatusReported())) {       
        tokenId = getTokenID(currToken.toString());
        switch(tokenId) {
        case IF : processIF();
        break;
        case FOR : processFOR();
        break;
        case WHILE : processWHILE();
        break;
        case PRINT : processPRINT();
        break;
        case INCLUDE : processINCLUDE();
        break;
        case SETAGENT :  processSETAGENT();
        break;
        case DELAGENT : processDELAGENT();
        break;
        case ZIPSEND : processZIPSEND();
        break;
        case ZIPCREATE_LOCAL : processZIPCREATE_LOCAL();
        break;
        case ZIPCLEAN : processZIPCLEAN();
        break;
        case SETPROP : processSETPROP();
        break;
        case DELPROP : processDELPROP();
        break;
        case CMDSTART :  processCMDSTART();
        break;
        case DAEMONSTART :  processDAEMONSTART();
        break;
        case CMDSTOP : processCMDSTOP();
        break;
        case CMDSTATUS : processCMDSTATUS();
        break;
        case CMDGETTRACE : processCMDGETTRACE();
        break;
        case CMDCLEAN :processCMDCLEAN();
        break;
        case SLEEP : processSLEEP();
        break;
        case REPORTSTATUS : processREPORTSTATUS();
        break;
        case RANDOM : processRANDOM();
        break;
        case GETFILE : processGETFILE();
        break;
        case SENDFILE : processSENDFILE();
        break;
        case CHECKFILE : processCHECKFILE();
        break;
        case CHECKFILE_LOCAL : processCHECKFILE_LOCAL();
        break;
        case CHECKAGENT : processCHECKAGENT();
        break;
        case DELFILE_LOCAL : processDELFILE_LOCAL();
        break;
        case DELFILE : processDELFILE();
        break;
        case MKDIR : processMKDIR();
        break;
        case MKDIR_LOCAL : processMKDIR_LOCAL();
        break;
        case KILLAGENT : processKILLAGENT();
        break;
        case FUNCTION : processFUNCTION();
        break;
        case CALLFUNCTION : processCALLFUNCTION();
        break;
        case SETSTATIC : processSETSTATIC();
        break;
        case GETSTATIC : processGETSTATIC();
        break;
        case DELSTATIC : processDELSTATIC();
        break;
        case PRINTENV : processPRINTENV();
        break;
        case AUTOCLEAN_ON : processAUTOCLEAN_ON();
        break;
        case AUTOCLEAN_OFF : processAUTOCLEAN_OFF();
        break;
        case ENVTRACECONTAINS : processENVTRACECONTAINS();
        break;
        case STDOUTCONTAINS : processSTDOUTCONTAINS();
        break;
        case STDERRCONTAINS : processSTDERRCONTAINS();
        break;
        case GETTRACEPATHS : processGETTRACEPATHS();
        break;
        case LOOP_OP : processLOOP_OP();
        break;
        default :
          switch (currToken.ttype) {
          case QASHToken.TT_EOL :
          case SEMICOLON :
            break;
          case SET_OPERATOR :
            processSET_OPERATOR(leftSide);
            break;
          case SOFTSET_OPERATOR :
            processSOFTSET_OPERATOR(leftSide);
            break;
          case CONCAT_OPERATOR :
            // must be part of a left side expression
            leftSide.add(currToken);
            break;
          case APPEND_OPERATOR :
            processAPPEND_OPERATOR(leftSide);
            break;
          default :
            // remembering
            leftSide.add(currToken);
          }
        }       
      }
      in.close();
    }
    catch (IOException e) {
      printError(2,"Couldn't resolve file (1)"+fileName+" :"+e.toString());
      setStatus(ProtocolConstants.UNRESOLVED);
    }
    catch (Throwable e) {
      printError(2,"Parser error trapped :"+fileName+" :"+e.toString());
      setStatus(ProtocolConstants.UNRESOLVED);
    }
    if (isInterrupted()) {
      if (!isStatusReported()) {
        setStatus(ProtocolConstants.UNRESOLVED);
      }
    }
    // report if user forgot to match SETAGENT/DELAGENT calls
    if ((qashProperties.getActiveAgentCount()>0)&&
        (isStatusReported())) {
      printWarning(22,"We still have "+qashProperties.getActiveAgentCount()+" uncleaned agents remaining from "+fileName);
      qashProperties.clearActiveAgents();
    }   

    return getStatus();
  }

  private boolean isInterrupted() {
    return interrupted;
  }

  public void interrupt() {
    interrupted = true;
    // now call kill for all active agents
    if (!inEvaluationMode()) {
      AgentInstance agent=null;
      for (Enumeration<AgentInstance> agentList = qashProperties.getActiveAgents() ; agentList.hasMoreElements() ;) {
        try {
          agent = (AgentInstance)agentList.nextElement();
          agent.KILLSTARTEDPROCESSES();
        }
        catch (Exception e) {
          printError(3,"Error killing one or more agent processes - recommend restarting agents manually!"+" :"+e.toString());
        }
      }
    }
    // clear our Hashtable now
    qashProperties.clearActiveAgents();
  }

  /**
   * Initialise all the line number counters etc used to parse a file.
   */
  private void initParser() {
    currToken = new QASHToken();
    setEvaluationMode(true);
    stack = new ArrayList<QASHToken>();
    loopStack = new ArrayList<List<QASHToken>>();
    resetParser();   
  }

  private void resetParser() {
    currToken.line = 0;
    loopStackCount = -1;
    stack.clear();
    loopStack.clear();
  }

  public void setupSyntax() {
    stream.resetSyntax();
    // indicate EOL is significant
    stream.eolIsSignificant(true);
    // set the whitespace chars
    stream.whitespaceChars(1,32);   
    // indicate we want to parse  numbers
    stream.parseNumbers();
    // set comments to start with a "#"
    stream.commentChar((int)COMMENT);
    // set the quote char we want to use
    stream.quoteChar((int)QASHToken.TT_STRING);       
    // set the characters accepted
    stream.wordChars('@', '_');
    stream.wordChars('a', '~');
    // set up the separator characters
    stream.ordinaryChar((int)LEFT_PARENTHESIS);
    stream.ordinaryChar((int)RIGHT_PARENTHESIS);
    stream.ordinaryChar((int)CONCAT_OPERATOR);
    stream.ordinaryChar((int)DEC_OPERATOR);
    stream.ordinaryChar((int)SET_OPERATOR);
    stream.ordinaryChar('!');
    stream.ordinaryChar((int)LOGICAL_AND);
    stream.ordinaryChar((int)LOGICAL_OR);
    stream.ordinaryChar((int)SOFTSET_OPERATOR);
    stream.ordinaryChar((int)GREATER_THAN);
    stream.ordinaryChar((int)LESS_THAN);
    stream.ordinaryChar((int)SEMICOLON);
  }

  private QASHToken getNextStreamToken() throws IOException {
    stream.nextToken();
    switch ((char)stream.ttype) {
    case QASHToken.TT_NUMBER :
      return new QASHToken(stream.nval,QASHToken.TT_NUMBER,stream.lineno());
    case COMMENT :
      return getNextStreamToken();
    case QASHToken.TT_EOL :
      return new QASHToken(QASHToken.TT_EOL,QASHToken.TT_EOL,stream.lineno());
    case QASHToken.TT_WORD :
      return new QASHToken(stream.sval,QASHToken.TT_WORD,stream.lineno());
    case QASHToken.TT_STRING :
      return new QASHToken(stream.sval,QASHToken.TT_STRING,stream.lineno());
    case QASHToken.TT_EOF :
      return new QASHToken(QASHToken.TT_EOF,QASHToken.TT_EOF,stream.lineno());
      // the next four traps are used to convert double char
      // operators into single char representations
    case CONCAT_OPERATOR : // +=
      if (doubleOp(stream))
        return new QASHToken(APPEND_OPERATOR,APPEND_OPERATOR,stream.lineno());
      else
        return new QASHToken(CONCAT_OPERATOR,CONCAT_OPERATOR,stream.lineno());
    case NOT_OPERATOR    : // !=
      if (doubleOp(stream))
        return new QASHToken(NOT_EQUAL_OPERATOR,NOT_EQUAL_OPERATOR,stream.lineno());
      else
        return new QASHToken(NOT_OPERATOR,NOT_OPERATOR,stream.lineno());
    case GREATER_THAN    : // >=
      if (doubleOp(stream))
        return new QASHToken(GREATER_THAN_EQ,GREATER_THAN_EQ,stream.lineno());
      else
        return new QASHToken(GREATER_THAN,GREATER_THAN,stream.lineno());
    case LESS_THAN       : // <=
      if (doubleOp(stream))
        return new QASHToken(LESS_THAN_EQ,LESS_THAN_EQ,stream.lineno());
      else
        return new QASHToken(LESS_THAN,LESS_THAN,stream.lineno());
    case SET_OPERATOR    : // ==
      if (doubleOp(stream))
        return new QASHToken(EQUALITY_OPERATOR,EQUALITY_OPERATOR,stream.lineno());
      else
        return new QASHToken(SET_OPERATOR,SET_OPERATOR,stream.lineno());
    default :
      return new QASHToken((char)stream.ttype,(char)stream.ttype,stream.lineno());
    }
  }

  /**
   * This method returns true if the next token is an '=', else
   * it puts the token back onto the stream.
   */
  private boolean doubleOp(StreamTokenizer stream) throws IOException
    if ((char)stream.nextToken()==SET_OPERATOR) {
      return true;
    }
    else {
      stream.pushBack();
      return false;
    }
  }

  private List<QASHToken> getTokensUntil(char c) throws Exception {
    List<QASHToken> result = new ArrayList<QASHToken>();
    while ((true)&&
        (!isInterrupted())) {
      getNextToken();
      if (currToken.ttype==c) {
        return result;
      }
      if (currToken.ttype==QASHToken.TT_EOL) {
        throw new Exception("Expected \""+c+"\" before EOL");
      }
      if (currToken.ttype==QASHToken.TT_EOF) {
        throw new Exception("Expected \""+c+"\" before EOF");
      }
      result.add(currToken);
    }
    return result;
  }

  /**
   * This method reads tokens until ";", "=", EOL or EOF is reached.
   */
  private List<QASHToken> getStatements() throws Exception {
    List<QASHToken> statement = new ArrayList<QASHToken>();
    QASHToken next;
    getNextToken();
    do  {
      next = currToken;
      switch(currToken.ttype) {
      case QASHToken.TT_WORD :
      case QASHToken.TT_NUMBER :
      case QASHToken.TT_STRING :
      case CONCAT_OPERATOR :
      case DEC_OPERATOR :
      case LEFT_PARENTHESIS :
      case RIGHT_PARENTHESIS :
        statement.add(next);
        break;
      case SET_OPERATOR :
      case SOFTSET_OPERATOR :
      case SEMICOLON :
      case QASHToken.TT_EOL :
      case QASHToken.TT_EOF :
        return statement;
      default :
        throw new Exception("Unexpected token in statement :"+currToken);
      }
      getNextToken();       
    } while (!isInterrupted());
    return statement;
  }

  /**
   * This method reads the tokens out of the file.
   * Sometimes a small lookahead is used.
   * When no more tokens are available, QASHToken.TT_EOF is returned.
   */
  private QASHToken getNextToken() throws IOException {
    // if the stack is empty, only then read another line
    if (stack.size()>0) {
      currToken = (QASHToken)stack.remove(0); // remove returns the element removed
    }
    else {
      currToken = getNextStreamToken();     
    }
    return currToken;
  }

  /**
   * This method ensures we maintain a list of all files which were used to resolve this QASH file.
   * The list of included files is saved in the property fields QASHParser.INCLUDE_PROPERTIES_LIST
   * and QASHParser.INCLUDE_MISC_LIST.
   */
  private void addToIncludeList(String fileName) {
    // now add this file to the include properties list if it has a ".properties" extension
    if (fileName.endsWith(".properties")) {
      addToIncludeList(INCLUDE_PROPERTIES_LIST,fileName);
      log.info(fileName+"--------------------->"+getProperty(INCLUDE_PROPERTIES_LIST));
    }
    // now add this file to the include misc list
    else {
      addToIncludeList(INCLUDE_QASH_LIST,fileName);  
      log.info(fileName+"--------------------->"+getProperty(INCLUDE_QASH_LIST));
    }
  }

  private void addToIncludeList(String key, String prop) {
    String list = getProperty(key,"");
    if (list.length()>0) {
      if (list.indexOf(prop)<0) {
        setProperty(key,list+File.pathSeparator+prop);
      }
    }
    else {
      setProperty(key,prop);
   
  }

  /**
   * This method reads a then statement block, and returns a ArrayList containing
   * each distinct token.
   * Due to the syntax restraints, a statement block is ended by an END or ELSE token.
   * These statements can then be re-pushed onto the stack depending on the value of if statements etc.
   * The last element of this ArrayList will be the matching ELSE or END.
   */
  private List<QASHToken> readThenStatementBlock() {
    List<QASHToken> tokens = new ArrayList<QASHToken>();
    try {
      QASHToken token=null;
      int endCount = 1;
      int tokenId;
      do {
        token = getNextToken();
        tokenId = getTokenID(token);
        if ((tokenId==FOR)||
            (tokenId==WHILE)||
            (tokenId==IF)) {
          endCount++;
        }
        else {
          if (tokenId==END) {
            endCount--;
          }
          if (tokenId==ELSE) {
            if (endCount==1) // then this one matches our then
              endCount--;
          }
        }
        tokens.add(currToken);
      } while ((!isInterrupted())&&
          (endCount>0)&&
          (currToken.ttype!=QASHToken.TT_EOF));
      if (currToken.ttype==QASHToken.TT_EOF)
        throw new Exception("The IF statement is missing an END or ELSE keyword");
    }
    catch (Exception e) {
      printError(4,"Statement syntax error "+e.getMessage(),currToken);
    }
    return tokens;
  }

  private List<QASHToken> readElseStatementBlock() {
    List<QASHToken> tokens = new ArrayList<QASHToken>();
    try {
      QASHToken token;
      int endCount = 1;
      int tokenId;
      do {
        token = getNextToken();
        tokenId = getTokenID(token);
        if ((tokenId==FOR)||
            (tokenId==WHILE)||
            (tokenId==IF)) {
          endCount++;
        }
        else {
          if (tokenId==END) {
            endCount--;
          }
        }
        tokens.add(token);
      } while ((!isInterrupted())&&
          (endCount>0)&&
          (currToken.ttype!=QASHToken.TT_EOF));
      if (currToken.ttype==QASHToken.TT_EOF)
        throw new Exception("The IF statement is missing an END keyword");
    }
    catch (Exception e) {
      printError(5,"Statement syntax error"+e.getMessage(),currToken);
    }
    return tokens;
  }

  /**
   * Process a line containing an INCLUDE statement.
   */
  private void processINCLUDE() {
    try {   
      QASHToken includeFileToken = resolveExpression(getStatements());
      String includeFile = includeFileToken.toString();

      setStatusText(QASHConstants.getTokenValue(INCLUDE),includeFile.toString());
      // check if it exists
      File f = new File(includeFile);
      if (!f.exists()) {
        throw new Exception("Include file not found");
      }
      // check if it's a .properties include or a .qinc/other type of file
      if (includeFile.toLowerCase().indexOf(Common.PROPERTIES_EXTENSION)>0) {
        // load the properties file, possibly using cached version
        // if we requested it previously
        Properties newProperties = qashProperties.getProperties(includeFile);
        properties = Utils.mergeProperties(properties,newProperties);
      }
      else {
        QASHParser childParser = new QASHParser(properties, qashProperties, includeFile, printStream, inEvaluationMode());
        childParser.setStatusLabel(parserStatus);
        int includeStatus = childParser.internalParseFile();       
        properties = childParser.getProperties();
        if (includeStatus!=ProtocolConstants.PASSED) {
          if (getStatus() != ProtocolConstants.UNRESOLVED) { // unresolved always overrides any test result
            setStatus(includeStatus);
            setStatusReported(true);
          }
        }
      }
      addToIncludeList(includeFile);
    }
    catch (Exception e) {
      printError(INCLUDE,"Syntax or file error in include statement ("+e.getMessage()+") :",currToken);
    }
    finally {
      resetVariables();
    }
  }

  //     /**
  //      * This method reads an expression of the form (.....) and will
  //      * not replace any variable reference by their actual values. It may still contain
  //      * append ops and parenthesis, so ideally need to call resolveExpression.
  //      */
  //     private ArrayList readExpression() throws Exception {
  //   expectLeftParenthesis();
  //   ArrayList expression = new ArrayList();
  //   int lcount=0,rcount=0;

  //   while (!isInterrupted()) {
  //       switch (currToken.ttype) {
  //       case QASHToken.TT_WORD :
  //     expression.add(currToken);
  //     break;
  //       case LEFT_PARENTHESIS :
  //     lcount++;
  //     expression.add(currToken);
  //     break;
  //       case RIGHT_PARENTHESIS :
  //     rcount++;
  //     expression.add(currToken);
  //     break;
  //       case QASHToken.TT_EOF :
  //     return expression;
  //       default :expression.add(currToken);
  //       }
  //       if (lcount==rcount)
  //     break;
  //       getNextToken();
  //   }
  //   return expression;
  //     }

  /**
   * This method reads a boolean expression of the form (.....) and will
   * replace any variable reference by their actual values. It may still contain
   * append ops and parenthesis, so ideally need to call resolveExpression.
   */
  private List<QASHToken> readBooleanExpression() throws Exception {
    return readExpression(true, true);
  }    /**
   * Same as above method, however will not replace variable names by values.
   * This method reads a boolean expression of the form (.....).
   * If replace is true, it will replace any variable references by their actual values.
   * It may still contain append ops and parenthesis,
   * so ideally need to call resolveExpression.
   * If enforce is true, any unknown variable will generate an exception.
   */
  private List<QASHToken> readExpression(boolean replace, boolean enforce) throws Exception {
    expectLeftParenthesis();
    List<QASHToken> expression = new ArrayList<QASHToken>();
    int lcount=0,rcount=0;

    while (!isInterrupted()) {
      switch (currToken.ttype) {
      case QASHToken.TT_WORD :
        if (enforce) {
          if (getToken(currToken.toString())==null) {
            throw new Exception("Unidentified identifier "+currToken.toString());
          }
        }
        if (replace)
          expression.add(getToken(currToken.toString()));
        else       
          expression.add(currToken);
        break;
      case LEFT_PARENTHESIS :
        lcount++;
        expression.add(currToken);
        break;
      case RIGHT_PARENTHESIS :
        rcount++;
        expression.add(currToken);
        break;
      case QASHToken.TT_EOF :
        return expression;
      default :expression.add(currToken);
      }
      if (lcount==rcount)
        break;
      getNextToken();
    }
    return expression;
  }

  /**
   * Process a line containing an IF statement.
   */
  private void processIF()  {
    int ifLineNumber = currToken.line;
    try {
      List<QASHToken> expression = readBooleanExpression();
      // discard the THEN statement
      do {
        getNextToken();
      } while ((currToken.ttype!=QASHToken.TT_EOF)&&
          (!isInterrupted())&&
          (currToken.ttype!=QASHToken.TT_WORD));

      if (!currToken.toString().equals(getTokenValue(THEN)))
        throw new Exception("Expected \"THEN\" statement instead of \""+currToken+"\"");

      List<QASHToken> thenStatements = readThenStatementBlock();
      // if there is an ELSE, read it's block
      List<QASHToken> elseStatements;
      if (getTokenID((QASHToken)thenStatements.get(thenStatements.size()-1))==ELSE) {
        elseStatements = readElseStatementBlock();
        elseStatements.remove(elseStatements.size()-1);// remove the END token
      }
      else {
        elseStatements = new ArrayList<QASHToken>();
      }
      thenStatements.remove(thenStatements.size()-1);// remove the ELSE or END token
      if (evaluateBooleanExpression(expression)) {
        pushOntoStack(thenStatements);
      }
      else {
        pushOntoStack(elseStatements);
      }
    }
    catch (Exception e) {
      printError(IF,"Syntax problem with IF statement ("+e.getMessage()+")",ifLineNumber);
    }
  }

  /**
   * This method has the effect of push the statements in this vector back
   * onto out stack for processing - used to re-ahead in statement blocks.
   */
  private void pushOntoStack(List<QASHToken> v) {
    for (int i = 0; i < v.size(); i++) {
      stack.add(0+i,v.get(i));
    }
  }

  /**
   * Process a line containing a WHILE statement.
   */
  private void processWHILE() {
    int whileLineNumber = currToken.line;
    try {      // HERE IS THE ERROR - the readBooleanExpression replaces variables by their value.
      // write a new method which keeps the variables in place so
      // when we create a LOOP_OP it will keep getting updated
      List<QASHToken> expression = readExpression(false,true);      // throw away ")"
      getNextToken();
      if (!currToken.toString().equals(getTokenValue(DO)))
        throw new Exception("Expected DO keyword instead of "+currToken);

      // now read until matching END statement
      List<QASHToken> tokens = new ArrayList<QASHToken>();
      QASHToken token;
      int tokenId;
      int endCount = 1;
      do {
        token = getNextToken();
        tokenId = getTokenID(token);
        if ((tokenId==FOR)||
            (tokenId==WHILE)||
            (tokenId==IF)) {
          endCount++;
        }
        else {
          if (tokenId==END) {
            endCount--;
          }
        }
        tokens.add(token);
      } while ((!isInterrupted())&&
          (endCount>0)&&
          (currToken.ttype!=QASHToken.TT_EOF));

      // check the expression - if it's false we can dump all the code and skip to next op
      if (evaluateBooleanExpression(expression)) {
        // remove the END statement
        tokens.remove(tokens.size()-1);
        // add the special loop operator
        tokens.add(new QASHToken(getTokenValue(LOOP_OP),QASHToken.TT_WORD,0));
        // add the loop type
        tokens.add(new QASHToken(getTokenValue(WHILE),QASHToken.TT_WORD,0));
        // add the condition expression
        for (int i = 0; i < expression.size(); i++)
          tokens.add((QASHToken)expression.get(i));

        // add the index into the loop stack
        loopStackCount++;
        tokens.add(new QASHToken(Integer.toString(loopStackCount),QASHToken.TT_WORD,0));

        // now add this while block onto our loop Stack
        loopStack.add(tokens);
        pushOntoStack(tokens);
      }

    }
    catch (Exception e) {
      printError(WHILE,"Syntax problem in WHILE loop :"+e.getMessage(),whileLineNumber);
    }
  }

  /**
   * This indicates we are currently in a loop. We read an expression, and if it's true,
   * repush the loopStack[loopStackIndex] set of tokens onto the main program stack.
   * If the expression evaluates to false, we can delete this set of tokens from the loopStack.
   */
  private void processLOOP_OP() {
    int loopLineNumber = currToken.line;
    try {
      QASHToken loopType = getNextToken();
      if (loopType.toString().equals(getTokenValue(FOR))) {
        QASHToken variable = getNextToken();
        getNextToken(); // should be the APPEND_OPERATOR +=
        QASHToken incAmount = getNextToken();
        setProperty(variable.toString(),
            Integer.toString(Integer.parseInt(getToken(variable.toString()).toString())+
                Integer.parseInt(incAmount.toString())));
      }
      List<QASHToken> expression = readBooleanExpression();
      int loopExpressionLineNumber = currToken.line;
      int loopStackIndex = (new Integer(getNextToken().toString())).intValue();
      List<QASHToken> loopArray;
      int value;
      currToken.line = loopExpressionLineNumber;
      if (evaluateBooleanExpression(expression)) {
        pushOntoStack(loopStack.get(loopStackIndex));
      }
      else {
        // finished with this loop, so discard it's ArrayList
        // need to decrement all the stack pointers after this one
        for (int i = loopStackIndex; i < loopStack.size(); i++) {
          loopArray = loopStack.get(i);
          value = new Integer(((QASHToken)loopArray.get(loopArray.size()-1)).toString()).intValue()-1;
          loopArray.set(loopArray.size()-1,new QASHToken(new Integer(value).toString(),QASHToken.TT_WORD,0));
        }
        loopStack.remove(loopStackIndex);
        loopStackCount--;
      }
    }
    catch (Exception e) {
      printError(LOOP_OP,"Internal error occurred during loop processing :"+e.getMessage(),loopLineNumber);
    }
  }

  /**
   * Process a line containing a PRINT statement.
   */
  private void processPRINT() {
    try {
      setStatusText(currToken.toString());
      QASHToken tokenToPrint = resolveExpression(getStatements());
      printStream.println(HtmlPrintStream.GREEN,tokenToPrint.toString());
    }
    catch (Exception ex) {
      printError(PRINT,"Print syntax incorrect :"+ex.getMessage(),currToken);
    }
  }

  /**
   * Process a line containing a FOR statement.
   */
  private void processFOR() {
    int forLineNumber = currToken.line;
    try {
      QASHToken leftSide = getNextToken();
      getNextToken();
      if (currToken.ttype!=(int)SET_OPERATOR)
        throw new Exception("Expected = operator but found "+currToken.toString());
      QASHToken rightSide = getNextToken();

      getNextToken();
      if (!currToken.toString().equals(getTokenValue(TO)))
        throw new Exception("Expected TO keyword but found "+currToken.toString());
      QASHToken upperLimitString = getNextToken();            QASHToken doString = getNextToken();
      // check if we have a BY keyword
      QASHToken incAmount = new QASHToken("1",QASHToken.TT_NUMBER,currToken.line);
      if (doString.toString().indexOf(getTokenValue(BY))>=0) {
        incAmount = getNextToken();
        doString = getNextToken();
      }      // check if right side is a constant or a variable
      try {
        Integer.parseInt(rightSide.toString());
        setProperty(leftSide.toString(),rightSide.toString());
      }
      catch (NumberFormatException ex) {
        // must be a variable, so resolve it
        setProperty(leftSide.toString(),resolveExpression(rightSide).toString());
      }

      QASHToken loopVariable = leftSide;

      // read until matching END statement
      List<QASHToken> tokens = new ArrayList<QASHToken>();
      QASHToken token;
      int tokenId;
      int endCount = 1;
      do {
        token = getNextToken();
        tokenId=getTokenID(token);
        if ((tokenId==FOR)||
            (tokenId==WHILE)||
            (tokenId==IF)) {
          endCount++;
        }
        else {
          if (tokenId==END) {
            endCount--;
          }
        }
        tokens.add(token);
      } while ((!isInterrupted())&&
          (endCount>0)&&
          (currToken.ttype!=QASHToken.TT_EOF));

      // check the expression - if it's false we can dump all the code and skip to next op
      List<QASHToken> expression = new ArrayList<QASHToken>();
      expression.add(new QASHToken(LEFT_PARENTHESIS,LEFT_PARENTHESIS,currToken.line));
      expression.add(loopVariable);
      expression.add(new QASHToken(LESS_THAN_EQ,LESS_THAN_EQ,currToken.line));
      expression.add(upperLimitString);
      expression.add(new QASHToken(RIGHT_PARENTHESIS,RIGHT_PARENTHESIS,currToken.line));

      if (evaluateBooleanExpression(expression)) {
        // remove the END statement
        tokens.remove(tokens.size()-1);

        // add the special loop operator
        tokens.add(new QASHToken(getTokenValue(LOOP_OP),QASHToken.TT_WORD,0));
        // add the loop type
        tokens.add(new QASHToken(getTokenValue(FOR),QASHToken.TT_WORD,0));

        // add the increment statement
        tokens.add(loopVariable);
        tokens.add(new QASHToken(APPEND_OPERATOR,APPEND_OPERATOR,0));
        tokens.add(incAmount);

        // add the condition expression
        tokens.add(new QASHToken(LEFT_PARENTHESIS,LEFT_PARENTHESIS,0));
        tokens.add(loopVariable);
        tokens.add(new QASHToken(LESS_THAN_EQ,LESS_THAN_EQ,stream.lineno()));
        tokens.add(upperLimitString);
        tokens.add(new QASHToken(RIGHT_PARENTHESIS,RIGHT_PARENTHESIS,0));    // add the index into the loop stack
        loopStackCount++;
        tokens.add(new QASHToken(Integer.toString(loopStackCount),QASHToken.TT_WORD,0));

        // now add this while block onto our loop Stack
        loopStack.add(tokens);
        pushOntoStack(tokens);
      }

    }
    catch (Exception e) {
      printError(FOR,"Syntax problem in FOR loop :"+e.getMessage(),forLineNumber);
    }
  }

  /**
   * Process an SET command.
   */
  private void processSET_OPERATOR(List<QASHToken> leftSideExpr) {
    try {
      QASHToken leftSide = resolveName(leftSideExpr);
      leftSideExpr.clear();
      List<QASHToken> rightSideExpr = getStatements();
      QASHToken rightSide = resolveExpression(rightSideExpr);
      setProperty(leftSide.toString(),rightSide.toString());
    }
    catch (Exception e) {
      printError(SET_OPERATOR,"Problem processing set statement command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Process an APPEND command of the form "simplestatement += complexstatement".
   */
  private void processAPPEND_OPERATOR(List<QASHToken> leftSideExpr) {
    try {
      QASHToken leftSide = resolveName(leftSideExpr);
      QASHToken leftSideVal = resolveExpression(leftSideExpr);
      leftSideExpr.clear();
      QASHToken rightSide = resolveExpression(getStatements());     
      leftSideVal.append(rightSide);     
      setProperty(leftSide,leftSideVal);
    }
    catch (Exception e) {
      printError(SET_OPERATOR,"Problem processing append statement ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Same as a set command, but only applied if the variable is not already defined.
   */
  private void processSOFTSET_OPERATOR(List<QASHToken> leftSideExpr) {
    try {
      QASHToken leftSideName = resolveName(leftSideExpr);
      QASHToken rightSide = resolveExpression(getStatements());
      leftSideExpr.clear();

      if (getToken(leftSideName)==null) {
        setProperty(leftSideName,rightSide);
      }
    }
    catch (Exception e) {
      printError(SOFTSET_OPERATOR,"Problem processing soft set statement command ("+e.toString()+") :"+leftSideExpr,currToken);
    }  
  }


  /**
   * This command creates an agent instance used to send,recieve,execute commands.
   * SYNTAX : SETAGENT(agentID, agentName, agentPort, agentWorkDirectory)
   * The identifier to use this agent instance is returned in the agentID property.
   */
  private void processSETAGENT() {
    try {
      expectLeftParenthesis();
      List<QASHToken> expression = getTokensUntil(COMMA);
      QASHToken agentID = resolveName(expression);

      expression = getTokensUntil(COMMA);
      QASHToken agentName = resolveExpression(expression);

      expression = getTokensUntil(COMMA);
      QASHToken agentPort = resolveExpression(expression);

      expression = getTokensUntil(RIGHT_PARENTHESIS);
      QASHToken agentWorkDir = resolveExpression(expression);

      setStatusText(QASHConstants.getTokenValue(SETAGENT),agentID.toString());

      // check that this agentID is unused
      if (getToken(agentID)!=null) {
        throw new Exception("Identifier already exists :"+agentName.toString());
      }
      int uniqueID = Utils.getUniqueID();
      AgentInstance agentInstance =  new AgentInstance(agentName.toString(),
          new Integer(agentPort.toString()).intValue(),
          agentWorkDir.toString(),
          inEvaluationMode());
      qashProperties.addActiveAgent(new Integer(uniqueID),agentInstance);
      // set the agentID property to reflect the agent ID which was allocated
      setProperty(agentID.toString(),Integer.toString(uniqueID));
    }
    catch (Exception e) {
      printError(SETAGENT,"Problem processing SETAGENT command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * This command removes a previously defined agent, as well as deleting that agents work directory.
   * Any files in the work directory, and their subdirectories will be removed.
   * SYNTAX : DELAGENT(agentID)
   */
  private void processDELAGENT() {
    try {
      expectLeftParenthesis();
      QASHToken agentIDName = resolveName(getTokensUntil(RIGHT_PARENTHESIS));
      QASHToken agentID = resolveExpression(agentIDName);      setStatusText(QASHConstants.getTokenValue(DELAGENT),agentIDName.toString());

      AgentInstance agentInstance = (AgentInstance)qashProperties.removeActiveAgent(new Integer(agentID.toString())); // remove also returns the object it deleted
      if (agentInstance==null)
        throw new Exception("Unknown agentID");
      try {
        agentInstance.DELAGENT();
      }
      catch (Exception ex) {
        printError(DELAGENT,"Error cleaning up agent ("+ex.toString()+") :",currToken);
      }
      finally {
        // unset this agentID property
        removeProperty(agentIDName);
      }
    }
    catch (Exception e) {
      printError(DELAGENT,"Problem in DELAGENT command ("+e.toString()+") :",currToken);
    }   
  }

  /**
   * This command sends a zip file to the specified agent to be unzipped.
   * SYNTAX : ZIPSEND(agentID, zipID, zipFile)
   */
  private void processZIPSEND() {
    try {
      expectLeftParenthesis();
      QASHToken agentID = resolveExpression(getTokensUntil(COMMA));
      QASHToken zipID = resolveName(getTokensUntil(COMMA));
      QASHToken zipFile = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));

      setStatusText(QASHConstants.getTokenValue(ZIPSEND),zipID.toString());

      // check that this zip id is unused
      if (getToken(zipID)!=null) {
        throw new Exception("ZIP Identifier already exists :"+zipID.toString()+SET_OPERATOR+getToken(zipID).toString());
      }
      AgentInstance agentInstance = (AgentInstance)qashProperties.getActiveAgent(new Integer(agentID.toString()));
      if (agentInstance==null)
        throw new Exception("Unknown agentID");
      // send the zip file, and retrieve the associated zip ID
      try {
        setProperty(zipID.toString(),agentInstance.ZIPSEND(zipFile.toString()));
      }
      catch (Exception ex) {
        printError(ZIPSEND,"Problem sending zip file to agent ("+ex.toString()+") :",currToken);
      }
    }
    catch (Exception e) {
      printError(ZIPSEND,"Problem processing ZIPSEND command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * This command commands the agent to delete the previously sent zip file with ID
   * agentID and all the files it unzipped.
   * SYNTAX : ZIPCLEAN(zipID)
   */
  private void processZIPCLEAN() {
    try {
      expectLeftParenthesis();
      List<QASHToken> zipIDVariable = getTokensUntil(RIGHT_PARENTHESIS);
      QASHToken zipIDVar = resolveName(zipIDVariable);
      QASHToken zipID = resolveExpression(zipIDVar);

      setStatusText(QASHConstants.getTokenValue(ZIPCLEAN),zipIDVar.toString());

      AgentInstance agentInstance = getAgentForZip(zipID.toString());
      if (agentInstance==null)
        throw new Exception("Unknown zipID "+zipIDVar);
      agentInstance.ZIPCLEAN(zipID.toString());
      removeProperty(zipIDVar.toString());
    }
    catch (Exception e) {
      printError(ZIPCLEAN,"Problem processing ZIPCLEAN command ("+e.toString()+") :",currToken);
    }
  }


  /**
   * This command creates a zip file called zipFileName, containing the contents of the
   * specified dirName. This is done on the maching running the harness only.
   * SYNTAX : ZIPCREATE_LOCAL(zipFileName, dirName)
   */
  private void processZIPCREATE_LOCAL() {
    try {
      expectLeftParenthesis();
      QASHToken zipFileName = resolveExpression(getTokensUntil(COMMA));
      QASHToken dirName = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));

      setStatusText(QASHConstants.getTokenValue(ZIPCREATE_LOCAL),zipFileName.toString());

      File cpFile = new File (dirName.toString());
      if (!cpFile.isFile() && !cpFile.isDirectory() ) {             
        printError(ZIPSEND,"Problem processing ZIPCREATE_LOCAL command ("+dirName+" does not exist) :",currToken);
        return;         
      }               
      FileOutputStream fos = new FileOutputStream(zipFileName.toString());     
      ZipOutputStream cpZipOutputStream = new ZipOutputStream(fos);         
      cpZipOutputStream.setLevel(9);         
      zipFiles(cpZipOutputStream, cpFile, dirName.toString());         
      cpZipOutputStream.finish();         
      cpZipOutputStream.close();       

    }
    catch (Exception e) {
      printError(ZIPSEND,"Problem processing ZIPCREATE_LOCAL command ("+e.toString()+") :",currToken);
    }
  }

  private void  zipFiles(ZipOutputStream cpZipOutputStream, File cpFile, String offset) {       

    int byteCount;
    final int DATA_BLOCK_SIZE = 2048
    FileInputStream cpFileInputStream;


    if (cpFile.isDirectory()) {
      File [] fList = cpFile.listFiles() ;
      for (int i=0; i< fList.length; i++){
        zipFiles(cpZipOutputStream, fList[i], offset) ;
      }       
    }

    else {
      try {               
        String strAbsPath = cpFile.getPath();       
        String strZipEntryName = strAbsPath.substring(offset.length()+1, strAbsPath.length());
        cpFileInputStream = new FileInputStream (cpFile) ;
        ZipEntry cpZipEntry = new ZipEntry(strZipEntryName);
        cpZipOutputStream.putNextEntry(cpZipEntry );

        byte[] b = new byte[DATA_BLOCK_SIZE];
        while ( (byteCount = cpFileInputStream.read(b, 0, DATA_BLOCK_SIZE)) != -1)
        {
          cpZipOutputStream.write(b, 0, byteCount);
        }

        cpZipOutputStream.closeEntry() ;
      } catch (Exception e) {               
        e.printStackTrace();           
      }       
    }   
  }

  /**
   * Set a property on an agent which will be visible to all following CMDSTART calls on that agent.
   * SYNTAX : SETPROP(AGENT_ID, KEY, VALUE) or SETPROP(AGENT_ID, KEY)
   */
  private void processSETPROP() {
    QASHToken key=null, value=null;
    try {
      expectLeftParenthesis();
      QASHToken agentID = resolveExpression(getTokensUntil(COMMA));     
      List<QASHToken> expression = getTokensUntil(RIGHT_PARENTHESIS);

      int commaIndex;
      if ((commaIndex=indexOf(expression,COMMA))>=0) {
        // form 1 of the allowed syntax
        List<QASHToken> keyList, valueList;
        keyList = copyRange(expression,0,commaIndex);
        valueList = copyRange(expression,commaIndex+1,expression.size());
        key = resolveExpression(keyList);
        value = resolveExpression(valueList);
      }
      else {
        // form 2 of the allowed syntax
        key = resolveName(expression);
        value = resolveExpression(key);
      }     
      AgentInstance agentInstance = (AgentInstance)qashProperties.getActiveAgent(new Integer(agentID.toString()));
      if (agentInstance==null)
        throw new Exception("Unknown agentID");
      agentInstance.SETPROP(key.toString(),value.toString());
    }
    catch (Exception e) {
      printError(SETPROP,"Problem processing SETPROP command ("+e.toString()+") :",currToken);
    }
  } 

  /**
   * Deletes a property on an agent which was previously set by a call to SETPROP
   * SYNTAX : DELPROPPROP(AGENT_ID, KEY)
   */
  private void processDELPROP() {
    try {
      expectLeftParenthesis();
      QASHToken agentIDName = resolveName(getTokensUntil(COMMA));
      QASHToken key = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));
      QASHToken agentID = resolveExpression(agentIDName);

      AgentInstance agentInstance = (AgentInstance)qashProperties.getActiveAgent(new Integer(agentID.toString()));
      if (agentInstance==null)
        throw new Exception("Unknown agentID");
      agentInstance.DELPROP(key.toString(),null);
    }
    catch (Exception e) {
      printError(DELPROP,"Problem processing DELPROP command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Excutes a command on a pre-activated agent.
   * SYNTAX: CMDSTART(agentID, processID, command, timeout) or CMDSTART(agentID, processID, command)
   * Warning :The second form will use a default timeout value of infinity. A call to CMDSTATUS
   * will then only return if the executed process exits normally.
   */
  private void processCMDSTART() {
    QASHToken command=null, timeout=null;
    try {
      expectLeftParenthesis();
      QASHToken agentID = resolveExpression(getTokensUntil(COMMA));
      QASHToken processID = resolveName(getTokensUntil(COMMA));     

      setStatusText(QASHConstants.getTokenValue(CMDSTART),processID.toString());

      // check that this process id is unused
      if (getToken(processID)!=null) {
        throw new Exception("processID Identifier already exists :"+processID+SET_OPERATOR+getToken(processID).toString());
      }
      List<QASHToken> expression = getTokensUntil(RIGHT_PARENTHESIS);
      int commaIndex;
      if ((commaIndex=indexOf(expression,COMMA))>=0) {
        // form 1 of the allowed syntax
        List<QASHToken> commandList, timeoutList;
        commandList = copyRange(expression,0,commaIndex);
        timeoutList = copyRange(expression,commaIndex+1,expression.size());
        command = resolveExpression(commandList);
        timeout = resolveExpression(timeoutList);
      }
      else {
        // form 2 of the allowed syntax
        command = resolveExpression(expression);
        timeout = new QASHToken("0",QASHToken.TT_NUMBER,command.line);
      }       

      AgentInstance agentInstance = (AgentInstance)qashProperties.getActiveAgent(new Integer(agentID.toString()));
      if (agentInstance==null)
        throw new Exception("Unknown agentID");      setProperty(processID.toString(),agentInstance.CMDSTART(strToArray(command.toString()), timeout.toString()));
    }
    catch (Exception e) {
      printError(CMDSTART,"Problem processing CMDSTART command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Converts a string of form "cmd arg1 arg2 arg3" into an array [cmd arg1, arg2, arg3]
   */
  private static String[] strToArray(String command) {
    try {
      int quoteChar = '\"';
      List<String> args = new ArrayList<String>();
      StreamTokenizer stream = new StreamTokenizer(new StringReader(command));
      stream.resetSyntax();
      // set the characters accepted
      stream.wordChars(33, 126);
      stream.whitespaceChars(1,32);
      stream.quoteChar(quoteChar);

      while (stream.nextToken()!=StreamTokenizer.TT_EOF) {
        //        if (stream.ttype==quoteChar) {
        //          args.add('\"'+stream.sval+'\"');
        //        }
        //        else {
        args.add(stream.sval);
        //        }
      }
      String strArgs[] = new String[args.size()];
      for (int i = 0; i < strArgs.length; i++) {   
        strArgs[i] = (String)args.get(i);
      }
      return strArgs;
    }
    catch(Exception ex) {
      ex.printStackTrace();
      return new String[0];
    }
  }    /**
   * Cleans the stdout and stderr files created by the process.
   * SYNTAX: CMDSTOP(processID) or CMDSTOP(processID, status)
   */
  private void processCMDSTOP() {
    try {
      QASHToken processIDName;
      QASHToken processID;
      QASHToken statusID=null;
      expectLeftParenthesis();

      List<QASHToken> expression = getTokensUntil(RIGHT_PARENTHESIS);
      int commaIndex;
      if ((commaIndex=indexOf(expression,COMMA))>=0) {
        // form 2 of the allowed syntax
        List<QASHToken> processIDList, statusIdList;
        processIDList = copyRange(expression,0,commaIndex);
        statusIdList  = copyRange(expression,commaIndex+1,expression.size());
        processIDName = resolveName(processIDList);
        statusID = resolveName(statusIdList);
      }
      else {
        // form 1 of the allowed syntax
        processIDName = resolveName(expression);
      }

      processID = getToken(processIDName);

      setStatusText(QASHConstants.getTokenValue(CMDSTOP),processIDName.toString());

      AgentInstance agentInstance = getAgentRunningProcess(processID.toString());
      if (agentInstance==null)
        throw new Exception("Unknown processID");
      if (statusID!=null) {
        //form 2
        setProperty(statusID.toString(),agentInstance.CMDSTOP(processID.toString()));
      }
      else {
        // form 1
        agentInstance.CMDSTOP(processID.toString());
      }
    }
    catch (Exception e) {
      printError(CMDSTOP,"Problem processing CMDSTOP command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Returns the status of the executed process..
   * SYNTAX: CMDSTATUS(processID, statusValue)
   * Warning : If the timeout was not set in the CMDSTART, this method will block until
   * the executed process exits.
   */
  private void processCMDSTATUS() {
    try {
      expectLeftParenthesis();
      QASHToken processIDName = resolveName( getTokensUntil(COMMA));
      QASHToken processID = getToken(processIDName);
      QASHToken processStatus = resolveName(getTokensUntil(RIGHT_PARENTHESIS));

      setStatusText(QASHConstants.getTokenValue(CMDSTATUS),processIDName.toString());

      AgentInstance agentInstance = getAgentRunningProcess(processID.toString());

      if (agentInstance==null)
        throw new Exception("Unknown processID");

      String statusCode = agentInstance.CMDSTATUS(processID.toString());
      setProperty(processStatus.toString(),statusCode);

      // now print out a message if the status was timed out
      int value = Integer.parseInt(statusCode);
      if (value == qat.agent.ExecProcess.TIMEDOUT_STATE) {
        printDebug("The command "+processIDName+" timed out!");
      }
      else {
        if (value < 0) {
          printDebug("The command "+processIDName.toString()+" failed!");
        }
        else {
          printDebug("The command "+processIDName.toString()+" exited normally");
        }
      }
    }
    catch (Exception e) {
      printError(CMDSTATUS,"Problem processing CMDSTATUS command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Cleans the stdout and stderr files created by the process.
   * SYNTAX: CMDGETTRACE(processID)
   * Warning : Do not call this method until after the output has been retrieved.
   */
  private void processCMDGETTRACE() {
    try {
      List<QASHToken> expression = readExpression(false, true);
      //  log.info("---------------------");
      //  log.info("expression>>>>>>>>>"+expression);
      QASHToken processIDName = resolveName(expression);
      //log.info("processIDName>>>>>>>>>"+processIDName);
      QASHToken processID = getToken(processIDName);
      //log.info("processID>>>>>>>>>"+processID);
      //log.info("---------------------");

      setStatusText(QASHConstants.getTokenValue(CMDGETTRACE),processIDName.toString());

      AgentInstance agentInstance = getAgentRunningProcess(processID.toString());

      if (agentInstance==null)
        throw new Exception("Unknown processID");
      agentInstance.CMDGETTRACE(processID.toString(),
          qashProperties.getProjectResultsDirectory()+File.separator+
          Common.getUniqueTestIdentifier(QASHFileName)+"_"+processIDName.toString());
      addToIncludeList(INTERNAL_TRACE_LIST,processIDName.toString());
    }
    catch (Exception e) {
      e.printStackTrace();
      printError(CMDGETTRACE,"Problem processing CMDGETTRACE command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Excutes a daemon command on a pre-activated agent.
   * SYNTAX: DAEMONSTART(agentID, command)
   * This function is used for launching daemons, since we typically
   * never need to kill these processes.
   * Apart from parsing errors, no errors about the status of the daemon, nor it's traces
   * may be obtained.
   *
   */
  private void processDAEMONSTART() {
    try {
      expectLeftParenthesis();
      QASHToken agentID = resolveExpression(getTokensUntil(COMMA));
      List<QASHToken> expression = getTokensUntil(RIGHT_PARENTHESIS);
      QASHToken command=null;
      command = resolveExpression(expression);

      setStatusText(QASHConstants.getTokenValue(DAEMONSTART),command.toString());

      AgentInstance agentInstance = (AgentInstance)qashProperties.getActiveAgent(new Integer(agentID.toString()));     

      if (agentInstance==null)
        throw new Exception("Unknown agentID");
      agentInstance.DAEMONSTART(strToArray(command.toString()));
    }
    catch (Exception e) {
      printError(DAEMONSTART,"Problem processing DAEMONSTART command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Returns 0 if the commandId contains the specified String in the stdout trace,
   * else returns 1. This command should only be called if the trace
   * was retrieved by a call to CMDGETTRACE(...)
   * SYNTAX: ENVTRACECONTAINS(processID, result, matchstring)
   * Warning : Do not call this method until after the output has been retrieved.
   */
  private void processENVTRACECONTAINS() {
    try {
      expectLeftParenthesis();
      QASHToken processIDName = resolveName(getTokensUntil(COMMA));
      QASHToken result        = resolveName(getTokensUntil(COMMA));
      QASHToken grepString    = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));
      QASHToken processID     = getToken(processIDName);

      setStatusText(QASHConstants.getTokenValue(ENVTRACECONTAINS),grepString.toString());      AgentInstance agentInstance = getAgentRunningProcess(processID.toString());
      if (agentInstance==null)
        throw new Exception("Unknown processID");
      String resultCode;
      resultCode = agentInstance.ENVTRACECONTAINS(processID.toString(),
          qashProperties.getProjectResultsDirectory()+File.separator+
          Common.getUniqueTestIdentifier(QASHFileName)+"_"+processIDName.toString(),
          grepString.toString());
      setProperty(result.toString(),resultCode);
    }
    catch (Exception e) {
      printError(ENVTRACECONTAINS,"Problem processing ENVTRACECONTAINS command ("+e.toString()+") :",currToken);
    }
  }


  /**
   * Returns 0 if the commandId contains the specified String in the stdout trace,
   * else returns 1. This command should only be called if the trace
   * was retrieved by a call to CMDGETTRACE(...)
   * SYNTAX: STDOUTCONTAINS(processID, result, matchstring)
   * Warning : Do not call this method until after the output has been retrieved.
   */
  private void processSTDOUTCONTAINS() {
    try {
      expectLeftParenthesis();
      QASHToken processIDName = resolveName(getTokensUntil(COMMA));
      QASHToken result        = resolveName(getTokensUntil(COMMA));
      QASHToken grepString    = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));
      QASHToken processID     = getToken(processIDName);

      setStatusText(QASHConstants.getTokenValue(STDOUTCONTAINS),grepString.toString());

      if (processIDName==null)
        throw new Exception("Invalid process ID");
      AgentInstance agentInstance = getAgentRunningProcess(processID.toString());
      if (agentInstance==null)
        throw new Exception("Unknown processID");
      String resultCode;
      resultCode = agentInstance.STDOUTCONTAINS(processID.toString(),
          qashProperties.getProjectResultsDirectory()+File.separator+
          Common.getUniqueTestIdentifier(QASHFileName)+"_"+processIDName.toString(),
          grepString.toString());
      setProperty(result.toString(),resultCode);
    }
    catch (Exception e) {
      printError(STDOUTCONTAINS,"Problem processing STDOUTCONTAINS command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Returns 0 if the commandId contains the specified String in the stdout trace,
   * else returns 1. This command should only be called if the trace
   * was retrieved by a call to CMDGETTRACE(...)
   * SYNTAX: STDERRCONTAINS(processID, result, matchstring)
   * Warning : Do not call this method until after the output has been retrieved.
   */
  private void processSTDERRCONTAINS() {
    try {
      expectLeftParenthesis();
      QASHToken processIDName = resolveName(getTokensUntil(COMMA));
      QASHToken result        = resolveName(getTokensUntil(COMMA));
      QASHToken grepString    = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));
      QASHToken processID     = getToken(processIDName);

      setStatusText(QASHConstants.getTokenValue(STDERRCONTAINS),grepString.toString());

      if (processIDName==null)
        throw new Exception("Invalid process ID");
      AgentInstance agentInstance = getAgentRunningProcess(processID.toString());
      if (agentInstance==null)
        throw new Exception("Unknown processID");
      String resultCode;
      resultCode = agentInstance.STDERRCONTAINS(processID.toString(),
          qashProperties.getProjectResultsDirectory()+File.separator+
          Common.getUniqueTestIdentifier(QASHFileName)+"_"+processIDName.toString(),
          grepString.toString());
      setProperty(result.toString(),resultCode);
    }
    catch (Exception e) {
      printError(STDERRCONTAINS,"Problem processing STDERRCONTAINS command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Returns the actual paths to the specified process trace files
   * relative to the agent. Only valid before the call to
   * CMDCLEAN(...), since that call will delete these files.
   * SYNTAX: GETTRACEPATHS(processID, env, stdout, stderr)
   * This may be called even if the output is not retrieved
   * from the agent.
   */
  private void processGETTRACEPATHS() {
    try {
      expectLeftParenthesis();
      QASHToken processIDName = resolveName(getTokensUntil(COMMA));
      QASHToken envFileName   = resolveName(getTokensUntil(COMMA));
      QASHToken stdoutFileName= resolveName(getTokensUntil(COMMA));
      QASHToken stderrFileName= resolveName(getTokensUntil(RIGHT_PARENTHESIS));
      QASHToken processID     = getToken(processIDName);

      setStatusText(QASHConstants.getTokenValue(GETTRACEPATHS),processIDName.toString());

      if (processIDName==null)
        throw new Exception("Invalid process ID");
      AgentInstance agentInstance = getAgentRunningProcess(processID.toString());
      if (agentInstance==null)
        throw new Exception("Unknown process ID");
      String results[] = agentInstance.GETTRACEPATHS(processID.toString());
      setProperty(envFileName.toString(),results[0]);
      setProperty(stdoutFileName.toString(),results[1]);
      setProperty(stderrFileName.toString(),results[2]);
    }
    catch (Exception e) {
      printError(GETTRACEPATHS,"Problem processing GETTRACEPATHS command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Cleans the stdout and stderr files created by the process.
   * SYNTAX: CMDCLEAN(processID)
   * Warning : Do not call this method until after the output has been retrieved.
   */
  private void processCMDCLEAN() {
    try {
      List<QASHToken> expression = readExpression(false, true);
      QASHToken processIDName = resolveName(expression);
      QASHToken processID = getToken(processIDName);
      AgentInstance agentInstance = getAgentRunningProcess(processID.toString());

      setStatusText(QASHConstants.getTokenValue(CMDCLEAN),processIDName.toString());

      if (agentInstance==null)
        throw new Exception("Unknown processID :"+processID.toString());
      agentInstance.CMDCLEAN(processID.toString());
      removeProperty(processIDName.toString());
    }
    catch (Exception e) {
      printError(CMDCLEAN,"Error processing CMDCLEAN command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * This method causes the shell to sleep for the specified number of seconds. All
   * background processes carry on unchanged.
   * SYNTAX: SLEEP(value), where value is the number of seconds to sleep.
   */
  private void processSLEEP() {
    try {
      List<QASHToken> expression = readExpression(false, true);
      QASHToken secondsStr = resolveExpression(expression);
      int seconds = secondsStr.intValue();

      if (!inEvaluationMode()) {
        while((seconds>0)&&(!isInterrupted())) {
          Utils.safeSleep(1);
          seconds--;
          setStatusText(QASHConstants.getTokenValue(SLEEP),Integer.toString(seconds));
        }
      }
    }
    catch (Exception e) {
      printError(SLEEP,"Invalid time value for SLEEP statement ("+e.toString()+") :",currToken);
    }   
  }

  /**
   * This method is for the test to communicate the outcome of the test to the test harness.
   * SYNTAX: REPORTSTATUS(value), where value is expected to be an integer value.
   * 0 indicates the test passed, and other value signifies it failed. If parse errors were encountered
   * the test status will always be reported as UNRESOLVED.
   */
  private void processREPORTSTATUS() {
    try {
      List<QASHToken> expression = readExpression(false, true);
      QASHToken status = resolveExpression(expression);

      setStatusText(QASHConstants.getTokenValue(REPORTSTATUS),status.toString());

      // unresolved always overrides any test result
      if (getStatus() != ProtocolConstants.UNRESOLVED) {
        if (inEvaluationMode()) {
          setStatus(ProtocolConstants.PASSED);
        }
        else {
          if ((new Integer(status.intValue()).intValue())==0) {
            setStatus(ProtocolConstants.PASSED);
          }
          else {
            if (getTestBugInfo().equals("")) {
              setStatus(ProtocolConstants.FAILED);
            }
            else {
              setStatus(ProtocolConstants.PENDING);
            }
          }
        }
      }
      setStatusReported(true);
    }
    catch (Exception e) {
      printError(REPORTSTATUS,"Invalid status reported ("+e.toString()+") :",currToken);
    }
    finally {
      if (qashProperties.isAutoClean())
        autoClean();
    }
  } 

  /**
   * This method is used to generate a positive random integer in the QASH scripts for
   * use in defining tmp directory offset etc.
   * SYNTAX: RANDOM(id), where id will contain the random integer value after this call is made.
   */
  private void processRANDOM() {
    setStatusText(currToken.toString());
    try {
      List<QASHToken> expression = readExpression(false, false);
      QASHToken randomVar = resolveName(expression);

      setProperty(randomVar,Integer.toString(Math.abs(new Random().nextInt())));   
    }
    catch (Exception e) {
      printError(RANDOM,"Invalid syntax in RANDOM command ("+e.toString()+") :",currToken);
    }   
  } 

  /**
   * Used for debugging - prints out the current contents of the qash properties.
   */
  @SuppressWarnings("unchecked")
  private void processPRINTENV() {
    setStatusText(currToken.toString());
    try {
      printStream.println("Defined variables:");
      String key;
      for (Enumeration<String> e = (Enumeration<String>) properties.propertyNames() ; e.hasMoreElements() ;) {
        key = (String)e.nextElement();
        printStream.println(key+"="+properties.getProperty(key));
      }
      //printStream.println(properties);
      printStream.println("Defined static variables:");
      printStream.println(staticVariables);
    }
    catch (Exception e) {
      printError(PRINTENV,"Invalid PRINTENV reported ("+e.toString()+") :",currToken);
    }
  }

  /**
   * This method will clear all agents referenced by this parse session in case
   * this user forgets to clear something, or if an error occurs in the parse
   * process.
   */
  private void autoClean() {
    printDebug("Processing AUTOCLEAN of "+qashProperties.getActiveAgentCount()+" agent(s)");
    // now call kill for all active agents
    if (!inEvaluationMode()) {
      AgentInstance agent=null;
      for (Enumeration<AgentInstance> agentList = qashProperties.getActiveAgents() ; agentList.hasMoreElements() ;) {
        try {
          agent = (AgentInstance)agentList.nextElement();
          agent.KILLSTARTEDPROCESSES();
        }
        catch (Exception e) {
          printError(3,"Error during AUTOCLEAN of one or more agent processes - may need restart of agents!"+" :"+e.toString());
        }
      }
    }
    // clear our Hashtable now
    qashProperties.clearActiveAgents();
  }

  /**
   * After calling this keyword, when calling REPORTSTATUS, any related zips will be cleaned,
   * commands run will be stopped, and their traces cleaned.
   */
  private void processAUTOCLEAN_ON() {
    try {
      qashProperties.setAutoClean(true);
      printDebug("AUTOCLEAN enabled");
    }
    catch (Exception e) {
      printError(AUTOCLEAN_ON,"Invalid AUTOCLEAN_ON reported ("+e.toString()+") :",currToken);
    }
  }

  /**
   * After calling this keyword, when calling REPORTSTATUS, any related zips will not be cleaned,
   * commands run will not be stopped, and their traces will not cleaned.
   * By default, this behaviour is enabled.
   */
  private void processAUTOCLEAN_OFF() {
    try {
      qashProperties.setAutoClean(false);
      printDebug("AUTOCLEAN disabled");
    }
    catch (Exception e) {
      printError(AUTOCLEAN_OFF,"Invalid AUTOCLEAN_OFF reported ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Gets an arbitrary file from the agent.
   * SYNTAX: GETFILE(agentid, src, dest), where src is the file path on the agent, and dest is the path on
   * the harness to place the file in.
   */
  private void processGETFILE() {
    try {
      expectLeftParenthesis();
      QASHToken agentID = resolveExpression(getTokensUntil(COMMA));
      QASHToken src = resolveExpression(getTokensUntil(COMMA));
      QASHToken dest = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));


      // check that this agentID is defined
      if (agentID==null) {
        throw new Exception("agentID Identifier not defined");
      }

      AgentInstance agentInstance = (AgentInstance)qashProperties.getActiveAgent(new Integer(agentID.toString()));
      if (agentInstance==null)
        throw new Exception("Unknown agentID");
      else
        agentInstance.GETFILE(src.toString(),dest.toString());
    }
    catch (Exception e) {
      printError(GETFILE,"Problem processing GETFILE command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Sends an arbitrary file to the agent.
   * SYNTAX: SENDFILE(agentid, src, dest), where src is the file path on the harness, and dest is the path on
   * the agent to place the file in.
   */
  private void processSENDFILE() {
    try {
      expectLeftParenthesis();
      QASHToken agentID = resolveExpression(getTokensUntil(COMMA));
      QASHToken src = resolveExpression(getTokensUntil(COMMA));
      QASHToken dest = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));

      // check that this agentID is defined
      if (agentID==null) {
        throw new Exception("agentID Identifier not defined");
      }

      AgentInstance agentInstance = (AgentInstance)qashProperties.getActiveAgent(new Integer(agentID.toString()));
      if (agentInstance==null)
        throw new Exception("Unknown agentID");
      else
        agentInstance.SENDFILE(src.toString(),dest.toString());
    }
    catch (Exception e) {
      printError(SENDFILE,"Problem processing SENDFILE command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Checks whether an arbitrary file exists on the agent or not.
   * SYNTAX: CHECKFILE(agentid, result, filename), where result is zero if the file exists, non-zero if it doesn't exist,
   * and filename is the name of the file to check relative to the agent.
   */
  private void processCHECKFILE() {
    try {

      expectLeftParenthesis();
      QASHToken agentID = resolveExpression(getTokensUntil(COMMA));
      QASHToken result = resolveName(getTokensUntil(COMMA));
      QASHToken filename = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));

      setStatusText(QASHConstants.getTokenValue(CHECKFILE),filename.toString());

      // check that this agentID is defined
      if (agentID==null) {
        throw new Exception("agentID Identifier not defined");
      }

      AgentInstance agentInstance = (AgentInstance)qashProperties.getActiveAgent(new Integer(agentID.toString()));
      if (agentInstance==null)
        throw new Exception("Unknown agentID");
      else
        setProperty(result,agentInstance.CHECKFILE(filename.toString()));
    }
    catch (Exception e) {
      printError(CHECKFILE,"Problem processing CHECKFILE command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Checks whether an arbitrary file exists on the harness side or not.
   * SYNTAX: CHECKFILE_LOCAL(result, filename), where result is zero if the file exists, non-zero if it doesn't exist,
   * and filename is the name of the file to check relative to the harness.
   * If the parser is running in evaluation mode, the result will always be zero.
   */
  private void processCHECKFILE_LOCAL() {
    try {
      expectLeftParenthesis();
      QASHToken result = resolveName(getTokensUntil(COMMA));
      QASHToken filename = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));

      setStatusText(QASHConstants.getTokenValue(CHECKFILE_LOCAL),filename.toString());

      if (!inEvaluationMode()) {       
        setProperty(result.toString(),((new File(filename.toString())).exists() "0" : "1" ));
      }
      else {
        setProperty(result.toString(),"0");
      }
    }
    catch (Exception e) {
      printError(CHECKFILE,"Problem processing CHECKFILE_LOCAL command ("+e.toString()+") :",currToken);
    }
  } 

  /**
   * Checks whether an arbitrary agent can be reached or not.
   * SYNTAX: CHECKAGENT(result,agentname,agentport), where result is zero if the agent exists, non-zero if it doesn't exist.
   * In evaluation mode it will always return 0.
   */
  @SuppressWarnings("unchecked")
  private void processCHECKAGENT() {
    try {
      expectLeftParenthesis();
      QASHToken result = resolveName(getTokensUntil(COMMA));
      QASHToken agentName = resolveExpression(getTokensUntil(COMMA));
      QASHToken agentPort = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));

      setStatusText(QASHConstants.getTokenValue(CHECKAGENT),agentName.toString());

      Socket socket = null;
      DataInputStream  inStream = null;
      DataOutputStream outStream = null;
      String resultCode ="0";
      if (!inEvaluationMode()) {
        try {
          socket = new Socket(agentName.toString(),(new Integer(agentPort.toString())).intValue());
          socket.setSoTimeout(3000);
          inStream  = new DataInputStream(socket.getInputStream());
          outStream = new DataOutputStream(socket.getOutputStream());
          outStream.writeInt(ProtocolConstants.CHECKAGENT);
          if (inStream.readInt()!=ProtocolConstants.RESPONSE_PROCESSING)
            throw new Exception("Error response from agent");
          // read the number of properties being sent
          int propertyCount = inStream.readInt();
          String key, value;
          for (int i = 0; i < propertyCount; i++) {
            key = agentName+"."+inStream.readUTF();
            value = inStream.readUTF();
            setProperty(key,value);
          }
          if (inStream.readInt()!=ProtocolConstants.RESPONSE_FINISHED_OK)
            throw new Exception("Error response from agent");                   
        }
        catch (ConnectException e) {
          resultCode = "1"
        }
        finally {
          if (inStream != null) inStream.close();
          if (outStream != null) outStream.close();
          if (socket != null) socket.close();
        }
      }
      else {
        // in eval mode, we set agent properties to use our properties
        Properties propertiesT = System.getProperties();
        String key, value;
        for (Enumeration<String> e = (Enumeration<String>) propertiesT.propertyNames() ; e.hasMoreElements() ;) {
          key = e.nextElement().toString();
          value = propertiesT.getProperty(key);
          key = agentName+"."+key;
          setProperty(key,value);
        }
      }
      setProperty(result,resultCode);
    }
    catch (Exception e) {
      printError(CHECKAGENT,"Problem processing CHECKAGENT command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Checks whether an arbitrary agent can be reached or not.
   * SYNTAX: KILLAGENT(result,agentname,agentport), where result is zero if the agent exists, non-zero if it doesn't exist.
   * In evaluation mode it will always return 0.
   */
  private void processKILLAGENT() {
    try {
      expectLeftParenthesis();
      QASHToken result = resolveName(getTokensUntil(COMMA));
      QASHToken agentName = resolveExpression(getTokensUntil(COMMA));
      QASHToken agentPort = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));

      setStatusText(QASHConstants.getTokenValue(KILLAGENT),agentName.toString());

      Socket socket = null;
      DataInputStream  inStream = null;
      DataOutputStream outStream = null;
      String resultCode ="0";
      if (!inEvaluationMode()) {
        try {
          socket = new Socket(agentName.toString(),(new Integer(agentPort.toString())).intValue());
          socket.setSoTimeout(3000);
          inStream  = new DataInputStream(socket.getInputStream());
          outStream = new DataOutputStream(socket.getOutputStream());
          outStream.writeInt(ProtocolConstants.KILLAGENT);
          if (inStream.readInt()!=ProtocolConstants.RESPONSE_PROCESSING)
            throw new Exception("Error response from agent");
          if (inStream.readInt()!=ProtocolConstants.RESPONSE_FINISHED_OK)
            throw new Exception("Error response from agent");                   
        }
        catch (ConnectException e) {
          resultCode = "1"
        }
        finally {
          if (inStream != null) inStream.close();
          if (outStream != null) outStream.close();
          if (socket != null) socket.close();
        }
      }
      setProperty(result,resultCode);
    }
    catch (Exception e) {
      printError(CHECKAGENT,"Problem processing KILLAGENT command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Defines a function for later use by the qash scripts.
   * SYNTAX: FUNCTION(func_name), where func_name is the name to be used
   * later on when calling the function via CALLFUNCTION(func_name) command.
   */
  private void processFUNCTION() {
    try {
      expectLeftParenthesis();
      QASHToken functionName = resolveName(getTokensUntil(COMMA));
      // now read parameters, if any
      List<QASHToken> parameters = new ArrayList<QASHToken>();
      QASHToken token;

      do {
        token = getNextToken();
        if ((token.ttype != COMMA)&&
            (token.ttype != RIGHT_PARENTHESIS)) {
          parameters.add(token);
        }
      } while ((token.ttype != RIGHT_PARENTHESIS)&&
          (token.ttype != QASHToken.TT_EOF));

      parameters.add(0,new QASHToken(Integer.toString(parameters.size()),QASHToken.ARG_COUNT,0));

      // process the function body
      List<QASHToken> functionBody = new ArrayList<QASHToken>();
      int nestedCount = 1;
      if (qashProperties.getFunction(functionName.toString())!=null) {
        throw new Exception("Function "+functionName.toString()+" is already defined");
      }
      do {
        token = getNextToken();
        switch (getTokenID(token.toString())) {
        case FUNCTION :
          nestedCount++;
          break;
        case ENDFUNCTION :
          nestedCount--;
          break;
        default : functionBody.add(token);
        }
      } while ((getTokenID(token.toString())!=ENDFUNCTION)&&
          (token.ttype != QASHToken.TT_EOF)&&
          (nestedCount>0));
      if (nestedCount>0)
        throw new Exception("Missing ENDFUNCTION statement");
      // insert our parameters
      for (int i = parameters.size()-1; i >= 0; i--)
        functionBody.add(0,parameters.get(i));
      // now add this mini stack to out static hashtable
      qashProperties.addFunction(functionName.toString(),functionBody);
    }
    catch (Exception e) {
      printError(FUNCTION,"Problem processing FUNCTION command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Defines a function for later use by the qash scripts.
   * SYNTAX: CALLFUNCTION(func_name), where func_name is the name used
   * previously when being defined by the FUNCTION(func_name) command.
   *
   */
  private void processCALLFUNCTION() {
    try {
      expectLeftParenthesis();
      QASHToken functionName = resolveName(getTokensUntil(COMMA));
      List<QASHToken> functionBodyCopy = (ArrayList<QASHToken>) qashProperties.getFunction(functionName.toString());
      if (functionBodyCopy==null) {
        throw new Exception("Function "+functionName.toString()+" is not defined");
      }
      else {
        functionBodyCopy = Utils.clone(functionBodyCopy);
      }

      // pop off the args count
      int argCount = Integer.parseInt(functionBodyCopy.remove(0).toString());
      // now read parameters passed to call this function with
      List<QASHToken> expr = new ArrayList<QASHToken>();
      List<QASHToken> args = new ArrayList<QASHToken>();
      QASHToken token;
      do {
        token = getNextToken();
        switch (token.ttype) {
        case RIGHT_PARENTHESIS :
        case COMMA :
          args.add(functionBodyCopy.remove(0));
          args.add(new QASHToken(SET_OPERATOR,SET_OPERATOR,0));
          args.add(resolveExpression(expr));
          args.add(new QASHToken(SEMICOLON,SEMICOLON,0));
          expr.clear();
          argCount--;
          break;
        default :
          expr.add(token);
        }
      } while ((token.ttype!=RIGHT_PARENTHESIS)&&
          (token.ttype != QASHToken.TT_EOF));
      if (argCount!=0)
        throw new Exception("Incorrect argument count for function "+functionName);
      pushOntoStack(functionBodyCopy);
      pushOntoStack(args);
    }
    catch (Exception e) {
      printError(CALLFUNCTION,"Problem processing CALLFUNCTION command ("+e.getMessage()+") :",currToken);
    }
  }     

  /**
   * Deletes an arbitrary file on the harness machine.
   * SYNTAX: DELFILE_LOCAL(filename), where src is the file path relative to the harness.
   * Use with caution - if filename is a directory, it and all it's subdirectories will be deleted!
   * If the parser is running in evaluation mode, this command will have no action.
   */
  private void processDELFILE_LOCAL() {
    try {
      List<QASHToken> expression = readExpression(false, true);
      QASHToken filename = resolveExpression(expression);

      setStatusText(QASHConstants.getTokenValue(DELFILE_LOCAL),filename.toString());

      if (!inEvaluationMode()) {
        if (!(new File(filename.toString())).exists())
          throw new Exception("Cannot delete - file does not exist ("+filename.toString()+")");
        else
          Utils.delete(filename.toString());
      }
    }
    catch (Exception e) {
      printError(DELFILE_LOCAL,"Problem processing DELFILE_LOCAL command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Deletes an arbitrary file on the harness machine.
   * SYNTAX: DELFILE(agentid, filename), where agentid refers to the agent machine where the file resides,
   * and src is the file path for deletion relative to the agent.
   * Use with caution - if filename is a directory, it and all it's subdirectories will be deleted!
   */
  private void processDELFILE() {
    try {
      expectLeftParenthesis();
      QASHToken agentID = resolveExpression(getTokensUntil(COMMA));
      QASHToken filename = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));

      setStatusText(QASHConstants.getTokenValue(DELFILE),filename.toString());

      AgentInstance agentInstance = (AgentInstance)qashProperties.getActiveAgent(new Integer(agentID.toString()));
      if (agentInstance==null)
        throw new Exception("Unknown agentID");
      else
        agentInstance.DELFILE(filename.toString());
    }
    catch (Exception e) {
      printError(DELFILE,"Problem processing DELFILE command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Deletes an arbitrary file on the harness machine.
   * SYNTAX: MKDIR(agentid, dirname), where agentid refers to the agent machine where the file resides,
   * and dir is the file path for the directory to be created..
   * If the parent directories specified in the dirname path do not exist, they will be created.
   */
  private void processMKDIR() {
    try {
      expectLeftParenthesis();
      QASHToken agentID = resolveExpression(getTokensUntil(COMMA));
      QASHToken filename = resolveExpression(resolveExpression(getTokensUntil(RIGHT_PARENTHESIS)));

      setStatusText(QASHConstants.getTokenValue(MKDIR),filename.toString());

      AgentInstance agentInstance = (AgentInstance)qashProperties.getActiveAgent(new Integer(agentID.toString()));
      if (agentInstance==null)
        throw new Exception("Unknown agentID");
      else
        agentInstance.MKDIR(filename.toString());
    }
    catch (Exception e) {
      printError(MKDIR,"Problem processing MKDIR command ("+e.toString()+") :",currToken);
    }
  } 

  /**
   * Creates a directory on the harness machine.
   * SYNTAX: MKDIR_LOCAL(filename), where src is the dir path relative to the harness.
   * If the parser is running in evaluation mode, this command will have no action.
   */
  private void processMKDIR_LOCAL() {
    try {
      expectLeftParenthesis();
      QASHToken filename = null;
      filename = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));

      setStatusText(QASHConstants.getTokenValue(MKDIR_LOCAL),filename.toString());

      if (!inEvaluationMode()) {
        Utils.touchDir(fileName.toString());
      }

    }
    catch (Exception e) {
      printError(MKDIR_LOCAL,"Problem processing MKDIR_LOCAL command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Set a property which will be accesible to other scripts for the lieftime of the JVM, or until it is deleted by a call to
   * DELSTATIC .
   * SYNTAX : SETSTATIC(KEY, VALUE)
   */
  private void processSETSTATIC() {
    try {
      expectLeftParenthesis();
      QASHToken key = resolveName(getTokensUntil(COMMA));
      QASHToken value = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));

      staticVariables.setProperty(key.toString(),value.toString());
    }
    catch (Exception e) {
      printError(SETPROP,"Problem processing SETSTATIC command ("+e.toString()+") :",currToken);
    }
  } 

  /**
   * Removes a previously set static variable.
   * SYNTAX : DELSTATIC(KEY)
   */
  private void processDELSTATIC() {
    try {
      expectLeftParenthesis();
      QASHToken key = resolveName(getTokensUntil(RIGHT_PARENTHESIS));
      staticVariables.remove(key.toString());
    }
    catch (Exception e) {
      printError(SETPROP,"Problem processing DELSTATIC command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Retrieves a previously set static variable.
   * If the variable specified by KEY does not exist, RESULT willbe undefined and attempts to
   * use it will generate an error.
   * SYNTAX : GETSTATIC(RESULT, KEY)
   */
  private void processGETSTATIC() {
    try {
      expectLeftParenthesis();
      QASHToken result = resolveName(getTokensUntil(COMMA));
      QASHToken key = resolveExpression(getTokensUntil(RIGHT_PARENTHESIS));
      String value = (String)staticVariables.getProperty(key.toString(),"").toString();
      if (value!="") {
        setProperty(result.toString(), value);
      }
      else {
        removeProperty(result.toString());
        printWarning(45,"Static variable not defined :"+key);
      }
    }
    catch (Exception e) {
      printError(SETPROP,"Problem processing GETSTATIC command ("+e.toString()+") :",currToken);
    }
  }

  /**
   * Looks through currently running agents for an identifier matching processID.
   * Returns the matching AgentInstance, or null if it wasn't found.
   */
  private AgentInstance getAgentRunningProcess(String processID) {
    try {
      AgentInstance agentInstance;
      for (Enumeration<AgentInstance> e = qashProperties.getActiveAgents() ; e.hasMoreElements() ;) {
        agentInstance = (AgentInstance)e.nextElement();
        if (agentInstance.isHandlingProcess(processID)) {
          return agentInstance;
        }
      }
    }
    catch(Exception e) {
      printError(23,"No agent was running this process!");
    }
    return null;
  }

  /**
   * Looks through currently running agents for an identifier matching zipID.
   * Returns the matching AgentInstance, or null if it wasn't found.
   */
  private AgentInstance getAgentForZip(String zipID) {
    AgentInstance agentInstance;
    for (Enumeration<AgentInstance> e = qashProperties.getActiveAgents() ; e.hasMoreElements() ;) {
      agentInstance = (AgentInstance)e.nextElement();
      if (agentInstance.isHandlingZip(zipID)) {
        return agentInstance;
      }
    }
    return null;
  }

  private static List<QASHToken> removeRange(List<QASHToken> array, int start, int end) {
    int count = end - start;
    for (int i = 0; i < count ; i++) {
      array.remove(start);
    }
    return array;
  }

  private List<QASHToken> copyRange(List<QASHToken> array, int start, int end) {
    List<QASHToken> result = new ArrayList<QASHToken>(end-start);
    for (int i = 0; i < (end-start); i++)
      result.add(array.get(i+start));
    return result;
  }

  /**
   * This method takes a list of tokens, and processes all the concat & parenthesis stuff but
   * does not actually dereference any variables in it unless they
   * exists along with other concat operators and constants.
   * eg. TOTO will stay TOTO, but TOTO+"xxx" will return "valxxx", assuming
   * TOTO was set to "val".
   */

  private QASHToken resolveName(List<QASHToken> expression) throws Exception {
    if (indexOf(expression,CONCAT_OPERATOR)>0)
      return resolveNameInternal(expression,true);
    else
      return resolveNameInternal(expression,false);
  }

  private QASHToken resolveNameInternal(List<QASHToken> expression, boolean referenceLast) throws Exception {
    QASHToken token = (QASHToken)((QASHToken)expression.get(0)).clone();
    // maybe we are finished already
    if (expression.size()==1) {
      if ((referenceLast)&
          (token.ttype==QASHToken.TT_WORD))
        return getToken(token);
      else
        return token;
    }
    switch (token.ttype) {
    case LEFT_PARENTHESIS :
      List<QASHToken> inside = Utils.clone(expression);
      int rindex = getClosingParenIndex(inside,0);
      inside = removeRange(inside,rindex,inside.size());
      inside.remove(0);
      expression=removeRange(expression,0,rindex+1);
      if (inside.size()>0)
        expression.add(0,resolveNameInternal(inside,referenceLast));
      return resolveNameInternal(expression,referenceLast);
    case CONCAT_OPERATOR :
      expression.remove(0);
      return resolveNameInternal(expression,referenceLast);
    case QASHToken.TT_WORD :
      expression.remove(0);
      QASHToken newtoken = getToken(token);
      if (newtoken==null) {
        throw new Exception("Undefined symbol "+token);
      }
      else {
        return newtoken.append(resolveNameInternal(expression,referenceLast));
      }
    default :
      expression.remove(0);
      if (expression.size()>0)
        token.append(resolveNameInternal(expression,referenceLast));
      return token;
    }
  }

  /**
   * This method replaces all the variable token by the actual values, or returns an error if
   * the value is not defined.
   */
  private QASHToken resolveExpression(QASHToken token) throws Exception {
    List<QASHToken> arrayList = new ArrayList<QASHToken>(1);
    arrayList.add(token);
    return resolveExpression(arrayList);
  }

  /**
   * This method replaces all variables by actual values, or returns an error if
   * any of the values are not defined. It process concat operators as well.
   */
  private QASHToken resolveExpression(List<QASHToken> expression) throws Exception {
    return resolveExpressionInternal(expression,true);
  }

  private QASHToken resolveExpressionInternal(List<QASHToken> expression, boolean strict) throws Exception {
    QASHToken token;
    // maybe we are finished already
    if (expression.size()==0) {
      return null;
    }
    else {
      // the clone is important here, else we change
      // the referenced value all the way through
      token = (QASHToken)((QASHToken)expression.get(0)).clone();
    }
    switch (token.ttype) {
    case LEFT_PARENTHESIS :
      List<QASHToken> inside = Utils.clone(expression);
      int rindex = getClosingParenIndex(inside,0);
      inside = removeRange(inside,rindex,inside.size());
      inside.remove(0);
      expression=removeRange(expression,0,rindex+1);
      if (inside.size()>0)
        expression.add(0,resolveExpressionInternal(inside,strict));
      // now since we enclosed it in brackets, try and evaluate it
      // further as a VARIABLE name instead of a value
      QASHToken tmp;
      if ((tmp = getToken((QASHToken)expression.get(0)))!=null) {
        expression.set(0,tmp);
      }
      return resolveExpressionInternal(expression,strict);
    case CONCAT_OPERATOR :
      expression.remove(0);
      return resolveExpressionInternal(expression,strict);
    case QASHToken.TT_STRING :
      expression.remove(0);
      token.append(resolveExpressionInternal(expression,strict));
      return token;
    case  QASHToken.TT_NUMBER :
      expression.remove(0);
      QASHToken theRest = resolveExpressionInternal(expression,strict);
      if (theRest!=null) {
        try {
          int v1 = Integer.parseInt(token.toString());
          int v2 = Integer.parseInt(theRest.toString());
          return new QASHToken(Integer.toString(v1+v2),QASHToken.TT_NUMBER,theRest.line);
        }
        catch (NumberFormatException e) {
          token.append(theRest);
          return token;
        }
      }
      else return token;
    case  QASHToken.TT_WORD :
      expression.remove(0);
      QASHToken value = getToken(token);
      if (value==null) {
        if (strict) {
          throw new Exception("Reference to undefined variable "+token.toString());
        }
      }
      else {
        token = value.append(resolveExpressionInternal(expression,strict));
      }
      return token;
    default :
      expression.remove(0);
      token.append(resolveExpressionInternal(expression,strict));
      return token;
    }
  }

  /**
   * This method returns the index of the c in the expression list,
   * or -1 if it is not found.
   */
  private int indexOf(List<QASHToken> expression, char c) {
    for (int i = 0 ; i < expression.size(); i++)
      if (((QASHToken)expression.get(i)).ttype==c)
        return i;
    return -1;
  }   

  private QASHToken getToken(String key) {
    return getToken(key,null);
  }

  private QASHToken getToken(QASHToken key) {
    return getToken(key.toString());
  }

  private QASHToken getToken(String key, String defaultValue) {
    String value = getProperty(key,defaultValue);
    if (value!=null) {
      QASHToken token = new QASHToken(value,currToken.line);
      return token;
    }
    // backward compat start - remove for 2.6.5
    return backwardsCompat(key,defaultValue);
    //    return null;
    // backward compat end - remove for 2.6.5
  }

  // backward compat start - remove for 2.6.5
  private QASHToken backwardsCompat(String key, String defaultValue) {
    if (key.indexOf("TESTPATH")>=0) {
      printWarning(8765,"Please use qat.project.path instead of TESTPATH");
      return new QASHToken(getProperty("qat.project.path",defaultValue),currToken.line);
    }
    if (key.indexOf("AGENT_COUNT")>=0) {
      printWarning(8765,"Please use qat.agent.count instead of AGENT_COUNT");
      return new QASHToken(getProperty("qat.agent.count",defaultValue),currToken.line);
    }
    if (key.indexOf("TEST_NAME")>=0) {
      printWarning(8765,"Please use qat.test.name instead of TEST_NAME");
      return new QASHToken(getProperty("TEST_NAME",defaultValue),currToken.line);
    }
    if (key.indexOf("TEST_AUTHOR")>=0) {
      printWarning(8765,"Please use qat.test.author instead of TEST_AUTHOR");
      return new QASHToken(getProperty("TEST_AUTHOR",defaultValue),currToken.line);
    }
    if (key.indexOf("TEST_DESCRIPTION")>=0) {
      printWarning(8765,"Please use qat.test.description instead of TEST_DESCRIPTION");
      return new QASHToken(getProperty("TEST_DESCRIPTION",defaultValue),currToken.line);
    }
    if (key.indexOf("BUG_INFO")>=0) {
      printWarning(8765,"Please use qat.test.buginfo instead of BUG_INFO");
      return new QASHToken(getProperty("BUG_INFO",defaultValue),currToken.line);
    }
    if (key.indexOf("KEYWORDS")>=0) {
      printWarning(8765,"Please use qat.test.keywords instead of KEYWORDS");
      return new QASHToken(getProperty("KEYWORDS",defaultValue),currToken.line);
    }
    if (key.startsWith("AGENT")) {
      String agent = key.substring(0,6).toLowerCase();
      if (key.indexOf("_NAME")>0) {
        printWarning(8765,"Please use agent1.name instead of AGENT1_NAME");
        return new QASHToken(getProperty(agent+".name",defaultValue),currToken.line);
      }
      if (key.indexOf("_PORT")>0) {
        printWarning(8765,"Please use agent1.port instead of AGENT1_PORT");
        return new QASHToken(getProperty(agent+".port",defaultValue),currToken.line);
      }
      if (key.indexOf("_ARCH")>0) {
        printWarning(8765,"Please use agent1.os.architecture instead of AGENT1_ARCH");
        return new QASHToken(getProperty(agent+".os.architecture",defaultValue),currToken.line);
      }
      if (key.indexOf("_OSNAME")>0) {
        printWarning(8765,"Please use agent1.os.name instead of AGENT1_OSNAME");
        return new QASHToken(getProperty(agent+".os.name",defaultValue),currToken.line);
      }
    }
    return null;
  }
  // backward compat end - remove for 2.6.5

  public String getProperty(String key) {
    return getProperty(key,"");
  }

  public String getProperty(String key, String defaultValue) {
    return properties.getProperty(key,defaultValue);
  }

  public final Properties getProperties() {
    return properties;
  }

  public void setProperty(QASHToken key, QASHToken value) {
    setProperty(key.toString(),value.toString());
  }

  public void setProperty(QASHToken key, String value) {
    setProperty(key.toString(),value);
  }

  public void setProperty(String key, String value) {
    properties.setProperty(key,value);
  }

  public void removeProperty(QASHToken key) {
    removeProperty(key.toString());
  }

  public void removeProperty(String key) {
    properties.remove(key);
  }

  public void printDebug(String msg) {
    printStream.printBold("[ ");
    printStream.print(HtmlPrintStream.PURPLE,msg);
    printStream.printBoldln(" ]");   
  }

  private void printWarning(int code, String msg) {
    log.warning(msg);
    printStream.printBold("[ ");
    printStream.print(HtmlPrintStream.BLUE,"Warning ");
    printStream.print("("+code+") :");
    printStream.print(msg);
    printStream.print(" in file ");
    printStream.print(HtmlPrintStream.BLUE,fileName);
    printStream.printBoldln(" ]")
  }

  private void printError(int code, String msg) {
    // make sure we only print the first error - others are
    // usually spurious
    if (getStatus()!=ProtocolConstants.UNRESOLVED) {
      printStream.printBold("[ ");
      printStream.print(HtmlPrintStream.RED,"Error ");
      printStream.print("(");
      printStream.print(code);
      printStream.print(") :");
      printStream.print(msg);
      printStream.print(" in file ");
      printStream.print(HtmlPrintStream.RED,fileName);
      printStream.printBoldln(" ]");
      setStatus(ProtocolConstants.UNRESOLVED);
    }

  }

  private void printError(int code, String msg, QASHToken token) {
    printError(code,msg,token.line)
  }

  private void printError(int code, String msg, int line) {
    printError(code,msg+" on line "+line)
  }

  private void expectLeftParenthesis() throws Exception {
    getNextToken();
    if (currToken.ttype!=LEFT_PARENTHESIS)
      throw new Exception("Expected ( instead of "+currToken.toString());
  }

  /*------- these are the boolean expression handling routines ----------*/

  /**
   * This method replaces all CONCAT_OPERATOR ops found in the expression
   * with the concatenated result.
   */
  private List<QASHToken> preProcessBooleanExpression(List<QASHToken> expression) throws Exception {
    for (int i = 1; i < expression.size()-1; i++) {
      QASHToken token = (QASHToken)expression.get(i);
      if (token.ttype==CONCAT_OPERATOR) {
        // append the previous token and the next token. This
        // is to handle simple appends in a boolean statement
        // such as (I+"2">4)  and ((I+"2")>4)
        List<QASHToken> miniExpression = new ArrayList<QASHToken>(5);
        miniExpression.add(new QASHToken(LEFT_PARENTHESIS,LEFT_PARENTHESIS,token.line));
        miniExpression.add((QASHToken)expression.get(i-1));
        miniExpression.add(token);
        miniExpression.add(((QASHToken)expression.get(i+1)));
        miniExpression.add(new QASHToken(RIGHT_PARENTHESIS,RIGHT_PARENTHESIS,token.line));
        expression.set(i-1,resolveExpression(miniExpression));
        expression.remove(i);
        expression.remove(i);
      }
    }
    return expression;
  }

  private boolean evaluateBooleanExpression(List<QASHToken> expression) throws Exception {
    return evaluateBooleanExpression(createExpressionTree(preProcessBooleanExpression(expression)));
  }

  private boolean evaluateBooleanExpression(QASHTree root) throws Exception {
    if ((root.left.isLeaf())&&(root.right.isLeaf())) {
      return evaluateBooleanNode(root.left.value,root.value,root.right.value);
    }
    else {
      switch (root.value.ttype) {
      case LOGICAL_AND         : return (evaluateBooleanExpression(root.left) & evaluateBooleanExpression(root.right));
      case LOGICAL_OR          : return (evaluateBooleanExpression(root.left) | evaluateBooleanExpression(root.right));
      case EQUALITY_OPERATOR   : return (evaluateBooleanExpression(root.left) == evaluateBooleanExpression(root.right));
      case NOT_EQUAL_OPERATOR  : return (evaluateBooleanExpression(root.left) != evaluateBooleanExpression(root.right));
      default : throw new Exception("Unknown operator encountered :"+root.value);
      }
    }
  }

  private boolean evaluateBooleanNode(QASHToken left, QASHToken operator, QASHToken right) throws Exception {
    QASHToken leftSideValue;
    if (left.ttype==QASHToken.TT_WORD)
      leftSideValue = getToken(left);
    else
      leftSideValue = left; // already evaluated
    QASHToken rightSideValue;
    if (right.ttype==QASHToken.TT_WORD)
      rightSideValue = getToken(right);
    else
      rightSideValue = right;  // already evaluated

    // now first try a numerical comparison
    try {
      switch (operator.ttype) {
      case GREATER_THAN :
        return (leftSideValue.intValue() > rightSideValue.intValue());
      case LESS_THAN :
        return (leftSideValue.intValue() < rightSideValue.intValue());
      case EQUALITY_OPERATOR :
        return (leftSideValue.equals(rightSideValue));
      case NOT_EQUAL_OPERATOR :
        return (!(leftSideValue.equals(rightSideValue)));
      case LESS_THAN_EQ :
        return (leftSideValue.intValue() <= rightSideValue.intValue());
      case GREATER_THAN_EQ :
        return (leftSideValue.intValue() >= rightSideValue.intValue());
      default :
        throw new Exception("Unknown operator type :"+operator);
      }
    }
    catch (NumberFormatException e) {
      // make the comparison as "String <operator> String"
      switch (operator.ttype) {
      case GREATER_THAN :
        return (leftSideValue.compareTo(rightSideValue)>0);
      case LESS_THAN :
        return (leftSideValue.compareTo(rightSideValue)<0);
      case EQUALITY_OPERATOR :
        return (leftSideValue.equals(rightSideValue));
      case NOT_EQUAL_OPERATOR :
        return (!leftSideValue.equals(rightSideValue));
      case LESS_THAN_EQ :
        return (leftSideValue.compareTo(rightSideValue)<=0);
      case GREATER_THAN_EQ :
        return (leftSideValue.compareTo(rightSideValue)>=0);
      default :
        throw new Exception("Unknown operator type :"+operator);
      }
    }
  }

  /**
   * This method splits an expression into  leftside, operator and rightside.
   */
  private static QASHTree createExpressionTree(List<QASHToken> expression) throws Exception {
    QASHTree root = new QASHTree();
    QASHTree currNode = root;
    for (int i = 0; i < expression.size(); i++) {
      QASHToken token = (QASHToken)expression.get(i);

      switch (token.ttype) {
      case LEFT_PARENTHESIS :
        currNode.left = new QASHTree(currNode);
        currNode = currNode.left;
        break;
      case RIGHT_PARENTHESIS : currNode = currNode.parent;
      break;
      case LESS_THAN :
      case GREATER_THAN :
      case LESS_THAN_EQ :
      case GREATER_THAN_EQ :
      case LOGICAL_AND :
      case LOGICAL_OR :
      case NOT_EQUAL_OPERATOR :
      case EQUALITY_OPERATOR :
        currNode.value=token;
        currNode.right = new QASHTree(currNode);
        currNode = currNode.right;
        break;
      case QASHToken.TT_WORD   :
      case QASHToken.TT_NUMBER :
      case QASHToken.TT_STRING                    :
        currNode.value=token;
        currNode = currNode.parent;
        break;
      default : throw new Exception("Didn't expect:"+token);
      }
    }
    return root;
  }
  /*--------- end the boolean expression handling routines ------------*/

  /**
   * Returns the index of the closing parenthesis matching the opening parenthesis
   * passed in the parameter openingParenPos.
   */
  public static int getClosingParenIndex(List<QASHToken> expr, int openingParenPos) throws Exception {
    int lCount=1, rCount=0;
    int i=openingParenPos+1;
    QASHToken token;
    while((lCount!=rCount)&&(i<expr.size())) {
      token = ((QASHToken)expr.get(i));
      if (token.ttype==RIGHT_PARENTHESIS) {
        rCount++;
      }
      else
        if (token.ttype==LEFT_PARENTHESIS) {
          lCount--;
        }
      i++;
    }
    if (lCount==rCount) {
      return (i-1);
    }
    else {
      throw new Exception("Unmatched parenthesis in"+expr);
    }
  }

  public String getTestName() {
    String res = getProperty(TEST_NAME_TAG);
    if (res == "") {
      return backwardsCompat("TEST_NAME",TEST_NAME_TAG+" NOT DEFINED").toString();
    }
    return res;
  }

  public String getTestAuthor() {
    String res = getProperty(TEST_AUTHOR_TAG);
    if (res == "") {
      return backwardsCompat("TEST_AUTHOR",TEST_AUTHOR_TAG+" NOT DEFINED").toString();
    }
    return res;
  }

  public String getTestBugInfo() {
    return getProperty(TEST_BUGINFO_TAG,"");
  }

  public String getTestDescription() {
    String res = getProperty(TEST_DESCRIPTION_TAG);
    if (res == "") {
      return backwardsCompat("TEST_DESCRIPTION",TEST_DESCRIPTION_TAG+" NOT DEFINED").toString();
    }
    return res;
  }

  private String getKeyWordsProperty() {
    String res = getProperty(KEYWORD_TAG);
    if (res == "") {
      res = backwardsCompat("KEYWORDS","NOT FOUND").toString();
    }
    return res;
  }

  public String[] getKeyWords() {
    return Utils.toStringArray(getKeyWordsProperty());
  }

  public String[] getIncludeList() {
    return Utils.toStringArray(getProperty(INCLUDE_QASH_LIST).toString());
  }

  public String[] getPropertiesIncludeList() {
    return Utils.toStringArray(getProperty(INCLUDE_PROPERTIES_LIST).toString());
  }

  public String[] getTraceList() {
    return Utils.toStringArray(getProperty(INTERNAL_TRACE_LIST).toString());
  }

  public static final void main(String args[]) {
    if (args.length<3) {
      System.out.println("QASHParser command line interface");
      System.out.println("     Useage  : QASHParser <name> <propertiesFile> test1 test2 .. testn");
      System.out.println("         name           = offset in the QAT home directory to place the results");
      System.out.println("         propertiesFile = the name of the properties or project file require to parse the test file");
      System.out.println("     Useage  : QASHParser <name> <propertiesFile> test1 test2 .. testn");
      System.out.println("     Returns : zero if all the tests passed or non-zero if one or more failed");
      System.exit(-1);
    }

    Properties def= new Properties(System.getProperties());
    try {
      def.load(new FileInputStream(new File(args[1])));
    }
    catch (Exception e) {
      System.out.println("Error loading properties file :"+args[1]);
      System.exit(-1);
    }
    try {
      QASHParser parser = new QASHParser();
      parser.setPrintStream(System.out,false);
      parser.prepare(args[1]);
      parser.setEvaluationMode(false);
      int currResult=0;
      int resultHolder=0;
      for (int i = 2; i < args.length; i++) {
        parser.setTestPath(args[i]);
        parser.setProperties(new Properties(def));
        try {
          currResult = parser.parseFile();
        }
        catch (Exception e) {
          log.severe("Parser error running :"+args[i]);
          currResult = -1;
        }
        if (currResult!=0) {
          resultHolder = currResult;
          log.severe(">>>>>>> Failed :"+currResult);                 
        }
        else {
          log.severe(">>>>>>> Passed :"+currResult);                 
        }
      }
      log.info("Finished running :"+resultHolder);
      System.exit(resultHolder);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  public void setStatusLabel(JLabel status) {
    this.parserStatus = status;
  }

  private void setStatus(int newStatus) {
    currQASHStatus = newStatus;
  }

  private int getStatus() {
    return currQASHStatus;
  }

  private boolean isStatusReported() {
    return statusReported;
  }

  private void setStatusReported(boolean r) {
    statusReported = r;
  }

  private void setStatusText(String s) {
    if (!inEvaluationMode())
      parserStatus.setText(s);
  }

  private void setStatusText(String s1, String s2) {
    if (!inEvaluationMode())
      parserStatus.setText(s1+LEFT_PARENTHESIS+Utils.trimFileName(s2,20)+RIGHT_PARENTHESIS);
  }

}
TOP

Related Classes of qat.parser.qashparser.QASHParser

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.