@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>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>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;
   }
 }