/**
  * {@inheritDoc}
  *
  * <p>Determine the result type of a unary operator.
  *
  * @param ctx The UnaryOpContext parse tree node
  * @return The result type of VOID if not valid.
  */
 @Override
 public Type visitUnaryop(UnaryopContext ctx) {
   String op = ctx.op.getText();
   Type rhsType = visit(ctx.expr());
   if (rhsType instanceof PrimitiveType) {
     switch ((PrimitiveType) rhsType) {
       case REAL:
         if (op.equals("-") || op.equals("+")) {
           typeMap.put(ctx, REAL);
           return REAL;
         }
         error(ctx, op + " cannot be applied to REAL");
         break;
       case INT:
         if (op.equals("-") || op.equals("+") || op.equals("~")) {
           typeMap.put(ctx, INT);
           return INT;
         }
         error(ctx, op + " cannot be applied to INT");
         break;
       case BOOL:
         if (op.equals("\00ac")) {
           typeMap.put(ctx, BOOL);
           return BOOL;
         }
         error(ctx, op + " cannot be applied to BOOL");
         break;
       default:
         error(ctx, op + " cannot be applied to " + rhsType);
         break;
     }
   }
   typeMap.put(ctx, VOID);
   return VOID;
 }
  @Override
  public Type visitRecordAccess(MicroParser.RecordAccessContext ctx) {
    Type recordType = visit(ctx.expr());

    if (recordType instanceof RecordType) {
      Scope recordScope = ((RecordType) recordType).getContainedScope();
      Identifier id = recordScope.resolve(ctx.ID().getText());
      if (id != null) {
        Type exprType = id.getType();
        typeMap.put(ctx, exprType);
        return exprType;
      } else {
        MicroCompilerV1.error(
            ctx,
            ctx.ID().getText()
                + " is not a member of "
                + ((RecordType) recordType).getRecordTypeName());
        return VOID;
      }
    } else if (recordType instanceof ArrayType && ctx.ID().getText().equals("length")) {
      typeMap.put(ctx, INT);
      return INT;
    } else {
      MicroCompilerV1.error(ctx, "Not a record type");
      return VOID;
    }
  }
 /**
  * {@inheritDoc}
  *
  * <p>Determine if the assignment is valid. BOOL and only be assigned to BOOL. INT or REAL can be
  * assigned to INT or REAL.
  *
  * @param ctx The Assignment_Statement Context parse tree node
  * @return VOID
  */
 @Override
 public Type visitAssignment_statement(Assignment_statementContext ctx) {
   Type lhsType = visit(ctx.lvalue());
   Type rhsType = visit(ctx.expr());
   if (!assignmentValid(lhsType, rhsType)) {
     MicroCompilerV1.error(ctx, rhsType + " cannot be assigned to " + lhsType);
   }
   typeMap.put(ctx, VOID);
   return VOID;
 }
 @Override
 public Type visitFcnCall(FcnCallContext ctx) {
   ctx.expr_list().expr().forEach(expr -> visit(expr));
   Identifier fcnId = currentScope.resolve(ctx.ID().getText());
   if (fcnId != null) {
     Type fcnType = fcnId.getType();
     if (fcnType instanceof ProcedureOrFunction) {
       Type returnType = ((ProcedureOrFunction) fcnType).getReturnType();
       typeMap.put(ctx, returnType);
       return returnType;
     } else {
       MicroCompilerV1.error(ctx, ctx.ID().getText() + " is not a function");
     }
   } else {
     MicroCompilerV1.error(ctx, ctx.ID().getText() + " is not a defined");
   }
   typeMap.put(ctx, VOID);
   return VOID;
 }
 /**
  * {@inheritDoc}
  *
  * <p>Determine if the while_statement part is valid. The guard expression must be of type BOOL.
  *
  * @param ctx The If_statement context parse tree node.
  * @return VOID
  */
 @Override
 public Type visitWhile_statement(While_statementContext ctx) {
   Type guardType = visit(ctx.expr());
   if (guardType != BOOL) {
     error(ctx, "While statement guard is not a boolean type");
   }
   visit(ctx.statement_list());
   typeMap.put(ctx, VOID);
   return VOID;
 }
 /**
  * {@inheritDoc}
  *
  * <p>The default implementation returns the result of calling {@link #visitChildren} on {@code
  * ctx}.
  */
 @Override
 public Type visitIdLvalue(IdLvalueContext ctx) {
   Identifier id = currentScope.resolve(ctx.ID().getText());
   if (id == null) {
     MicroCompilerV1.error(ctx, ctx.ID().getText() + " is not defined");
     return VOID;
   }
   Type lhsType = id.getType();
   typeMap.put(ctx, lhsType);
   return lhsType;
 }
 /**
  * {@inheritDoc}
  *
  * <p>Determine the type of an ID
  *
  * @param ctx The parse tree node.
  * @return The type of the identifier.
  */
 @Override
 public Type visitId(IdContext ctx) {
   String idName = ctx.ID().getText();
   Identifier id = currentScope.resolve(idName);
   if (id != null) {
     typeMap.put(ctx, id.getType());
     return id.getType();
   }
   error(ctx, "Undefined identifier " + idName);
   typeMap.put(ctx, VOID);
   return VOID;
 }
 /**
  * {@inheritDoc}
  *
  * <p>Determine result type of logical op. Both operands must be BOOL
  *
  * @param ctx LogicalopContext node.
  * @return BOOL if valid, VOID otherwise
  */
 @Override
 public Type visitLogicalop(LogicalopContext ctx) {
   Type lhsType = visit(ctx.expr(0));
   Type rhsType = visit(ctx.expr(1));
   if (BOOL == lhsType && BOOL == rhsType) {
     typeMap.put(ctx, BOOL);
     return BOOL;
   }
   error(ctx, lhsType + " cannot be combined using a logical operator with " + rhsType);
   typeMap.put(ctx, VOID);
   return VOID;
 }
 /**
  * {@inheritDoc}
  *
  * <p>The default implementation returns the result of calling {@link #visitChildren} on {@code
  * ctx}.
  */
 @Override
 public Type visitArrayAccess(MicroParser.ArrayAccessContext ctx) {
   Type arrayType = visit(ctx.expr(0));
   if (arrayType instanceof ArrayType) {
     Type exprType = ((ArrayType) arrayType).getComponentType();
     typeMap.put(ctx, exprType);
     return exprType;
   } else {
     MicroCompilerV1.error(ctx, "Not an array type");
     return VOID;
   }
 }
 /**
  * {@inheritDoc}
  *
  * <p>Determine if the if statement is valid. The guard expression must be of type BOOL.
  *
  * @param ctx The If_statement context parse tree node.
  * @return VOID
  */
 @Override
 public Type visitIf_statement(If_statementContext ctx) {
   Type guardType = visit(ctx.expr());
   if (guardType != BOOL) {
     error(ctx, "If statement guard is not a boolean type");
   }
   visit(ctx.statement_list());
   ctx.elsif_part().forEach(elsif_part -> visit(elsif_part));
   if (ctx.else_part() != null) {
     visit(ctx.else_part());
   }
   typeMap.put(ctx, VOID);
   return VOID;
 }
 /**
  * {@inheritDoc}
  *
  * <p>Determine the result type of a POW expression.
  *
  * @param ctx The POW op context parse tree node
  * @return The result type or VOID if invalid.
  */
 @Override
 public Type visitPowop(PowopContext ctx) {
   Type lhsType = visit(ctx.expr(0));
   Type rhsType = visit(ctx.expr(1));
   Type resultType;
   if (lhsType == CHAR || rhsType == CHAR) {
     resultType = VOID;
   } else {
     resultType = determineExpressionResult(lhsType, rhsType);
   }
   if (VOID == resultType) {
     error(ctx, lhsType + " ** " + rhsType + " is not valid");
   }
   typeMap.put(ctx, resultType);
   return resultType;
 }
 /**
  * {@inheritDoc}
  *
  * <p>Determine the result type of an arithmetic expression.
  *
  * @param ctx The parse tree node.
  * @return The result type or VOID if invalid.
  */
 @Override
 public Type visitArithop(ArithopContext ctx) {
   Type lhsType = visit(ctx.expr(0));
   Type rhsType = visit(ctx.expr(1));
   Type resultType;
   String op = ctx.op.getText();
   if (lhsType == CHAR || rhsType == CHAR) {
     switch (op) {
       case "+":
         if ((lhsType == CHAR && rhsType == INT) || (rhsType == CHAR && lhsType == INT)) {
           resultType = CHAR;
         } else {
           resultType = VOID;
         }
         break;
       case "-":
         if (lhsType == CHAR && rhsType == CHAR) {
           resultType = INT;
         } else if (lhsType == CHAR && rhsType == INT) {
           resultType = CHAR;
         } else {
           resultType = VOID;
         }
         break;
       default:
         resultType = VOID;
         break;
     }
   } else {
     resultType = determineExpressionResult(lhsType, rhsType);
   }
   if (VOID == resultType) {
     error(ctx, lhsType + " " + op + " " + rhsType + " is not a valid combination");
   }
   typeMap.put(ctx, resultType);
   return resultType;
 }