<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def parse_san(self, san: str) -> Move: """ Uses the current position as the context to parse a move in standard algebraic notation and returns the corresponding move object. The returned move is guaranteed to be either legal or a null move. :raises: :exc:`ValueError` if the SAN is invalid or ambiguous. """

        # Castling.
        try:
            if san in ["O-O", "O-O+", "O-O#"]:
                return next(move for move in self.generate_castling_moves() if self.is_kingside_castling(move))
            elif san in ["O-O-O", "O-O-O+", "O-O-O#"]:
                return next(move for move in self.generate_castling_moves() if self.is_queenside_castling(move))
        except StopIteration:
            raise ValueError("illegal san: {!r} in {}".format(san, self.fen()))

        # Match normal moves.
        match = SAN_REGEX.match(san)
        if not match:
            # Null moves.
            if san in ["--", "Z0"]:
                return Move.null()

            raise ValueError("invalid san: {!r}".format(san))

        # Get target square.
        to_square = SQUARE_NAMES.index(match.group(4))
        to_mask = BB_SQUARES[to_square] & ~self.occupied_co[self.turn]

        # Get the promotion type.
        p = match.group(5)
        promotion = p and PIECE_SYMBOLS.index(p[-1].lower())

        # Filter by piece type.
        if match.group(1):
            piece_type = PIECE_SYMBOLS.index(match.group(1).lower())
            from_mask = self.pieces_mask(piece_type, self.turn)
        else:
            from_mask = self.pawns

        # Filter by source file.
        if match.group(2):
            from_mask &= BB_FILES[FILE_NAMES.index(match.group(2))]

        # Filter by source rank.
        if match.group(3):
            from_mask &= BB_RANKS[int(match.group(3)) - 1]

        # Match legal moves.
        matched_move = None
        for move in self.generate_legal_moves(from_mask, to_mask):
            if move.promotion != promotion:
                continue

            if matched_move:
                raise ValueError("ambiguous san: {!r} in {}".format(san, self.fen()))

            matched_move = move

        if not matched_move:
            raise ValueError("illegal san: {!r} in {}".format(san, self.fen()))

        return matched_move