/**
   * Parse a Triangle SingleDeclarationParser.
   *
   * <p>Single-Declaration ::= const Identifier ~ Expression | var Identifier : Type-Denoter | proc
   * Identifier (Formal-Parameter-Sequence) ~ Single-Command | func Identifier
   * (Formal-Parameter-Sequence) : Type-Denoter ~ Expression | Type Identifier ~ Type-Denoter
   *
   * <p>To be overridden by the specialized command parse subclasses.
   *
   * @param token the initial token.
   * @return the root node of the generated parse tree.
   * @throws Exception if an error occurred.
   */
  public void parse(Token token) throws Exception {
    SingleCommandParser singleCommand = null;
    ExpressionParser expression = null;
    FormalParameterSequenceParser formalParameterSequence = null;
    TypeDenoterParser typeDenoter = null;
    EnumSet<TriangleTokenType> syncSet = null;
    TypeSpec identifierType = TrianglePredefined.undefinedType;
    Token identifierToken = null;
    ICode routineICode = null;

    token = currentToken();

    switch ((TriangleTokenType) token.getType()) {
      case CONST:
        identifierToken = nextToken();
        syncSet = EnumSet.of(TILDE);
        syncSet.addAll(ExpressionParser.FIRST_FOLLOW_SET);
        synchronize(IDENTIFIER, MISSING_IDENTIFIER, syncSet);
        syncSet.remove(TILDE);
        token = synchronize(TILDE, MISSING_TILDE, syncSet);
        expression = new ExpressionParser(this);
        ICodeNode expressionNode = expression.parse(token);

        SymTabEntry constantId = symTabStack.lookupLocal(identifierToken.getText().toLowerCase());
        // Enter the new identifier into the symbol table
        // but don't set how it's defined yet.
        if (constantId == null) {
          constantId = symTabStack.enterLocal(identifierToken.getText().toLowerCase());
          constantId.setDefinition(DefinitionImpl.CONSTANT);
          constantId.setAttribute(CONSTANT_VALUE, expressionNode);
          constantId.appendLineNumber(identifierToken.getLineNumber());
          constantId.setTypeSpec(expressionNode.getTypeSpec());
        } else {
          errorHandler.flag(identifierToken, IDENTIFIER_REDEFINED, this);
        }
        break;
      case VAR:
        identifierToken = nextToken();
        syncSet = EnumSet.of(COLON);
        syncSet.addAll(TypeDenoterParser.FIRST_FOLLOW_SET);
        synchronize(IDENTIFIER, MISSING_IDENTIFIER, syncSet);

        syncSet.remove(COLON);
        token = synchronize(COLON, MISSING_COLON, syncSet);

        typeDenoter = new TypeDenoterParser(this);
        identifierType = typeDenoter.parse(token);

        SymTabEntry variableId = symTabStack.lookupLocal(identifierToken.getText().toLowerCase());
        // Enter the new identifier into the symbol table
        // but don't set how it's defined yet.
        if (variableId == null) {
          variableId = symTabStack.enterLocal(identifierToken.getText().toLowerCase());
          variableId.setDefinition(DefinitionImpl.VARIABLE);
          variableId.appendLineNumber(identifierToken.getLineNumber());
          variableId.setTypeSpec(identifierType);
        } else {
          errorHandler.flag(identifierToken, IDENTIFIER_REDEFINED, this);
        }
        break;
      case PROC:
        identifierToken = nextToken();
        routineICode = ICodeFactory.createICode();
        syncSet = EnumSet.of(LEFT_PAREN);
        syncSet.addAll(FormalParameterSequenceParser.FIRST_FOLLOW_SET);
        token = synchronize(IDENTIFIER, MISSING_IDENTIFIER, syncSet);
        syncSet.remove(LEFT_PAREN);
        token = synchronize(LEFT_PAREN, MISSING_LEFT_PAREN, syncSet);
        SymTabEntry procId = symTabStack.lookupLocal(identifierToken.getText().toLowerCase());
        // Enter the new identifier into the symbol table
        // but don't set how it's defined yet.
        if (procId == null) {
          procId = symTabStack.enterLocal(identifierToken.getText().toLowerCase());
          procId.setTypeSpec(TrianglePredefined.undefinedType);
          procId.setDefinition(DefinitionImpl.PROCEDURE);
          procId.appendLineNumber(identifierToken.getLineNumber());
          procId.setAttribute(ROUTINE_SYMTAB, symTabStack.push());
          procId.setAttribute(ROUTINE_ICODE, routineICode);
        } else {
          errorHandler.flag(identifierToken, IDENTIFIER_REDEFINED, this);
          procId = null;
        }

        formalParameterSequence = new FormalParameterSequenceParser(this);
        ArrayList<SymTabEntry> procParamList = formalParameterSequence.parse(token);
        syncSet = EnumSet.of(TILDE);
        syncSet.addAll(SingleCommandParser.FIRST_FOLLOW_SET);
        token = synchronize(RIGHT_PAREN, MISSING_RIGHT_PAREN, syncSet);
        syncSet.remove(TILDE);
        token = synchronize(TILDE, MISSING_TILDE, syncSet);
        singleCommand = new SingleCommandParser(this);
        routineICode.setRoot(singleCommand.parse(token));
        if (procId != null) {
          procId.setAttribute(ROUTINE_PARMS, procParamList);
        }
        symTabStack.pop();
        break;
      case FUNC:
        identifierToken = nextToken();
        routineICode = ICodeFactory.createICode();
        SymTabEntry funcId = symTabStack.lookupLocal(identifierToken.getText().toLowerCase());
        // Enter the new identifier into the symbol table
        // but don't set how it's defined yet.
        if (funcId == null) {
          funcId = symTabStack.enterLocal(identifierToken.getText().toLowerCase());
          funcId.setDefinition(DefinitionImpl.FUNCTION);
          funcId.appendLineNumber(identifierToken.getLineNumber());
          funcId.setTypeSpec(TrianglePredefined.undefinedType);
          funcId.setAttribute(ROUTINE_SYMTAB, symTabStack.push());
          funcId.setAttribute(ROUTINE_ICODE, routineICode);
        } else {
          errorHandler.flag(identifierToken, IDENTIFIER_REDEFINED, this);
          funcId = null;
        }
        syncSet = EnumSet.of(LEFT_PAREN);
        syncSet.addAll(FormalParameterSequenceParser.FIRST_FOLLOW_SET);
        token = synchronize(IDENTIFIER, MISSING_IDENTIFIER, syncSet);
        syncSet.remove(LEFT_PAREN);
        token = synchronize(LEFT_PAREN, MISSING_LEFT_PAREN, syncSet);
        formalParameterSequence = new FormalParameterSequenceParser(this);
        ArrayList<SymTabEntry> funcParamList = formalParameterSequence.parse(token);
        syncSet = EnumSet.of(COLON);
        syncSet.addAll(TypeDenoterParser.FIRST_FOLLOW_SET);
        synchronize(RIGHT_PAREN, MISSING_RIGHT_PAREN, syncSet);
        syncSet.remove(COLON);
        token = synchronize(COLON, MISSING_COLON, syncSet);
        Token typeToken = currentToken();
        typeDenoter = new TypeDenoterParser(this);
        TypeSpec funcType = typeDenoter.parse(token);
        syncSet = ExpressionParser.FIRST_FOLLOW_SET.clone();
        token = synchronize(TILDE, MISSING_TILDE, syncSet);
        expression = new ExpressionParser(this);
        routineICode.setRoot(expression.parse(token));
        if (funcId != null) {
          funcId.setTypeSpec(funcType);
          funcId.setAttribute(ROUTINE_PARMS, funcParamList);
          if (!routineICode.getRoot().getTypeSpec().equals(funcType)) {
            errorHandler.flag(typeToken, RETURN_TYPE_MISMATCH, this);
          }
        }
        symTabStack.pop();
        break;
      case TYPE:
        identifierToken = nextToken();
        syncSet = EnumSet.of(TILDE);
        syncSet.addAll(TypeDenoterParser.FIRST_FOLLOW_SET);
        synchronize(IDENTIFIER, MISSING_IDENTIFIER, syncSet);
        syncSet.remove(TILDE);
        token = synchronize(TILDE, MISSING_TILDE, syncSet);
        typeDenoter = new TypeDenoterParser(this);
        TypeSpec typeType = typeDenoter.parse(token);
        SymTabEntry typeId = symTabStack.lookupLocal(identifierToken.getText().toLowerCase());
        // Enter the new identifier into the symbol table
        // but don't set how it's defined yet.
        if (typeId == null) {
          typeId = symTabStack.enterLocal(identifierToken.getText().toLowerCase());
          typeId.setDefinition(DefinitionImpl.TYPE);
          typeId.appendLineNumber(identifierToken.getLineNumber());
          typeId.setTypeSpec(typeType);
        } else {
          errorHandler.flag(token, IDENTIFIER_REDEFINED, this);
        }
        break;
      default:
        errorHandler.flag(token, MISSING_DECLARATION, this);
        break;
    }
  }
  /**
   * Parse a factor.
   *
   * @param token the initial token.
   * @return the root of the generated parse subtree.
   * @throws Exception if an error occurred.
   */
  private ICodeNode parseFactor(Token token) throws Exception {
    TokenType tokenType = token.getType();
    ICodeNode rootNode = null;

    switch ((PascalTokenType) tokenType) {
      case IDENTIFIER:
        {
          // Look up the identifier in the symbol table stack.
          // Flag the identifier as undefined if it's not found.
          String name = token.getText().toLowerCase();
          SymTabEntry id = symTabStack.lookup(name);
          if (id == null) {
            errorHandler.flag(token, IDENTIFIER_UNDEFINED, this);
            id = symTabStack.enterLocal(name);
          }

          rootNode = ICodeFactory.createICodeNode(VARIABLE);
          rootNode.setAttribute(ID, id);
          id.appendLineNumber(token.getLineNumber());

          token = nextToken(); // consume the identifier
          break;
        }

      case INTEGER:
        {
          // Create an INTEGER_CONSTANT node as the root node.
          rootNode = ICodeFactory.createICodeNode(INTEGER_CONSTANT);
          rootNode.setAttribute(VALUE, token.getValue());

          token = nextToken(); // consume the number
          break;
        }

      case REAL:
        {
          // Create an REAL_CONSTANT node as the root node.
          rootNode = ICodeFactory.createICodeNode(REAL_CONSTANT);
          rootNode.setAttribute(VALUE, token.getValue());

          token = nextToken(); // consume the number
          break;
        }

      case STRING:
        {
          String value = (String) token.getValue();

          // Create a STRING_CONSTANT node as the root node.
          rootNode = ICodeFactory.createICodeNode(STRING_CONSTANT);
          rootNode.setAttribute(VALUE, value);

          token = nextToken(); // consume the string
          break;
        }

      case NOT:
        {
          token = nextToken(); // consume the NOT

          // Create a NOT node as the root node.
          rootNode = ICodeFactory.createICodeNode(ICodeNodeTypeImpl.NOT);

          // Parse the factor.  The NOT node adopts the
          // factor node as its child.
          rootNode.addChild(parseFactor(token));

          break;
        }

      case LEFT_PAREN:
        {
          token = nextToken(); // consume the (

          // Parse an expression and make its node the root node.
          rootNode = parseExpression(token);

          // Look for the matching ) token.
          token = currentToken();
          if (token.getType() == RIGHT_PAREN) {
            token = nextToken(); // consume the )
          } else {
            errorHandler.flag(token, MISSING_RIGHT_PAREN, this);
          }

          break;
        }
      case LEFT_BRACKET:
        {
          rootNode = parseSet(token);

          break;
        }

      default:
        {
          errorHandler.flag(token, UNEXPECTED_TOKEN, this);
          break;
        }
    }

    return rootNode;
  }