def attach(p1, to:, between: nil, and: nil)
      p2 = to
      sender = between
      # use binding#local_variable_get because 'and:' keyword argument clashes
      # with the language reserved keyword.
      receiver = binding.local_variable_get(:and)

      raise ArgumentError.new("'between:' keyword was omitted, 'p1' should be a Port.") if sender.nil? && !p1.is_a?(Port)
      raise ArgumentError.new("'and:' keyword was omitted, 'to:' should be a Port") if receiver.nil? && !p2.is_a?(Port)

      a = if p1.is_a?(Port) then p1.host
      elsif sender.is_a?(Coupleable) then sender
      elsif sender == @name then self
      else fetch_child(sender); end

      b = if p2.is_a?(Port) then p2.host
      elsif receiver.is_a?(Coupleable) then receiver
      elsif receiver == @name then self
      else fetch_child(receiver); end

      if has_child?(a) && has_child?(b) # IC
        p1 = a.output_port(p1) unless p1.is_a?(Port)
        p2 = b.input_port(p2) unless p2.is_a?(Port)
        raise InvalidPortTypeError.new unless p1.output? && p2.input?
        raise FeedbackLoopError.new("#{a} must be different than #{b}") if a.object_id == b.object_id
        _internal_couplings[p1] << p2
      elsif a == self && has_child?(b)  # EIC
        p1 = a.input_port(p1) unless p1.is_a?(Port)
        p2 = b.input_port(p2) unless p2.is_a?(Port)
        raise InvalidPortTypeError.new unless p1.input? && p2.input?
        _input_couplings[p1] << p2
      elsif has_child?(a) && b == self  # EOC
        p1 = a.output_port(p1) unless p1.is_a?(Port)
        p2 = b.output_port(p2) unless p2.is_a?(Port)
        raise InvalidPortTypeError.new unless p1.output? && p2.output?
        _output_couplings[p1] << p2
      else
        raise InvalidPortHostError.new("Illegal coupling between #{p1} and #{p2}")
      end
    end
def attach(p1, to:, between: nil, and: nil)
      p2 = to
      sender = between
      # use binding#local_variable_get because 'and:' keyword argument clashes
      # with the language reserved keyword.
      receiver = binding.local_variable_get(:and)

      raise ArgumentError.new("'between:' keyword was omitted, 'p1' should be a Port.") if sender.nil? && !p1.is_a?(Port)
      raise ArgumentError.new("'and:' keyword was omitted, 'to:' should be a Port") if receiver.nil? && !p2.is_a?(Port)

      a = if p1.is_a?(Port) then p1.host
      elsif sender.is_a?(Coupleable) then sender
      elsif sender == @name then self
      else fetch_child(sender); end

      b = if p2.is_a?(Port) then p2.host
      elsif receiver.is_a?(Coupleable) then receiver
      elsif receiver == @name then self
      else fetch_child(receiver); end

      if has_child?(a) && has_child?(b) # IC
        p1 = a.output_port(p1) unless p1.is_a?(Port)
        p2 = b.input_port(p2) unless p2.is_a?(Port)
        raise InvalidPortTypeError.new unless p1.output? && p2.input?
        raise FeedbackLoopError.new("#{a} must be different than #{b}") if a.object_id == b.object_id
        _internal_couplings[p1] << p2
      elsif a == self && has_child?(b)  # EIC
        p1 = a.input_port(p1) unless p1.is_a?(Port)
        p2 = b.input_port(p2) unless p2.is_a?(Port)
        raise InvalidPortTypeError.new unless p1.input? && p2.input?
        _input_couplings[p1] << p2
      elsif has_child?(a) && b == self  # EOC
        p1 = a.output_port(p1) unless p1.is_a?(Port)
        p2 = b.output_port(p2) unless p2.is_a?(Port)
        raise InvalidPortTypeError.new unless p1.output? && p2.output?
        _output_couplings[p1] << p2
      else
        raise InvalidPortHostError.new("Illegal coupling between #{p1} and #{p2}")
      end
    end
Adds a coupling to self between two ports.

 Depending on *p1* and *to* hosts, the function will create an internal
 coupling (IC), an external input coupling (EIC) or an external output
 coupling (EOC).

 @overload attach(p1, to:)
   @param p1 [Port] the sender port of the coupling
   @param to [Port] the receiver port of the coupling
 @overload attach(p1, to:, between:, and:)
   @param p1 [Port,Symbol,String] the sender port of the coupling
   @param to [Port,Symbol,String] the receiver port of the coupling
   @param between [Coupleable,Symbol,String] the sender
   @param and [Coupleable,Symbol,String] the receiver

 @raise [FeedbackLoopError] if *p1* and *p2* hosts are the same child
   when constructing an internal coupling. Direct feedback loops are not
   allowed, i.e, no output port of a component may be connected to an input
   port of the same component.
 @raise [InvalidPortTypeError] if given ports are not of the expected IO
   modes (:input or :output).
 @raise [InvalidPortHostError] if no coupling can be established from
   given ports hosts.

 @note If given port names *p1* and *p2* doesn't exist within their
   host (respectively *sender* and *receiver*), they will be automatically
   generated.
 @example Attach two ports together to form a new coupling
   attach component1.output_port(:out) to: component2.input_port(:in) # IC
   attach self.input_port(:in) to: child.input_port(:in) # EIC
   attach child.output_port(:out) to: self.output_port(:out) # EOC
 @example Attach specified ports and components to form a new coupling
   attach :out, to: :in, between: :component1, and: :component2
   attach :in, to: :in, between: self, and: child