@Override
 public DataType visitFactor(@NotNull PythonParser.FactorContext ctx) {
   if (ctx.power() != null) {
     return visitPower(ctx.power());
   } else if (ctx.factor() != null) {
     DataType type = visitFactor(ctx.factor());
     if (type.isInteger()) {
       if (ctx.PLUS() != null) {
         return type;
       } else if (ctx.MINUS() != null) {
         mv.visitInsn(INEG);
         return type;
       }
     } else if (type.isBoolean()) {
       if (ctx.PLUS() != null) {
         return type;
       } else if (ctx.MINUS() != null) {
         mv.visitInsn(INEG);
         return PrimitiveType.INTEGER;
       }
     } else {
       if (ctx.PLUS() != null) {
         throw new CompileException(
             String.format("Doing %s with %s is making no sense!", ctx.PLUS().getText(), type));
       } else if (ctx.MINUS() != null) {
         throw new CompileException(
             String.format("Doing %s with %s is making no sense!", ctx.MINUS().getText(), type));
       }
     }
   }
   throw new CompileException(
       String.format(
           "Something gone wrong! Please write to [email protected] with example test."));
 }
 @Override
 public DataType visitArith_expr(@NotNull PythonParser.Arith_exprContext ctx) {
   DataType type = visitTerm(ctx.term(0));
   int i = 1;
   if (!ctx.MINUS().isEmpty()) {
     if (!type.isPrimitive()) {
       throw new CompileException(
           String.format(
               "Doing %s with %s is making no sense!", ctx.MINUS().get(0).getText(), type));
     }
     for (TerminalNode ignored : ctx.MINUS()) {
       type = visitTerm(ctx.term(i++));
       mv.visitInsn(ISUB);
     }
     if (type.equals(PrimitiveType.BOOLEAN)) return PrimitiveType.INTEGER;
   } else if (!ctx.PLUS().isEmpty()) {
     if (!type.isPrimitive()) {
       throw new CompileException(
           String.format(
               "Doing %s with %s is making no sense!", ctx.PLUS().get(0).getText(), type));
     }
     for (TerminalNode ignored : ctx.PLUS()) {
       type = visitTerm(ctx.term(i++));
       mv.visitInsn(IADD);
     }
     if (type.equals(PrimitiveType.BOOLEAN)) return PrimitiveType.INTEGER;
   }
   return type;
 }
 @Override
 public DataType visitAnd_test(@NotNull PythonParser.And_testContext ctx) {
   DataType type = visitNot_test(ctx.not_test(0));
   if (!ctx.isEmpty() && ctx.not_test().size() > 1) {
     if (!type.isPrimitive()) {
       throw new CompileException(String.format("Doing and with %s is making no sense!", type));
     }
     for (int i = 1; i < ctx.not_test().size(); i++) {
       visitNot_test(ctx.not_test(i));
       Label falseLabel = new Label();
       Label fastFalseLabel = new Label();
       Label exitLabel = new Label();
       mv.visitJumpInsn(IFEQ, fastFalseLabel);
       mv.visitJumpInsn(IFEQ, falseLabel);
       mv.visitInsn(ICONST_1);
       mv.visitJumpInsn(GOTO, exitLabel);
       mv.visitLabel(fastFalseLabel);
       mv.visitInsn(POP);
       mv.visitLabel(falseLabel);
       mv.visitInsn(ICONST_0);
       mv.visitLabel(exitLabel);
     }
     return PrimitiveType.BOOLEAN;
   }
   return type;
 }
 @Override
 public DataType visitPrint_stmt(@NotNull PythonParser.Print_stmtContext ctx) {
   mv.visitFieldInsn(
       GETSTATIC,
       Type.getInternalName(System.class),
       "out",
       Type.getDescriptor(PrintStream.class));
   DataType type = visitTest(ctx.test());
   mv.visitMethodInsn(
       INVOKEVIRTUAL,
       Type.getInternalName(PrintStream.class),
       "println",
       Type.getMethodDescriptor(Type.VOID_TYPE, type.getType()),
       false);
   return null;
 }
 @Override
 public DataType visitXor_expr(@NotNull PythonParser.Xor_exprContext ctx) {
   DataType type = visitAnd_expr(ctx.and_expr(0));
   int i = 1;
   if (!ctx.CIRCUMFLEX().isEmpty()) {
     if (!type.isPrimitive()) {
       throw new CompileException(
           String.format(
               "Doing %s with %s is making no sense!", ctx.CIRCUMFLEX().get(0).getText(), type));
     }
     for (TerminalNode ignored : ctx.CIRCUMFLEX()) {
       type = visitAnd_expr(ctx.and_expr(i++));
       mv.visitInsn(IXOR);
     }
     if (type.equals(PrimitiveType.BOOLEAN)) return PrimitiveType.INTEGER;
   }
   return type;
 }
  @Override
  public DataType visitComparison(@NotNull PythonParser.ComparisonContext ctx) {
    DataType type = visitExpr(ctx.expr(0));
    int i = 1;
    if (!ctx.comp_op().isEmpty()) {
      if (!type.isPrimitive()) {
        throw new CompileException(
            String.format(
                "Doing %s with %s is making no sense!", ctx.comp_op().get(0).getText(), type));
      }
      for (PythonParser.Comp_opContext op : ctx.comp_op()) {
        visitExpr(ctx.expr(i++));
        visitComp_op(op);
      }
      return PrimitiveType.BOOLEAN;
    }

    return type;
  }
  @Override
  public DataType visitTerm(@NotNull PythonParser.TermContext ctx) {
    DataType type = visitFactor(ctx.factor(0));
    int i = 1;
    if (!ctx.PERCENT().isEmpty()) {
      if (!type.isPrimitive()) {
        throw new CompileException(
            String.format(
                "Doing %s with %s is making no sense!", ctx.PERCENT().get(0).getText(), type));
      }
      for (TerminalNode ignored : ctx.PERCENT()) {
        type = visitFactor(ctx.factor(i++));
        mv.visitInsn(IREM);
      }
      if (type.equals(PrimitiveType.BOOLEAN)) return PrimitiveType.INTEGER;
    } else if (!ctx.SLASH().isEmpty()) {
      if (!type.isPrimitive()) {
        throw new CompileException(
            String.format(
                "Doing %s with %s is making no sense!", ctx.SLASH().get(0).getText(), type));
      }
      for (TerminalNode ignored : ctx.SLASH()) {
        type = visitFactor(ctx.factor(i++));
        mv.visitInsn(IDIV);
      }
      if (type.equals(PrimitiveType.BOOLEAN)) return PrimitiveType.INTEGER;
    } else if (!ctx.STAR().isEmpty()) {
      if (!type.isPrimitive()) {
        throw new CompileException(
            String.format(
                "Doing %s with %s is making no sense!", ctx.STAR().get(0).getText(), type));
      }
      for (TerminalNode ignored : ctx.STAR()) {
        type = visitFactor(ctx.factor(i++));
        mv.visitInsn(IMUL);
      }
      if (type.equals(PrimitiveType.BOOLEAN)) return PrimitiveType.INTEGER;
    }

    return type;
  }
  @Override
  public DataType visitAtom(@NotNull PythonParser.AtomContext ctx) {
    //        System.out.println("Zz" + ctx.NAME() + ctx.STRING());
    if (ctx.test() != null) { // 1
      return visitTest(ctx.test());
    } else if (ctx.STRING().size() > 0) { // 7,8,9
      String ss = "";
      for (TerminalNode s : ctx.STRING()) {
        ss += s.getText();
      }
      mv.visitLdcInsn(ss.length());
      mv.visitIntInsn(NEWARRAY, T_CHAR);
      int tempIndex = 0;
      for (int i = 0; i < ss.length(); i++) {
        if (!("\"".equals(ss.substring(i, i + 1))) && !("\'".equals(ss.substring(i, i + 1)))) {
          mv.visitInsn(DUP);
          mv.visitLdcInsn(tempIndex);
          mv.visitLdcInsn(ss.charAt(i));
          mv.visitInsn(CASTORE);
          tempIndex++;
        }
      }
      if (ctx.LBRACK() != null) {
        if (Integer.parseInt(ctx.INT().getText()) > tempIndex - 1) {
          throw new CompileException(
              String.format(
                  "Index out of bounds exception for string %s with index %s",
                  ss, Integer.parseInt(ctx.INT().getText())));
        }
        mv.visitLdcInsn(Integer.parseInt(ctx.INT().getText()));
        mv.visitInsn(CALOAD);
        mv.visitMethodInsn(
            INVOKESTATIC,
            Type.getType(String.class).getInternalName(),
            "valueOf",
            "(C)Ljava/lang/String;",
            false);
        return new StringType(1);
      } else {
        mv.visitMethodInsn(
            INVOKESTATIC,
            Type.getType(String.class).getInternalName(),
            "valueOf",
            "([C)Ljava/lang/String;",
            false);
        return new StringType(ss.length());
      }
    } else if (!ctx.NAME().isEmpty()) { // 3,4,5
      DataType type = null;
      //            System.out.println("GG" + ctx.getText() + "GG");
      String varName = ctx.NAME().get(0).getText();
      if (scope.isLocalVariable(varName)) {
        if (ctx.LBRACK() != null) { // 4,5
          type = scope.getLocalVariableType(varName);
          if (!(type instanceof StringType)) {
            throw new CompileException(String.format("Trying to take index not from string!!"));
          }
          mv.visitVarInsn(type.isPrimitive() ? ILOAD : ALOAD, scope.getLocalVariableIndex(varName));
          if (ctx.INT() != null) {
            mv.visitMethodInsn(
                INVOKEVIRTUAL,
                Type.getType(String.class).getInternalName(),
                "toCharArray",
                "()[C",
                false);
            if (Integer.parseInt(ctx.INT().getText()) > type.getSize()) {
              throw new CompileException(String.format("Index out of bounds exception!"));
            }
            mv.visitLdcInsn(Integer.parseInt(ctx.INT().getText()));
            mv.visitInsn(CALOAD);
            mv.visitMethodInsn(
                INVOKESTATIC,
                Type.getType(String.class).getInternalName(),
                "valueOf",
                "(C)Ljava/lang/String;",
                false);
            return new StringType(1);
          } else if (ctx.NAME(1) != null) {
            varName = ctx.NAME(1).getText();
            type = scope.getLocalVariableType(ctx.NAME(1).getText());
            if (!type.isInteger()) {
              throw new CompileException(
                  String.format("Trying to take index from string not with int!"));
            } else {
              Label okLabel = new Label();
              mv.visitMethodInsn(
                  INVOKEVIRTUAL,
                  Type.getType(String.class).getInternalName(),
                  "toCharArray",
                  "()[C",
                  false);
              mv.visitVarInsn(ILOAD, scope.getLocalVariableIndex(ctx.NAME(1).getText()));
              mv.visitInsn(DUP);
              mv.visitLdcInsn(type.getSize());
              mv.visitInsn(SWAP);
              mv.visitJumpInsn(IF_ICMPGT, okLabel);
              mv.visitLabel(okLabel);
              mv.visitInsn(CALOAD);
              mv.visitMethodInsn(
                  INVOKESTATIC,
                  Type.getType(String.class).getInternalName(),
                  "valueOf",
                  "(C)Ljava/lang/String;",
                  false);
              // TODO out of bounds a[b]
              //                            mv.visitLdcInsn("Out of bounds!");
              //                            mv.visitMethodInsn(INVOKESPECIAL,
              //                                    "java/lang/CompilerException", "<init>",
              // "(Ljava/lang/String;)V", false);
              return new StringType(1);
            }

          } else {
            throw new CompileException(String.format("Unidentified operation"));
          }
        } else { // 3
          type = scope.getLocalVariableType(varName);
          mv.visitVarInsn(type.isPrimitive() ? ILOAD : ALOAD, scope.getLocalVariableIndex(varName));
        }
      } else if (scope.isGlobalVariable(varName)) {
        if (ctx.LBRACK() != null) { // 4,5
          type = scope.getGlobalVariableType(varName);
          if (!(type instanceof StringType)) {
            throw new CompileException(String.format("Trying to take index not from string!!"));
          }
          mv.visitFieldInsn(
              GETSTATIC, scope.getClassName(), varName, type.getType().getDescriptor());
          if (ctx.INT() != null) {
            mv.visitMethodInsn(
                INVOKEVIRTUAL,
                Type.getType(String.class).getInternalName(),
                "toCharArray",
                "()[C",
                false);
            if (Integer.parseInt(ctx.INT().getText()) > type.getSize()) {
              throw new CompileException(String.format("Index out of bounds exception!"));
            }
            mv.visitLdcInsn(Integer.parseInt(ctx.INT().getText()));
            mv.visitInsn(CALOAD);
            mv.visitMethodInsn(
                INVOKESTATIC,
                Type.getType(String.class).getInternalName(),
                "valueOf",
                "(C)Ljava/lang/String;",
                false);
            return new StringType(1);
          } else if (ctx.NAME(1) != null) {
            varName = ctx.NAME(1).getText();
            type = scope.getLocalVariableType(ctx.NAME(1).getText());
            if (!type.isInteger()) {
              throw new CompileException(
                  String.format("Trying to take index from string not with int!"));
            } else {
              Label okLabel = new Label();
              mv.visitMethodInsn(
                  INVOKEVIRTUAL,
                  Type.getType(String.class).getInternalName(),
                  "toCharArray",
                  "()[C",
                  false);
              mv.visitVarInsn(ILOAD, scope.getLocalVariableIndex(ctx.NAME(1).getText()));
              mv.visitInsn(DUP);
              mv.visitLdcInsn(type.getSize());
              mv.visitInsn(SWAP);
              mv.visitJumpInsn(IF_ICMPGT, okLabel);
              mv.visitLabel(okLabel);
              mv.visitInsn(CALOAD);
              mv.visitMethodInsn(
                  INVOKESTATIC,
                  Type.getType(String.class).getInternalName(),
                  "valueOf",
                  "(C)Ljava/lang/String;",
                  false);
              // TODO out of bounds a[b]
              //                            mv.visitLdcInsn("Out of bounds!");
              //                            mv.visitMethodInsn(INVOKESPECIAL,
              //                                    "java/lang/CompilerException", "<init>",
              // "(Ljava/lang/String;)V", false);
              return new StringType(1);
            }

          } else {
            throw new CompileException(String.format("Unidentified operation"));
          }
        } else { // 3
          type = scope.getGlobalVariableType(varName);
          mv.visitFieldInsn(
              GETSTATIC, scope.getClassName(), varName, type.getType().getDescriptor());
        }
      }
      return type;
      //			} else if (scope.isFunction)
    } else if (ctx.INT() != null) { // 6
      mv.visitLdcInsn(Integer.parseInt(ctx.INT().getText()));
      return PrimitiveType.INTEGER;
    } else if (ctx.BOOL() != null) { // 2
      mv.visitInsn("False".equals(ctx.BOOL().getText()) ? ICONST_0 : ICONST_1);
      return PrimitiveType.BOOLEAN;
    } else {
      throw new CompileException(String.format("Undefined thing to work with! %s", ctx.getText()));
    }
  }
  @Override
  public DataType visitPower(@NotNull PythonParser.PowerContext ctx) {
    //        System.out.println("called!");

    if (ctx.trailer() != null)
      if (ctx.atom().NAME() != null) {
        String name = ctx.atom().NAME(0).getText();
        Integer argumentGiven = 0;
        String returnSignatue = "V";
        String argSignature = "";
        String totalSinature = "";

        if (ctx.trailer().arglist() != null) {
          argumentGiven = ctx.trailer().arglist().argument().size();
        }
        if (!scope.isFunctionDeclared(name))
          throw new CompileException(String.format("Function %s is not defined.", name));
        else {
          Integer argumentsCount = scope.functionArgumentCount(name);
          if (!argumentsCount.equals(argumentGiven)) {
            throw new CompileException(
                String.format(
                    "Function %s takes exactly %s arguments (%s given)",
                    name, argumentsCount, argumentGiven));
          } else {
            if (argumentGiven == 0) {
              argSignature = "";
            } else {
              int i = 0;
              String[] argumentsNames = scope.getFunctionArgumentsNames(name, argumentGiven);
              for (PythonParser.ArgumentContext argctx : ctx.trailer().arglist().argument()) {
                DataType argType = visitArgument(argctx);
                argSignature += argType.getType().getDescriptor();
                // System.out.println("Хуйня тут" + argType + argumentsNames[i]);
                scope.addLocalVariable(argumentsNames[i], argType);
                //                                int opcode =
                // scope.getLocalVariableType(argumentsNames[i]).isPrimitive() ? ISTORE : ASTORE;
                //                                mv.visitVarInsn(opcode,
                // scope.getLocalVariableIndex(argumentsNames[i]));
                i++;
              }
            }

            totalSinature = "(" + argSignature + ")" + returnSignatue;
            System.out.println("ACTION!" + totalSinature);
            MethodVisitor mvSave = mv;
            mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, name, totalSinature, null, null);
            visitSuite(scope.getFunctionSuite(name, argumentGiven));
            mv.visitInsn(RETURN);
            mv.visitMaxs(0, 0);
            mv.visitEnd();
            scope.setMethodName(name);
            scope.refreshLocalVariables();
            mv = mvSave;
            mv.visitMethodInsn(INVOKESTATIC, scope.getClassName(), name, totalSinature, false);
          }
        }

        //                //TODO function with () call {a()} + default values
        //                DataType returnType = scope.getFunctionReturnType(name, argumentType);
        //                mv.visitMethodInsn(INVOKESTATIC, scope.getClassName(), name,
        // Utils.getFunctionDescriptor(returnType, argumentType), false);
        //                System.out.println("called!");
        return null;

      } else {
        throw new CompileException(
            String.format("%s object is not callable!", ctx.atom().getText()));
      }
    return visitAtom(ctx.atom());
  }
 @Override
 public DataType visitExpr_stmt(@NotNull PythonParser.Expr_stmtContext ctx) {
   if (ctx.power() != null) {
     if (ctx.power().atom().NAME() != null && ctx.power().atom().STRING().size() == 0) {
       String var = ctx.power().atom().NAME().get(0).getText();
       if (ctx.power().atom().LBRACK() != null) {
         if (scope.isLocalVariable(var)) {
           if (!(scope.getLocalVariableType(var) instanceof StringType)) {
             throw new CompileException(String.format("Trying to take index not from string!"));
           }
         } else if (scope.isGlobalVariable(var)) {
           if (!(scope.getGlobalVariableType(var) instanceof StringType)) {
             throw new CompileException(String.format("Trying to take index not from string!"));
           }
         }
         throw new CompileException(
             String.format("Assigning to element of String is not implemented yet!"));
         //                    if (ctx.power().atom().INT() != null) {
         //
         //                    } else {
         //
         //                    }
       } else {
         if (ctx.test() != null) {
           if (ctx.power().trailer() != null) {
             throw new CompileException(String.format("Can't assign to function!"));
           }
           DataType etype = visitTest(ctx.test());
           if (scope.isLocalVariable(var)) {
             //                System.out.println("LOCAL" + var + "e" + etype +  "e" +
             // scope.getLocalVariableType(var) );
             if (etype.equals(scope.getLocalVariableType(var))) {
               //                    System.out.println("Type not changing!");
               int opcode = scope.getLocalVariableType(var).isPrimitive() ? ISTORE : ASTORE;
               mv.visitVarInsn(opcode, scope.getLocalVariableIndex(var));
             } else {
               //                    System.out.println("Type changing!");
               scope.changeLocalVariableType(var, etype);
               int opcode = etype.isPrimitive() ? ISTORE : ASTORE;
               mv.visitVarInsn(opcode, scope.getLocalVariableIndex(var));
             }
           } else if (scope.isGlobalVariable(var)) {
             DataType vtype = scope.getGlobalVariableType(var);
             mv.visitFieldInsn(
                 PUTSTATIC, scope.getClassName(), var, vtype.getType().getDescriptor());
             //                        } else if (scope.isFunctionDeclared(var)) {    TODO
           } else {
             scope.addLocalVariable(var, etype);
             int opcode = scope.getLocalVariableType(var).isPrimitive() ? ISTORE : ASTORE;
             mv.visitVarInsn(opcode, scope.getLocalVariableIndex(var));
             // throw new CompileException(String.format("Variable %s not found in context %s.",
             // var, ctx.getText()));
           }
           return etype;
         } else if (ctx.power().trailer() != null) { // is function
           visitPower(ctx.power());
           //                        if
           // (!scope.isFunctionDeclared(ctx.power().atom().NAME(0).getText(),
           // ctx.power().trailer().arglist().argument().size()))
           //                            throw new CompileException(String.format("Function %s not
           // defined.", var));
           //                        else
           //                            System.out.println("ACTION!");
         } else if (scope.isLocalVariable(var)) {
           return scope.getLocalVariableType(var);
         } else if (scope.isGlobalVariable(var)) {
           return scope.getGlobalVariableType(var);
         } else throw new CompileException(String.format("Variable %s not defined.", var));
       }
     } else {
       throw new CompileException(String.format("can't assign to operator"));
     }
   } else {
     System.out.println("Something bad happened!");
   }
   return null;
 }