private static Expression getRvalue(
     Module module, SourceReader sReader, TokenReader reader, boolean noDecl) {
   reader.skipWhitespace();
   Expression rvalue = ExpressionParser.parse(module, sReader, reader, noDecl);
   if (rvalue == null) {
     throw new CompilationFailedError(
         sReader.getLocation(reader.peek()), "Expected rvalue after binary operator");
   }
   return rvalue;
 }
  public static Expression parse(
      Module module, SourceReader sReader, TokenReader reader, boolean noDecl) {

    int mark = reader.mark();

    Token firstToken = reader.peek();
    if (firstToken.type == TokenType.BANG) {
      reader.skip();
      Expression inner = ExpressionParser.parse(module, sReader, reader, noDecl);
      if (inner == null) {
        reader.reset(mark);
        return null;
      }
      return new Not(inner, firstToken);
    }

    if (firstToken.type == TokenType.TILDE) {
      reader.skip();
      Expression inner = ExpressionParser.parse(module, sReader, reader, noDecl);
      if (inner == null) {
        reader.reset(mark);
        return null;
      }
      return new BinaryNegation(inner, firstToken);
    }

    if (firstToken.type == TokenType.MINUS) {
      reader.skip();
      Expression inner = ExpressionParser.parse(module, sReader, reader, noDecl);
      if (inner == null) {
        reader.reset(mark);
        return null;
      }
      return new Sub(new IntLiteral(0, Format.DEC, firstToken), inner, firstToken);
    }

    Expression expr = null;
    if (reader.peek().type == TokenType.OPEN_PAREN) {
      reader.skip();
      reader.skipWhitespace();
      expr = parse(module, sReader, reader, noDecl);
      if (expr == null) {
        throw new CompilationFailedError(
            sReader.getLocation(reader.peek()),
            "Failed to parse expression between parenthesis, got " + reader.peek());
      }
      expr = new Parenthesis(expr, expr.startToken);
      reader.skipWhitespace();
      if (reader.read().type != TokenType.CLOS_PAREN) {
        throw new CompilationFailedError(
            sReader.getLocation(reader.prev()),
            "Expected closing parenthesis, but got " + reader.prev());
      }
    } else {
      expr = parseFlatNoparen(module, sReader, reader, noDecl);
    }

    if (expr == null) return null;

    while (reader.hasNext()) {

      Token token = reader.peek();

      if (token.isNameToken()) {
        FunctionCall call = FunctionCallParser.parse(module, sReader, reader);
        if (call != null) {
          expr = new MemberCall(expr, call, token);
          continue;
        }

        VariableAccess varAccess = AccessParser.parse(module, sReader, reader);
        if (varAccess != null) {
          expr = new MemberAccess(expr, varAccess, token);
          continue;
        }
      }

      if (token.type == TokenType.DOUBLE_DOT) {

        reader.skip();
        Expression upper = ExpressionParser.parse(module, sReader, reader, noDecl);
        if (upper == null) {
          throw new CompilationFailedError(
              sReader.getLocation(reader.peek()),
              "Expected expression for the upper part of a range literal");
        }
        // this is so beautiful it makes me wanna cry
        expr = new RangeLiteral(expr, upper, expr.startToken);
      }

      if (token.type == TokenType.OPEN_SQUAR) {

        ArrayAccess arrAcc = new ArrayAccess(expr, token);
        expr = arrAcc;
        reader.skip();

        while (reader.peekWhiteless().type != TokenType.CLOS_SQUAR) {
          Expression index = ExpressionParser.parse(module, sReader, reader, noDecl);
          if (index == null) {
            throw new CompilationFailedError(
                sReader.getLocation(reader.peek()),
                "Expected expression for the index of an array access");
          }
          arrAcc.getIndices().add(index);

          if (reader.peekWhiteless().type != TokenType.COMMA) {
            break;
          }
          reader.skip();
        }

        if (reader.read().type != TokenType.CLOS_SQUAR) {
          throw new CompilationFailedError(
              sReader.getLocation(reader.prev()),
              "Expected closing bracket to end array access, got " + reader.prev() + " instead.");
        }
        continue;
      }

      if (token.type == TokenType.ASSIGN) {

        reader.skip();
        Expression rvalue = ExpressionParser.parse(module, sReader, reader, noDecl);
        if (rvalue == null) {
          throw new CompilationFailedError(
              sReader.getLocation(reader.peek()), "Expected expression after '='.");
        }
        ensureAccess(expr, sReader, reader);
        if (token.type == TokenType.ASSIGN) {
          expr = new Assignment(expr, rvalue, token);
        }
        continue;
      }

      if (token.type == TokenType.AMPERSAND) {
        reader.skip();
        expr = new AddressOf(expr, token);
        continue;
      }

      if (token.type == TokenType.QUEST) {
        reader.skip();
        Expression ifTrue = parse(module, sReader, reader, true);
        if (reader.read().type != TokenType.COLON) {
          throw new CompilationFailedError(
              sReader.getLocation(reader.prev()),
              "Expected ':' after '?', but got " + reader.prev());
        }
        Expression ifFalse = parse(module, sReader, reader, true);
        expr = new Ternary(expr.startToken, expr, ifTrue, ifFalse);
      }

      if (token.type == TokenType.AT) {
        reader.skip();
        expr = new Dereference(expr, token);
        continue;
      }

      /** Must find a way to clean that up */
      if (token.type == TokenType.GREATERTHAN) {
        reader.skip();
        Token token2 = reader.peek();
        if (token2.type == TokenType.GREATERTHAN) {
          reader.skip();
          Token token3 = reader.peek();
          if (token3.type == TokenType.ASSIGN) {
            reader.skip();
            Expression rvalue = getRvalue(module, sReader, reader, noDecl);
            rvalue = new BinaryCombination(BinaryComp.RSHIFT, expr, rvalue, token);
            expr = new Assignment(Mode.REGULAR, expr, rvalue, token);
          } else {
            Expression rvalue = getRvalue(module, sReader, reader, noDecl);
            expr = new BinaryCombination(BinaryComp.RSHIFT, expr, rvalue, token);
          }
        } else if (token2.type == TokenType.ASSIGN) {
          reader.skip();
          Expression rvalue = getRvalue(module, sReader, reader, noDecl);
          expr = new Compare(expr, rvalue, CompareType.GREATER_OR_EQUAL, token);
        } else {
          Expression rvalue = getRvalue(module, sReader, reader, noDecl);
          expr = new Compare(expr, rvalue, CompareType.GREATER, token);
        }
      } else if (token.type == TokenType.LESSTHAN) {
        reader.skip();
        Token token2 = reader.peek();
        if (token2.type == TokenType.LESSTHAN) {
          reader.skip();
          Token token3 = reader.peek();
          if (token3.type == TokenType.ASSIGN) {
            reader.skip();
            Expression rvalue = getRvalue(module, sReader, reader, noDecl);
            rvalue = new BinaryCombination(BinaryComp.LSHIFT, expr, rvalue, token);
            expr = new Assignment(Mode.REGULAR, expr, rvalue, token);
          } else {
            Expression rvalue = getRvalue(module, sReader, reader, noDecl);
            expr = new BinaryCombination(BinaryComp.LSHIFT, expr, rvalue, token);
          }
        } else if (token2.type == TokenType.ASSIGN) {
          reader.skip();
          Expression rvalue = getRvalue(module, sReader, reader, noDecl);
          expr = new Compare(expr, rvalue, CompareType.LESSER_OR_EQUAL, token);
        } else {
          Expression rvalue = getRvalue(module, sReader, reader, noDecl);
          expr = new Compare(expr, rvalue, CompareType.LESSER, token);
        }
      } else if (token.type == TokenType.PLUS
          || token.type == TokenType.STAR
          || token.type == TokenType.MINUS
          || token.type == TokenType.SLASH
          || token.type == TokenType.PERCENT
          || token.type == TokenType.EQUALS
          || token.type == TokenType.NOT_EQUALS
          || token.type == TokenType.PLUS_ASSIGN
          || token.type == TokenType.MINUS_ASSIGN
          || token.type == TokenType.STAR_ASSIGN
          || token.type == TokenType.SLASH_ASSIGN
          || token.type == TokenType.DOUBLE_PIPE
          || token.type == TokenType.DOUBLE_AMPERSAND
          || token.type == TokenType.PIPE
          || token.type == TokenType.AMPERSAND
          || token.type == TokenType.BINARY_AND
          || token.type == TokenType.CARET) {

        reader.skip();
        reader.skipWhitespace();

        boolean isAssign = false;

        if (reader.peek().type == TokenType.ASSIGN) {
          isAssign = true;
          reader.skip();
        }

        Expression rvalue = getRvalue(module, sReader, reader, noDecl);

        switch (token.type) {
          case TokenType.PLUS:
            expr = new Add(expr, rvalue, token);
            break;
          case TokenType.STAR:
            expr = new Mul(expr, rvalue, token);
            break;
          case TokenType.MINUS:
            expr = new Sub(expr, rvalue, token);
            break;
          case TokenType.SLASH:
            expr = new Div(expr, rvalue, token);
            break;
          case TokenType.PERCENT:
            expr = new Mod(expr, rvalue, token);
            break;
          case TokenType.EQUALS:
            expr = new Compare(expr, rvalue, CompareType.EQUAL, token);
            break;
          case TokenType.NOT_EQUALS:
            expr = new Compare(expr, rvalue, CompareType.NOT_EQUAL, token);
            break;
          case TokenType.PLUS_ASSIGN:
            ensureAccess(expr, sReader, reader);
            expr = new Assignment(Mode.ADD, expr, rvalue, token);
            break;
          case TokenType.MINUS_ASSIGN:
            ensureAccess(expr, sReader, reader);
            expr = new Assignment(Mode.SUB, expr, rvalue, token);
            break;
          case TokenType.STAR_ASSIGN:
            ensureAccess(expr, sReader, reader);
            expr = new Assignment(Mode.MUL, expr, rvalue, token);
            break;
          case TokenType.SLASH_ASSIGN:
            ensureAccess(expr, sReader, reader);
            expr = new Assignment(Mode.DIV, expr, rvalue, token);
            break;
          case TokenType.PIPE:
            if (isAssign) {
              ensureAccess(expr, sReader, reader);
              expr = new Assignment(Mode.B_OR, expr, rvalue, token);
              break;
            }
            expr = new BinaryCombination(BinaryComp.BITWISE_OR, expr, rvalue, token);
            break;
          case TokenType.AMPERSAND:
          case TokenType.BINARY_AND:
            if (isAssign) {
              ensureAccess(expr, sReader, reader);
              expr = new Assignment(Mode.B_AND, expr, rvalue, token);
              break;
            }
            expr = new BinaryCombination(BinaryComp.BITWISE_AND, expr, rvalue, token);
            break;
          case TokenType.DOUBLE_PIPE:
            expr = new BinaryCombination(BinaryComp.LOGICAL_OR, expr, rvalue, token);
            break;
          case TokenType.DOUBLE_AMPERSAND:
            expr = new BinaryCombination(BinaryComp.LOGICAL_AND, expr, rvalue, token);
            break;
          case TokenType.CARET:
            if (isAssign) {
              ensureAccess(expr, sReader, reader);
              expr = new Assignment(Mode.B_XOR, expr, rvalue, token);
              break;
            }
            expr = new BinaryCombination(BinaryComp.BITWISE_XOR, expr, rvalue, token);
            break;
          default:
            throw new CompilationFailedError(
                sReader.getLocation(reader.prev()), "Unknown binary operation yet " + token.type);
        }
        continue;
      }

      if (token.type == TokenType.AS_KW) {

        reader.skip();
        Type type = TypeParser.parse(module, sReader, reader);
        if (type == null) {
          throw new CompilationFailedError(
              sReader.getLocation(reader.prev()),
              "Expected destination type after 'as' keyword (e.g. for casting)");
        }
        expr = new Cast(expr, type, token);
        continue;
      }

      return expr;
    }

    return null;
  }