% \iffalse meta-comment
%% =============================================================================
%%
%% string-diagrams 0.4.0 (2026/11/12)
%%
%% Copyright (C) 2023 by Paolo Brasolin <paolo.brasolin@gmail.com>
%% SPDX-License-Identifier: LPPL-1.3c
%%
%% =============================================================================
%%
%% This work may be distributed and/or modified under the
%% conditions of the LaTeX Project Public License, either version 1.3c
%% of this license or (at your option) any later version.
%% The latest version of this license is in
%%   https://www.latex-project.org/lppl.txt
%% and version 1.3c or later is part of all distributions of LaTeX
%% version 2008 or later.
%%
%% This work has the LPPL maintenance status `author-maintained'.
%%
%% The Current Maintainer of this work is Paolo Brasolin.
%%
%% This work consists of the files  README.md,
%%                                  string-diagrams.dtx,
%%                                  string-diagrams.ins,
%% and the derived files            string-diagrams.sty, and
%%                                  string-diagrams.pdf.
%%
%% =============================================================================
%%
%
% %%%=[ DRIVER ]================================================================
%
%<*driver>
\documentclass[a4paper,full]{l3doc}
\usepackage{parskip}

\NewDocumentCommand{\TikZ}{}{Ti\emph{k}Z}

\EnableCrossrefs
\CodelineIndex
\RecordChanges
\usepackage{string-diagrams}
\usetikzlibrary{calc}
^^A l3doc makes " a short verbatim character, which breaks the quotes of TikZ in the examples.
\AtBeginDocument{\DeleteShortVerb\"}

\usepackage{tcolorbox}
\tcbuselibrary{listings, skins}

\lstset{
  language=[LaTeX]TeX,
  basicstyle=\MacroFont,
  columns=flexible,
  ^^A keywordstyle=\color{red},
  ^^A morekeywords={},
  texcsstyle=*\color{violet},
  moretexcs={node,wires,draw,path,foreach,side,n},
  breaklines=true,
}

\tcbset{example/.style={
  listing file={\jobname-example-#1.tex},
  listing engine=listings,
  verbatim ignore percent=true,
  listing side text,
  size=minimal,
  skin=bicolor,
  colback=black!5!white,
  colbacklower=white,
  sidebyside,
  lefthand ratio=0.62,
  listing options={
    xleftmargin=-1.6em, % poor man's gobble=4
  },
}}

\begin{document}
  \DocInput{\jobname.dtx}
  \PrintChanges
  \PrintIndex
\end{document}
%</driver>
%
% \fi
%
% \changes{0.1.0}{2023/05/31}{initial version}
% \changes{0.2.0}{2023/06/12}{make box ports configurable}
% \changes{0.3.0}{2026/11/07}{replace loose ends with wires to the boundary node \texttt{@}}
% \changes{0.4.0}{2026/11/12}{write wires whole, leave out anchors, draw wires on \TikZ\ paths with the \texttt{wire} key, and label their ends}
%
% \GetFileInfo{\jobname.sty}
%
% \title{^^A
%   The \pkg{\jobname} package^^A
%   \thanks{Thanks!}\\^^A
%   \fileinfo^^A
% }
%
% \author{^^A
%   Paolo Brasolin\\^^A
%   \texttt{\href{mailto:paolo.brasolin@gmail.com}{paolo.brasolin@gmail.com}}^^A
% }
%
% \date{\fileversion~(\filedate)}
%
% \maketitle
%
% ^^A=[ DOCUMENTATION ]=========================================================
%
% \begin{documentation}
%
% \begin{tcolorbox}[
%   colback=red!5!white,
%   colframe=red,
%   sharp corners,
%   boxrule=1pt,
% ]
% This is \href{https://semver.org/#spec-item-4}{major version zero}, for initial development: \emph{anything MAY change at any time}.
% So this is the best time to \href{https://github.com/paolobrasolin/string-diagrams#contributing}{contribute}!
% You can also keep the \texttt{sty} file with your code, and not care at all.
% \end{tcolorbox}
%
%
% \section{Documentation}
%
% Load the package with \cs{usepackage}\texttt{\{string-diagrams\}}.
% It loads \TikZ, with its \texttt{fit} and \texttt{quotes} libraries.
% The examples below need nothing else, unless they say otherwise.
%
% \subsection{Boxes}
%
% \begin{function}[added=2023-05-31,updated=2026-11-07]{/pgf/box}
%
%   To draw a box, use this style on a node.
%   Its label is in math mode already, so write it without dollars: \texttt{\{f\}}, since \texttt{\{\$f\$\}} breaks the node.
%
%   \begin{tcblisting}{example=box}
%     \begin{tikzpicture}
%       \node[box] {A};
%     \end{tikzpicture}
%   \end{tcblisting}
%
% \end{function}
%
% Place boxes with any positioning technique of \TikZ, and name them, so that wires can refer to them.
% The last box below is placed with the \texttt{calc} library of \TikZ: load it with \cs{usetikzlibrary}\texttt{\{calc\}}.
%
% \begin{tcblisting}{example=boxes}
%     \begin{tikzpicture}
%       \node[box] (A) at (0,0) {A};
%       \node[box, right of=A] (B) {B};
%       \node[box] (C) at ($(B)+(2cm,1em)$) {C};
%     \end{tikzpicture}
% \end{tcblisting}
%
% \begin{function}[added=2023-06-12,updated=2026-11-07]{
%   /pgf/box ports north,
%   /pgf/box ports east,
%   /pgf/box ports south,
%   /pgf/box ports west,
% }
%   \begin{syntax}
%     /pgf/box ports north=\meta{integer}
%     /pgf/box ports east=\meta{integer}
%     /pgf/box ports south=\meta{integer}
%     /pgf/box ports west=\meta{integer}
%   \end{syntax}
%
%   These keys open ports on the sides of a box: a box has none until you open them.
%   As other \TikZ\ keys, you can set them on a picture or a scope, for all the boxes in it.
%   Refer to a port by its side and number: ports are numbered from left to right on the north and south sides, and from top to bottom on the east and west sides.
%   A port that a box doesn't have is an error.
%
%   \begin{tcblisting}{example=box-ports,lefthand ratio=0.8}
%     \begin{tikzpicture}[
%       marker/.style={circle, fill, inner sep=1pt, text=white},
%     ]
%
%     \node[
%       box,
%       box ports north=3,
%       box ports east=3,
%       box ports south=3,
%       box ports west=3,
%       minimum width=6em,
%       minimum height=6em,
%     ] (A) {A};
%
%     \foreach \side in {north,east,south,west}
%       \foreach \index in {1,...,3}
%         \node[marker] at (A.\side.\index) {\index};
%
%     \end{tikzpicture}
%   \end{tcblisting}
%
% \end{function}
%
% \begin{function}[added=2023-06-12,updated=2026-11-07]{
%   /pgf/box ports,
% }
%   \begin{syntax}
%     /pgf/box ports=\meta{integer}/\meta{integer}/\meta{integer}/\meta{integer}
%   \end{syntax}
%
%   The \texttt{box ports} key sets the ports of all sides at once.
%   It takes exactly four counts, for the north, east, south and west sides.
%
%   \begin{tcblisting}{example=box-ports-shortcut,lefthand ratio=0.8}
%     \begin{tikzpicture}[
%       marker/.style={circle, fill, inner sep=1pt},
%     ]
%
%     \node[box, box ports=1/2/3/4] (A) {A};
%
%     \foreach \side/\n in {north/1,east/2,south/3,west/4}
%       \foreach \index in {1,...,\n}
%         \node[marker] at (A.\side.\index) {};
%
%     \end{tikzpicture}
%   \end{tcblisting}
%
%   You can also give the same value to the \texttt{box} key itself.
%   Without a value, \texttt{box} keeps the counts in force.
%
% \end{function}
%
% \subsection{Wires}
%
% \begin{function}[added=2023-05-31,updated=2026-11-11]{\wires}
%   \begin{syntax}
%     \cs{wires}\oarg{\TikZ\ keys}\marg{wires}
%   \end{syntax}
%
%   To connect nodes, use \cmd\wires.
%   Its optional argument holds \TikZ\ keys for all its wires.
%   Its mandatory argument lists the wires, separated by commas.
%   A wire goes from an anchor of a node to an anchor of a node: \meta{node}\texttt{.}\meta{anchor} \texttt{=} \meta{node}\texttt{.}\meta{anchor}.
%
%   \begin{tcblisting}{example=wire}
%     \begin{tikzpicture}
%       \node[box=1/0/0/0] (f) at (0,0) {f};
%       \node[box=0/0/1/0] (g) at (1,1.5) {g};
%       \wires{ f.north.1 = g.south.1 }
%     \end{tikzpicture}
%   \end{tcblisting}
%
% \end{function}
%
% Wires start and end at these anchors:
% \begin{itemize}
%   \item the sides of a node: \texttt{north}, \texttt{east}, \texttt{south} and \texttt{west};
%   \item its corners: \texttt{north east} and so on;
%   \item the ports of a box, such as \texttt{east.1}.
% \end{itemize}
% Wires leave sides and ports straight out, and corners diagonally.
% Other anchors, such as \texttt{center}, have no direction, so wires can't start or end there.
%
% The boundary of the picture is a node too, called \texttt{@}.
% Wires to its sides are the loose ends of the diagram.
% They go straight out to their side, or bend towards it.
%
% Wires that begin alike can share their beginning.
% Write it once, then \texttt{=} and a group in braces, with the rest of each wire.
% A beginning ends after the name of a node, or at the \texttt{=} of a wire.
% So a group can hold:
% \begin{itemize}
%   \item the anchors of the node the wires start from, as in \texttt{A = \{ east.1 = C.west, west = @.west \}};
%   \item the targets of an anchor, as in \texttt{f.north.1 = \{ g.south.1, h.south.1 \}};
%   \item the anchors of the node of a target, as in \texttt{f.north.1 = \{ g = \{ south.1, south.2 \} \}}.
% \end{itemize}
% Groups can nest, and every grouping of the same wires draws the same wires.
% Only beginnings are shared: wires that end alike can't share their end.
% A node can appear in more than one entry, and an anchor can carry several wires, in and out.
%
% \begin{tcblisting}{example=wires}
%     \begin{tikzpicture}[scale=0.6]
%       \node[box=0/2/0/1] (A) at (-2, 0) {A};
%       \node[box=0/1/0/2] (B) at (+2, 0) {B};
%       \node[box=0/1/0/1] (C) at ( 0,+1) {C};
%       \node[box=0/1/0/1] (D) at ( 0,-1) {D};
%       \wires{
%         A = { east.1 = C.west
%             , east.2 = D.west
%             , west = @.west },
%         B.east = @.east,
%         C.east = B.west.1,
%         D.east = B.west.2,
%       }
%     \end{tikzpicture}
% \end{tcblisting}
%
% The first \cmd\wires\ of a picture draws the boundary around the nodes drawn so far, with a margin.
% Later calls in the same picture use the same boundary.
% So draw all nodes before the wires.
%
% A wire across the boundary runs through its middle.
% To place it elsewhere, for example beside a box:
% \begin{enumerate}
%   \item Place a coordinate there, with a path as below: \cs{coordinate} doesn't count towards the size of the picture, so the boundary might miss it.
%   \item Wire the coordinate to both sides of the boundary.
% \end{enumerate}
%
% \begin{tcblisting}{example=identity}
%     \begin{tikzpicture}
%       \node[box=1/0/1/0] (f) at (0,0) {f};
%       \path (0.8,0) coordinate (i);
%       \wires{
%         f = { north.1 = @.north
%             , south.1 = @.south },
%         i = { north = @.north
%             , south = @.south },
%       }
%     \end{tikzpicture}
% \end{tcblisting}
%
% The keys of a call apply to all its wires.
% Keys in brackets can also begin any entry, group or target, as in \texttt{f.north.1 = [dashed] g.south.1}.
% They apply to every wire they reach, and add up from the call down to each wire, so the keys closest to a wire win.
% As in \TikZ, keys in brackets end at the first \texttt{]} outside braces: so a key with brackets goes in braces, as in \texttt{[-\{Stealth[length=6pt]\}]}.
%
% \begin{tcblisting}{example=styling}
%     \begin{tikzpicture}
%       \node[box=1/0/1/0] (f) at (0,0) {f};
%       \node[box=1/0/1/0] (g) at (0,1.5) {g};
%       \wires{
%         f.south.1 = @.south,
%         f.north.1 = [dashed] g.south.1,
%         g.north.1 = @.north,
%       }
%     \end{tikzpicture}
% \end{tcblisting}
%
% All keys come after the angles that \cmd\wires\ computes, so \texttt{out} and \texttt{in} override those angles.
% \texttt{bend left} and \texttt{bend right} bend a wire between its ends.
% Each wire is a \TikZ\ edge, so its keys style its stroke too.
%
% Wires take their directions from the names of anchors, in the frame of the picture.
% So you can shift and scale a picture, but not rotate or mirror it.
% Rotated nodes aren't supported either.
%
% \subsection{Anchors left out}
%
% \begin{function}[added=2026-11-11]{/tikz/wire flow}
%   \begin{syntax}
%     /tikz/wire flow=\meta{direction}
%   \end{syntax}
%
%   Sets the direction of wires between ends without anchors: \texttt{north}, \texttt{east}, \texttt{south} or \texttt{west}.
%   The synonyms \texttt{up}, \texttt{right}, \texttt{down} and \texttt{left} work as for the \texttt{grow} key of \TikZ.
%   \texttt{none} removes the flow.
%   Set it on a picture, a scope, a call, a group, a wire or a path, as any key.
%
%   \begin{tcblisting}{example=flow}
%     \begin{tikzpicture}[wire flow=north]
%       \node[box=1/0/1/0] (f) at (0,0) {f};
%       \node[box=1/0/1/0] (g) at (.4,1.4) {g};
%       \wires{ @ = f, f = g, g = @ }
%     \end{tikzpicture}
%   \end{tcblisting}
%
% \end{function}
%
% You can leave out the anchor of either end of a wire, or of both, as in \texttt{f = g}.
% An end without anchor is the side that the wire crosses going straight on.
% If that side has one port, the wire ends at the port.
% If it has more, the package can't choose: it reports an error, so name the port.
%
% The direction of the wire comes from:
% \begin{itemize}
%   \item the anchored end, if there is one. A wire from a corner goes diagonally, so it turns to the axis of the flow, if set, or else to the axis that separates its ends more.
%   \item the flow, if both ends are bare.
%   \item else, the axis that separates the centers of the two nodes more.
%     Two bare ends on nodes with the same center need a flow.
% \end{itemize}
%
% \begin{tcblisting}{example=bare}
%     \begin{tikzpicture}
%       \node[box=2/0/0/0] (f) at (0,0) {f};
%       \node[box=0/0/1/0] (g) at (-.5,1.5) {g};
%       \node[dot] (d) at (.8,1.5) {};
%       \wires{ f = { north.1 = g, north.2 = d } }
%     \end{tikzpicture}
% \end{tcblisting}
%
% The flow decides even when the target lies behind: then the wire leaves along the flow, and loops round.
% A wire goes from its start to its target, so swap them to turn it.
% Without a flow, the axis flips when two nodes are near a diagonal: a small move of a node can turn their wire.
% Set a flow to keep it.
%
% With a flow, loose ends need no anchors either: \texttt{@ = f} leaves the boundary behind \texttt{f}, and \texttt{f = @} reaches it ahead of \texttt{f}.
%
% \begin{tcblisting}{example=flow-east}
%     \begin{tikzpicture}[wire flow=east]
%       \node[box=0/1/0/1] (a) at (0,0) {a};
%       \node[box=0/1/0/1] (b) at (1.2,.3) {b};
%       \node[box=0/1/0/1] (c) at (2.4,0) {c};
%       \wires{ @ = a, a = b, b = c, c = @ }
%     \end{tikzpicture}
% \end{tcblisting}
%
% \subsection{Frames}
%
% An end faces inwards when its node holds the other node: its box contains the box of the other, borders that touch counting as inside.
% Then the wire leaves or enters that end the other way, and a side without ports is reached straight across.
% This is how loose ends work, since the boundary holds the nodes of the picture.
% A frame drawn by hand works the same way.
%
% \begin{tcblisting}{example=frame}
%     \begin{tikzpicture}
%       \node[box=0/1/0/1] (a) at (0,.25) {a};
%       \node[draw, dashed, minimum width=2.6cm,
%             minimum height=1.4cm] (F) at (0,0) {};
%       \wires{ F.west = a, a = F.east }
%     \end{tikzpicture}
% \end{tcblisting}
%
% Nodes apart both face out, and a wire between them may loop round.
% Nodes that overlap otherwise, or have the same box, face out too.
% But then the package can't tell which way a bare end goes, so give both ends anchors.
%
% A wire from a node to itself loops round the node, and a wire from the boundary to itself crosses the picture.
% With both ends bare, such a wire needs a flow: with \texttt{wire flow=east}, \texttt{f = f} is \texttt{f.east = f.west}, and \texttt{@ = @} is \texttt{@.west = @.east}.
%
% \subsection{The wire key}
%
% \begin{function}[added=2026-11-11]{/tikz/wire,/tikz/every wire}
%   \begin{syntax}
%     \cs{draw} (\meta{node}.\meta{anchor}) to[wire] (\meta{node}.\meta{anchor});
%     \cs{path} (\meta{node}.\meta{anchor}) edge[wire] (\meta{node}.\meta{anchor});
%   \end{syntax}
%
%   The \texttt{wire} key draws a \texttt{to} path or an edge of \TikZ\ as a wire, as \cmd\wires\ does.
%   The \texttt{every wire} style applies right after it.
%
%   \begin{tcblisting}{example=wire-key}
%     \begin{tikzpicture}
%       \node[box=1/0/0/0] (f) at (0,0) {f};
%       \node[box=0/0/1/0] (g) at (1.2,1.5) {g};
%       \node[box=1/0/0/0] (h) at (2.4,0) {h};
%       \draw[->] (f.north.1) to[wire] (g.south.1);
%       \path (h) edge[wire, dashed, "$A$"'] (g);
%     \end{tikzpicture}
%   \end{tcblisting}
%
% \end{function}
%
% Ends can be bare, as in \cmd\wires.
% Loose ends need the boundary, which only \cmd\wires\ draws: so call \cmd\wires\ first in the picture.
% The key sets the \texttt{out} and \texttt{in} keys of \TikZ, so \TikZ\ draws the curve and places its labels.
% Keys after \texttt{wire} override its angles, and keys before it don't.
% A wrong end raises an error, and the wire isn't drawn.
%
% On an edge, \texttt{every wire} styles the stroke, as \texttt{every edge} does.
% On a \texttt{to} path it doesn't, as \texttt{every to} doesn't: there, keys shape the curve, but only the keys of the path draw it.
%
% \subsection{Labels}
%
% \begin{function}[added=2026-11-11]{
%   /tikz/beyond,
%   /tikz/beside,
%   /tikz/beyond end,
%   /tikz/beyond start,
%   /tikz/beside end,
%   /tikz/beside start,
% }
%   These keys place a label as the \texttt{auto} key of \TikZ\ does, but past the end of a wire, or beside it and clear of the box.
%   \texttt{beyond end} is short for \texttt{at end, beyond}, and so on.
%
%   \begin{tcblisting}{example=labels}
%     \begin{tikzpicture}
%       \node[box=2/0/1/0] (f) {f};
%       \wires{ f = {
%         north.1 = ["$A$" beyond end] @,
%         north.2 = ["$B$" beyond end] @,
%         south.1 = ["$C$" beyond end] @ } }
%     \end{tikzpicture}
%   \end{tcblisting}
%
% \end{function}
%
% Wires take three kinds of labels:
% \begin{itemize}
%   \item The type of a wire, along it.
%     Use the quotes of \TikZ, as in \texttt{f.north.1 = ["\$A\$"] g.south.1}.
%     The label goes to the left of the wire, and \texttt{"\$A\$"'} puts it to the right.
%   \item The interface of the diagram, at its loose ends.
%     Use \texttt{beyond end}, or \texttt{beyond start} on a wire from the boundary: the label goes past the end.
%   \item The type of a port, at its box.
%     Use \texttt{beside start} or \texttt{beside end} on the wire, or \texttt{beside} in the label of the box, as in \texttt{label=\{[beside]north.1:\$A\$\}}.
%     The label goes beside the port, clear of the box, to the left of a wire leaving it; \texttt{swap} puts it to the right.
%     In the label of the box, it belongs to the port, so it shows once, however many wires the port has.
% \end{itemize}
%
% \begin{tcblisting}{example=labels-all}
%     \begin{tikzpicture}[wire flow=north]
%       \node[box=1/0/2/0,
%         label={[swap, beside]south.1:$A$},
%         label={[beside]south.2:$B$}] (f) at (0,0) {f};
%       \node[box=1/0/2/0] (g) at (.4,1.6) {g};
%       \wires{
%         @ = { f.south.1, f.south.2,
%               ["$D$" beyond start] g.south.2 },
%         f.north.1 = ["$C$"] g.south.1,
%         g.north.1 = ["$E$" beyond end] @,
%       }
%     \end{tikzpicture}
% \end{tcblisting}
%
% The keys only choose the anchor of the label, from the direction of the wire there; \texttt{at end}, \texttt{at start} and \texttt{pos} still place it.
% On a path, they act as at the end the label is nearer.
% In the label of a node, they read the name of the anchor.
% Where a label touches a wire, swap it.
%
% \subsection{Dots}
%
% \begin{function}[added=2023-05-31,updated=2026-11-07]{/pgf/dot}
%
%   To split and join wires, use dots.
%   Wires attach to their sides and corners, and leave corners diagonally.
%   Style wires as you like.
%
%   \begin{tcblisting}{example=dots}
%     \begin{tikzpicture}
%       \node[box=0/1/0/2] (A) at ( 0,+1) {A};
%       \node[box=0/2/0/1] (B) at ( 0,-1) {B};
%       \node[dot] (x) at (+1, 0) {};
%       \node[dot] (y) at (-1, 0) {};
%       \wires[looseness=1.5,-stealth]{
%         A = { east.1 = x.north
%             , west.1 = @.west },
%         B = { east.1 = x.south
%             , east.2 = @.east },
%         y = { north = A.west.2
%             , south = B.west
%             , west = @.west },
%         x = { east = @.east },
%       }
%     \end{tikzpicture}
%   \end{tcblisting}
%
% \end{function}
%
% \begin{tcblisting}{example=bubble}
%     \begin{tikzpicture}
%       \node[dot] (c) at (0,0) {};
%       \node[dot] (m) at (0,1.2) {};
%       \wires{
%         c = { south = @.south
%             , north west = m.south west
%             , north east = m.south east },
%         m = { north = @.north },
%       }
%     \end{tikzpicture}
% \end{tcblisting}
%
% \subsection{\TikZ\ pitfalls}
%
% Some behaviors of \TikZ\ surprise in diagrams.
% \begin{itemize}
%   \item \emph{Styles of the picture.}
%     \cmd\wires\ draws its wires as edges, but with the default \texttt{every edge} of \TikZ: so only \texttt{every wire} and \texttt{every curve to} reach them, not \texttt{every to} or \texttt{every edge}.
%     A \texttt{to[wire]} or \texttt{edge[wire]} written by hand follows \TikZ.
%   \item \emph{Options of quotes.}
%     The options of a path end at the first \texttt{]} outside braces, so \texttt{"x"[blue]} fails.
%     Write \texttt{"x" blue}, or \texttt{"x" \{pos=1, below\}}.
%   \item \emph{Quotes and \pkg{babel}.}
%     Languages of \pkg{babel} that make \texttt{"} a shorthand break quotes: load the \texttt{babel} library of \TikZ.
%   \item \emph{Arrow tips.}
%     \cs{draw}\texttt{[->]} with edges adds a stray tip to the path itself: write \cs{path}\texttt{[->]} instead.
%   \item \emph{Nodes on a path.}
%     After a node on a path, the next \texttt{to} or edge starts at that node, not at the coordinate before it.
%   \item \emph{Wires in a row.}
%     In \texttt{(f.north.1) to[wire] (g.west.1) to[wire] (h.west.1)}, the second wire leaves \texttt{g.west.1} westwards, back the way the first came.
%   \item \emph{Feedback loops.}
%     A wire between two level ends of a node, as \texttt{f.east.1 = f.west.1}, leaves and enters straight, so \TikZ\ draws it as a line through the node.
%     Give it angles, as in \texttt{f = [out=60, in=120, looseness=2.5] f}.
%   \item \emph{Nodes drawn late.}
%     A node drawn after the first \cmd\wires\ of a picture can stick out of the boundary.
%     Then it overlaps the boundary, and its wires to \texttt{@} face out.
%   \item \emph{Markings.}
%     The markings of the \texttt{decorations.markings} library fail on wires of no length, with \enquote{Dimension too large}.
% \end{itemize}
%
% \subsection{Changes from earlier versions}
%
% Version 0.4.0 changes some behaviors of 0.3.0:
% \begin{itemize}
%   \item \texttt{every to} no longer styles the wires of \cmd\wires: use \texttt{every wire}.
%   \item An end without anchor is no longer an error.
%   \item The boundary faces inwards only towards the nodes it holds, as any node does.
% \end{itemize}
%
% Up to version 0.2.1, \cmd\wires\ took a third argument, listing the loose ends.
% Loose ends are now wires to the boundary, and \cmd\wires\ ignores a third argument.
%
% \end{documentation}
%
% ^^A=[ PACKAGE ]===============================================================
%
% \begin{implementation}
%
% \section{Implementation}
%
% Open the \pkg{DocStrip} guards and set the internal namespace prefix (as per \LaTeX3 \pkg{DocStrip} convention).
%    \begin{macrocode}
%<*package>
%<@@=stridi>
%    \end{macrocode}
%
% Require a \LaTeX\ with hooks that take arguments, and load the essential support (\pkg{expl3}, and \TikZ\ with the \texttt{fit} library that the boundary of \cmd\wires\ needs, and the \texttt{quotes} library that labels wires) \enquote{up-front}.
%
%    \begin{macrocode}
\NeedsTeXFormat{LaTeX2e}[2023/06/01]
\RequirePackage{expl3}[2023/06/05]
\RequirePackage{tikz}[2023/01/15]
\usetikzlibrary{fit}
\usetikzlibrary{quotes}
%    \end{macrocode}
%
% Identify the package and give the over all version information.
%    \begin{macrocode}
\ProvidesExplPackage
  {string-diagrams}
  {2026/11/12}
  {0.4.0}
  {Draw string diagrams using TikZ}
%    \end{macrocode}
%
%  \begin{macro}{
%    /pgf/box ports north,
%    /pgf/box ports east,
%    /pgf/box ports south,
%    /pgf/box ports west,
%    /pgf/box ports,
%  }
%   \changes{0.3.0}{2026/11/07}{counts start at zero, and the shortcut checks that it gets four}
%
%    Define high level keys to configure the number of ports on each side.
%    Boxes start with no ports.
%    The shortcut checks that it gets four counts, and sets the keys of the sides to their values; given nothing, it leaves them alone, so that the \texttt{box} style without counts keeps the ones in force.
%    \begin{macrocode}
\msg_new:nnn { string-diagrams } { port-counts }
  {
    Box~ports~take~four~counts,~for~the~north,~east,~south~and~west~sides,~
    as~in~1/2/1/2,~not~`#1'.
  }
\seq_new:N \l_@@_port_counts_seq
\pgfkeys{
  /pgf/box~ports~north/.initial=0,
  /pgf/box~ports~east/.initial=0,
  /pgf/box~ports~south/.initial=0,
  /pgf/box~ports~west/.initial=0,
  /pgf/box~ports/.code={
    \tl_if_blank:nF { #1 }
      {
        \seq_set_split:Nnn \l_@@_port_counts_seq { / } { #1 }
        \int_compare:nNnTF { \seq_count:N \l_@@_port_counts_seq } = { 4 }
          {
            \exp_args:Ne \pgfkeysalso
              {
                /pgf/box~ports~north = \seq_item:Nn \l_@@_port_counts_seq { 1 } ,
                /pgf/box~ports~east  = \seq_item:Nn \l_@@_port_counts_seq { 2 } ,
                /pgf/box~ports~south = \seq_item:Nn \l_@@_port_counts_seq { 3 } ,
                /pgf/box~ports~west  = \seq_item:Nn \l_@@_port_counts_seq { 4 } ,
              }
          }
          { \msg_error:nnn { string-diagrams } { port-counts } { #1 } }
      }
  },
}
%    \end{macrocode}
%  \end{macro}
%
%  \begin{macro}{\@@_intersect_hv_lines_through:NN}
%    Calculates the intersection of two lines parallel to axes passing through given points on the plane.
%    \begin{arguments}
%      \item Point through which the vertical line passes
%      \item Point through which the horizontal line passes
%    \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_intersect_hv_lines_through:NN {
  \pgfextractx { \pgf@xa } { #1 }
  \pgfextracty { \pgf@ya } { #2 }
  \pgfpoint { \pgf@xa } { \pgf@ya }
}
%    \end{macrocode}
%  \end{macro}
%
% \begin{macro}{\@@_subdivide_segment:nNNNNN}
%   Defines the port anchors of a side of a box: equally spaced points on a segment.
%   Anchors are defined globally, by the first box with enough ports on that side, so each one checks the port count of the box it is asked of.
%   The box is named in the error when an anchor of a named node is asked for, but not while placing a node by one of its own anchors.
%   \begin{arguments}
%     \item Side of the box
%     \item Points count
%     \item Point containing the x coordinate of the starting point
%     \item Point containing the y coordinate of the starting point
%     \item Point containing the x coordinate of the ending point
%     \item Point containing the y coordinate of the ending point
%   \end{arguments}
%    \begin{macrocode}
\msg_new:nnn { string-diagrams } { missing-port }
  { #1~has~no~port~#2.#3,~since~its~#2~side~has~#4. }
\cs_new_protected:Nn \@@_subdivide_segment:nNNNNN {
  \int_step_inline:nnnn { #2 } { -1 } { 1 } {
    \cs_if_exist:cTF
      { pgf@anchor@box@#1.##1 }
      { \prg_break: }
      { \prg_do_nothing: }
    \cs_new_nopar:cpn
      { pgf@anchor@box@#1.##1 }
      {
        \int_compare:nNnTF { ##1 } > { #2 }
          {
            \msg_error:nneeee { string-diagrams } { missing-port }
              {
                \cs_if_exist:NTF \pgfreferencednodename
                  { Box~`\pgfreferencednodename' }
                  { This~box }
              }
              { #1 } { ##1 } { #2 }
            \pgfpointlineattime
              { 0.5 }
              { \@@_intersect_hv_lines_through:NN { #3 } { #4 } }
              { \@@_intersect_hv_lines_through:NN { #5 } { #6 } }
          }
          {
            \pgfmathdivide
              { 2 * ##1 - 1 }
              { 2 * #2 }
            \pgfpointlineattime
              { \pgfmathresult }
              { \@@_intersect_hv_lines_through:NN { #3 } { #4 } }
              { \@@_intersect_hv_lines_through:NN { #5 } { #6 } }
          }
      }
  }
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[no-user-doc]{box}
%   \changes{0.2.0}{2023/06/12}{make ports configurable through \TikZ\ keys}
%   \changes{0.3.0}{2026/11/07}{ports a box doesn't have are an error}
%
%   Define a rectangular shape with configurable ports.
%    \begin{macrocode}
\pgfdeclareshape{box}{

  % Inherit all the structure of rectangle
  \inheritsavedanchors[from=rectangle]
  \clist_map_inline:nn
    {
      north~west,  north, north~east,
            west, center,       east,
        mid~west,    mid,   mid~east,
       base~west,   base,  base~east,
      south~west,  south, south~east,
    }
    { \inheritanchor[from=rectangle]{#1} }
  \inheritanchorborder[from=rectangle]
  \inheritbackgroundpath[from=rectangle]

  % Dump port counts into saved macros
  \savedmacro\portsnorth
    {\pgfmathtruncatemacro\portsnorth{\pgfkeysvalueof{/pgf/box~ports~north}}}
  \savedmacro\portseast
    {\pgfmathtruncatemacro\portseast{\pgfkeysvalueof{/pgf/box~ports~east}}}
  \savedmacro\portssouth
    {\pgfmathtruncatemacro\portssouth{\pgfkeysvalueof{/pgf/box~ports~south}}}
  \savedmacro\portswest
    {\pgfmathtruncatemacro\portswest{\pgfkeysvalueof{/pgf/box~ports~west}}}

  % Add ports definitions to shape definition
  \expandafter\pgfutil@g@addto@macro\csname pgf@sh@s@box\endcsname{
    \@@_subdivide_segment:nNNNNN { north } { \portsnorth }
      { \southwest } { \northeast } { \northeast } { \northeast }
    \@@_subdivide_segment:nNNNNN { east } { \portseast }
      { \northeast } { \northeast } { \northeast } { \southwest }
    \@@_subdivide_segment:nNNNNN { south } { \portssouth }
      { \southwest } { \southwest } { \northeast } { \southwest }
    \@@_subdivide_segment:nNNNNN { west } { \portswest }
      { \southwest } { \northeast } { \southwest } { \southwest }
  }

}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{/pgf/box}
%   \changes{0.2.0}{2023/06/12}{acts as a shortcut for setting port counts}
%   \changes{0.3.0}{2026/11/07}{without counts, keeps the ones in force}
%
%   Define style to draw boxes.
%    \begin{macrocode}
\ExplSyntaxOff
\tikzset{
  box/.default={},
  box/.style args={#1}{
    shape=box,
    draw,
    inner sep=.5em,
    minimum width=2em,
    minimum height=2em,
    execute at begin node=$,
    execute at end node=$,
    /pgf/box ports=#1,
  },
}
\ExplSyntaxOn
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{/pgf/dot}
%
%   Define style to draw dots.
%    \begin{macrocode}
\ExplSyntaxOff
\tikzset{
  dot/.style={
    shape=circle,
    fill,
    inner sep=0,
    minimum width=0.4em,
  },
}
\ExplSyntaxOn
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_clamp_to_node:n}
%   Moves the point in the internal \texttt{pgf} variables to the nearest point of the bounding rectangle of the given node, if it lies outside it.
%    \begin{macrocode}
\dim_new:N \l_@@_x_dim
\dim_new:N \l_@@_y_dim
\cs_new_protected:Nn \@@_clamp_to_node:n {
  \dim_set:Nn \l_@@_x_dim { \pgf@x }
  \dim_set:Nn \l_@@_y_dim { \pgf@y }
  \pgfpointanchor { #1 } { north~east }
  \dim_set:Nn \l_@@_x_dim { \dim_min:nn { \l_@@_x_dim } { \pgf@x } }
  \dim_set:Nn \l_@@_y_dim { \dim_min:nn { \l_@@_y_dim } { \pgf@y } }
  \pgfpointanchor { #1 } { south~west }
  \dim_set:Nn \l_@@_x_dim { \dim_max:nn { \l_@@_x_dim } { \pgf@x } }
  \dim_set:Nn \l_@@_y_dim { \dim_max:nn { \l_@@_y_dim } { \pgf@y } }
  \pgfpoint { \l_@@_x_dim } { \l_@@_y_dim }
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_calctarget:nnnn}
%   Performs the core calculation for \cmd{\@@_end_coordinate:NnnnnN}.
%   The result is stored in the internal \texttt{pgf} variables.
%   A wire from a corner to a side named in it, such as south west to south or west, ends offset along its diagonal.
%   A bend that would end past a corner of the boundary ends at that corner.
%   \begin{arguments}
%     \item source node
%     \item source anchor
%     \item target node (can be boundary)
%     \item target anchor
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_calctarget:nnnn {
  \pgfpointanchor{#1}{#2} \pgf@xa=\pgf@x \pgf@ya=\pgf@y
  \pgfpointanchor{#3}{#4} \pgf@xb=\pgf@x \pgf@yb=\pgf@y
  \regex_match_case:nn
  {
    { \A north ; north \  west \b } { \pgfpoint { \pgf@xa-.618*(\pgf@yb-\pgf@ya) } { \pgf@yb } }
    { \A north ; north \  east \b } { \pgfpoint { \pgf@xa+.618*(\pgf@yb-\pgf@ya) } { \pgf@yb } }
    { \A south ; south \  west \b } { \pgfpoint { \pgf@xa-.618*(\pgf@ya-\pgf@yb) } { \pgf@yb } }
    { \A south ; south \  east \b } { \pgfpoint { \pgf@xa+.618*(\pgf@ya-\pgf@yb) } { \pgf@yb } }
    { \A east  ; north \  east \b } { \pgfpoint { \pgf@xb } { \pgf@ya+.618*(\pgf@xb-\pgf@xa) } }
    { \A east  ; south \  east \b } { \pgfpoint { \pgf@xb } { \pgf@ya-.618*(\pgf@xb-\pgf@xa) } }
    { \A west  ; north \  west \b } { \pgfpoint { \pgf@xb } { \pgf@ya+.618*(\pgf@xa-\pgf@xb) } }
    { \A west  ; south \  west \b } { \pgfpoint { \pgf@xb } { \pgf@ya-.618*(\pgf@xa-\pgf@xb) } }
    { \A north ; north \b } { \pgfpoint { \pgf@xa } { \pgf@yb } }
    { \A north ; south \b } { \pgfpoint { \pgf@xa } { \pgf@yb } }
    { \A south ; north \b } { \pgfpoint { \pgf@xa } { \pgf@yb } }
    { \A south ; south \b } { \pgfpoint { \pgf@xa } { \pgf@yb } }
    { \A north ; east  \b } { \pgfpoint { \pgf@xa+.618*(\pgf@yb-\pgf@ya) } { \pgf@yb } }
    { \A south ; east  \b } { \pgfpoint { \pgf@xa-.618*(\pgf@yb-\pgf@ya) } { \pgf@yb } }
    { \A north ; west  \b } { \pgfpoint { \pgf@xa-.618*(\pgf@yb-\pgf@ya) } { \pgf@yb } }
    { \A south ; west  \b } { \pgfpoint { \pgf@xa+.618*(\pgf@yb-\pgf@ya) } { \pgf@yb } }
    { \A east  ; east  \b } { \pgfpoint { \pgf@xb } { \pgf@ya } }
    { \A east  ; west  \b } { \pgfpoint { \pgf@xb } { \pgf@ya } }
    { \A west  ; east  \b } { \pgfpoint { \pgf@xb } { \pgf@ya } }
    { \A west  ; west  \b } { \pgfpoint { \pgf@xb } { \pgf@ya } }
    { \A east  ; north \b } { \pgfpoint { \pgf@xb } { \pgf@ya+.618*(\pgf@xb-\pgf@xa) } }
    { \A west  ; north \b } { \pgfpoint { \pgf@xb } { \pgf@ya-.618*(\pgf@xb-\pgf@xa) } }
    { \A east  ; south \b } { \pgfpoint { \pgf@xb } { \pgf@ya-.618*(\pgf@xb-\pgf@xa) } }
    { \A west  ; south \b } { \pgfpoint { \pgf@xb } { \pgf@ya+.618*(\pgf@xb-\pgf@xa) } }
  } { #4 ; #2 }
  \@@_clamp_to_node:n { #3 }
}
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_angle_int}
%   Scratch variable for \cmd{\@@_anchor_to_angle:Nn}.
%    \begin{macrocode}
\int_new:N \l_@@_angle_int
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_anchor_to_angle:Nn}
%   Calculates the angle at which a wire leaves an anchor outwards, from its name.
%   Wires leave sides and their ports straight out, and corners diagonally; other anchors have no angle, and leave the variable empty.
%   An end that faces inwards turns this angle round (see \cmd{\@@_wire:}).
%   \begin{arguments}
%     \item token list variable
%     \item anchor
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_anchor_to_angle:Nn {
  \regex_match_case:nnTF
    {
      { \A north \  east \b } { \int_set:Nn \l_@@_angle_int {   45 } }
      { \A north \  west \b } { \int_set:Nn \l_@@_angle_int {  135 } }
      { \A south \  east \b } { \int_set:Nn \l_@@_angle_int {  -45 } }
      { \A south \  west \b } { \int_set:Nn \l_@@_angle_int { -135 } }
      { \A north \b } { \int_set:Nn \l_@@_angle_int {  90 } }
      { \A east  \b } { \int_set:Nn \l_@@_angle_int {   0 } }
      { \A south \b } { \int_set:Nn \l_@@_angle_int { -90 } }
      { \A west  \b } { \int_set:Nn \l_@@_angle_int { 180 } }
    }
    { #2 }
    { \tl_set:Nf #1 { \int_use:N \l_@@_angle_int } }
    { \tl_clear:N #1 }
}
%    \end{macrocode}
% \end{macro}
%

% \begin{variable}{\l_@@_oangle_tl,\l_@@_iangle_tl,\l_@@_source_tl,\l_@@_target_tl}
%   Initialize scratch variables required by \cmd{\@@_wire:}.
%    \begin{macrocode}
\tl_new:N \l_@@_oangle_tl
\tl_new:N \l_@@_iangle_tl
\tl_new:N \l_@@_source_tl
\tl_new:N \l_@@_target_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_draw_wire:nnnnn,\@@_draw_wire_edge:nnnnn,\@@_wire_coordinate:nn}
%   Draws a wire as an edge with the \texttt{wire} key, followed by the keys of the call, so that it is drawn as on any path of \TikZ: the key sets its angles and moves its ends on the boundary, and raises an error for each end without an angle.
%   The edge is an implementation detail, so it gets \TikZ's default \texttt{every edge}, which only draws it: the wires of \cmd\wires\ are styled by \texttt{every wire} and the keys of the call, not by the \texttt{every edge} of the picture.
%   The coordinates are expanded first, since \TikZ\ hands the target to the key as written; an end without an anchor is a bare node.
%   \TikZ\ resolves the start of a path while reading it, before any key, so a start on a node that doesn't exist, or at an anchor its node lacks, would get only the error of pgf, which for an anchor is about a mathematical function: the start is checked first, and such a wire is reported and not drawn.
%   \begin{arguments}
%     \item \TikZ\ keys
%     \item source node
%     \item source anchor
%     \item target node
%     \item target anchor
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_draw_wire:nnnnn {
  \cs_if_exist:cTF { pgf@sh@ns@ #2 }
    {
      \tl_if_empty:nTF { #3 }
        { \@@_draw_wire_edge:nnnnn { #1 } { #2 } { #3 } { #4 } { #5 } }
        {
          \@@_if_anchor:nnTF { #2 } { #3 }
            { \@@_draw_wire_edge:nnnnn { #1 } { #2 } { #3 } { #4 } { #5 } }
            { \@@_no_anchor:nn { #2 } { #3 } }
        }
    }
    {
      \msg_error:nne { string-diagrams } { end-no-node }
        { \@@_wire_coordinate:nn { #2 } { #3 } }
    }
}
\cs_new_protected:Nn \@@_draw_wire_edge:nnnnn {
  \use:e
    {
      \exp_not:N \path [ every~edge/.style = { draw } ]
        ( \@@_wire_coordinate:nn { #2 } { #3 } )
        edge [ wire, \exp_not:n { #1 } ]
        ( \@@_wire_coordinate:nn { #4 } { #5 } ) ;
    }
}
\cs_new:Nn \@@_wire_coordinate:nn
  { \exp_not:n { #1 } \tl_if_blank:nF { #2 } { . \exp_not:n { #2 } } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[pTF]{\@@_if_boundary_in_picture:}
%   Tests whether the current picture has a boundary, by the picture that pgf records for each node: node names outlive their pictures.
%    \begin{macrocode}
\prg_new_conditional:Nnn \@@_if_boundary_in_picture: { p, T, F, TF }
  {
    % NOTE: the space keeps docstrip from turning the @@ of the name of node @ into the module prefix.
    \bool_lazy_and:nnTF
      { \cs_if_exist_p:c { pgf@sh@pi@ @ } }
      { \str_if_eq_p:ee { \use:c { pgf@sh@pi@ @ } } { \pgfpictureid } }
      { \prg_return_true: }
      { \prg_return_false: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_draw_boundary:,\@@_draw_boundary_around:nn}
%   Creates the boundary node around the picture so far, or around the origin if nothing has been drawn yet (as pgf does, it tells an empty picture by its untouched bounds).
%   A picture gets a single boundary: later calls of \cmd\wires\ in it reuse the first one.
%   \begin{arguments}
%     \item north east corner to surround
%     \item south west corner to surround
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_draw_boundary: {
  \@@_if_boundary_in_picture:F
    {
      \dim_compare:nNnTF { \pgf@picmaxx } = { -16000pt }
        { \@@_draw_boundary_around:nn { 0pt, 0pt } { 0pt, 0pt } }
        {
          \@@_draw_boundary_around:nn
            { current~bounding~box.north~east }
            { current~bounding~box.south~west }
        }
    }
}
\cs_new_protected:Nn \@@_draw_boundary_around:nn {
  \node [%draw, box=2/2/0/1, dashed, red,
    name=@, node~contents={},
    fit={
      ([shift={(+2em,+1em)}]#1)
      ([shift={(-2em,-1em)}]#2)
    },
  ];
}
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_split_seq}
%   Scratch variable for \cmd{\@@_split_node_anchor:NNn}.
%    \begin{macrocode}
\seq_new:N \l_@@_split_seq
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_split_node_anchor:NNn}
%   Parses a token list into a node/anchor pair.
%   \begin{arguments}
%     \item token list variable to store node
%     \item token list variable to store anchor
%     \item token list to be parsed
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_split_node_anchor:NNn {
  \regex_extract_once:nnN { ([^\.]*) \.? (.*) } { #3 } \l_@@_split_seq
  \seq_pop:NN \l_@@_split_seq #1  % discard match
  \seq_pop:NN \l_@@_split_seq #1  % keep 1st matching group (node)
  \seq_pop:NN \l_@@_split_seq #2  % keep 2nd matching group (anchor)
}
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\g_@@_anchors_seq}
%   The anchors resolved in the outermost picture so far, pictures nested in it included, newest first: each as the picture it was resolved in, its node, its anchor and the two coordinates of its point.
%    \begin{macrocode}
\seq_new:N \g_@@_anchors_seq
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_record_anchor:nn}
%   Records an anchor that pgf has just resolved in a picture, with the point it gave.
%   \TikZ\ gives a to path its start as a point, not as the anchor that was written, so the \texttt{wire} key looks the point up in this record.
%   It runs after every \cs{pgfpointanchor}; \TikZ\ passes it node names wrapped in their prefix and suffix, and anchors that may hold macros, such as the variable of a \cs{foreach}, so both are expanded.
%   The record is cleared when an outermost picture begins: pgf defines \cs{pgfpictureid} only inside pictures, and restores it when a nested one ends.
%   \begin{arguments}
%     \item node
%     \item anchor
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_record_anchor:nn {
  \cs_if_exist:NT \pgfpictureid
    {
      \seq_gput_left:Ne \g_@@_anchors_seq
        {
          { \pgfpictureid } { #1 } { #2 }
          { \dim_use:N \pgf@x } { \dim_use:N \pgf@y }
        }
    }
}
\AddToHookWithArguments { cmd/pgfpointanchor/after }
  { \@@_record_anchor:nn {#1} {#2} }
\AddToHook { cmd/pgfpicture/before }
  { \cs_if_exist:NF \pgfpictureid { \seq_gclear:N \g_@@_anchors_seq } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_find_anchor:NNw,\@@_find_anchor_check:NNnnnnnnn}
%   Finds the newest anchor recorded at a point in the current picture, within 0.01pt, and stores its node and anchor; it stores nothing if there is none.
%   \begin{arguments}
%     \item token list variable to store the node
%     \item token list variable to store the anchor
%     \item the point, as \meta{x}\texttt{,}\meta{y}, ended by \cs{q_stop}
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Npn \@@_find_anchor:NNw #1#2#3 , #4 \q_stop {
  \tl_clear:N #1
  \tl_clear:N #2
  \seq_map_inline:Nn \g_@@_anchors_seq
    { \@@_find_anchor_check:NNnnnnnnn #1 #2 { #3 } { #4 } ##1 }
}
\cs_new_protected:Nn \@@_find_anchor_check:NNnnnnnnn {
  \bool_lazy_all:nT
    {
      { \str_if_eq_p:ee { #5 } { \pgfpictureid } }
      { \dim_compare_p:n { \dim_abs:n { #8 - ( #3 ) } < 0.01pt } }
      { \dim_compare_p:n { \dim_abs:n { #9 - ( #4 ) } < 0.01pt } }
    }
    {
      \tl_set:Nn #1 { #6 }
      \tl_set:Nn #2 { #7 }
      \seq_map_break:
    }
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[TF]{\@@_if_anchor:nn}
%   Tests whether a node has an anchor: a name its shape defines, or an angle, which pgf reads as a point of the border.
%   The ports of boxes are defined for all boxes at once, by the first box with enough ports on a side, so a port of a box is checked against the count of that box.
%    \begin{macrocode}
\prg_new_protected_conditional:Nnn \@@_if_anchor:nn { TF }
  {
    \@@_port_of_box:nn { #1 } { #2 }
    \seq_if_empty:NTF \l_@@_port_seq
      {
        \cs_if_exist:cTF { pgf@anchor@ \use:c { pgf@sh@ns@ #1 } @ #2 }
          { \prg_return_true: }
          {
            \regex_match:nnTF { \A [+\-]? [0-9.]+ \Z } { #2 }
              { \prg_return_true: }
              { \prg_return_false: }
          }
      }
      {
        \@@_side_ports:neN { #1 } { \seq_item:Nn \l_@@_port_seq { 2 } } \l_@@_ports_int
        \int_compare:nTF
          { 1 <= \seq_item:Nn \l_@@_port_seq { 3 } <= \l_@@_ports_int }
          { \prg_return_true: }
          { \prg_return_false: }
      }
  }
\prg_generate_conditional_variant:Nnn \@@_if_anchor:nn { nV , VV } { TF }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_side_ports:nnN,\@@_port_of_box:nn,\@@_no_anchor:nn}
%   \cmd{\@@_side_ports:nnN} counts the ports of a side of a node, none unless it is a box, from the counts saved in the node.
%   \cmd{\@@_port_of_box:nn} splits the anchor of a box into side and number, in \cs{l_@@_port_seq}, if it is a port, and leaves the sequence empty otherwise.
%   \cmd{\@@_no_anchor:nn} reports an anchor that a node doesn't have, as a missing port for a box asked for a port beyond its count, and pgf would otherwise fail to read it, with an error about a mathematical function.
%    \begin{macrocode}
\int_new:N \l_@@_ports_int
\int_new:N \g_@@_ports_int
\cs_new_protected:Nn \@@_side_ports:nnN {
  \int_zero:N #3
  \str_if_eq:eeT { \use:c { pgf@sh@ns@ #1 } } { box }
    {
      \group_begin:
        \use:c { pgf@sh@ma@ #1 }
        \int_gset:Nn \g_@@_ports_int { \use:c { ports #2 } }
      \group_end:
      \int_set_eq:NN #3 \g_@@_ports_int
    }
}
\msg_new:nnn { string-diagrams } { no-anchor }
  { Node~`#1'~has~no~anchor~`#2',~so~the~wire~is~not~drawn. }
\seq_new:N \l_@@_port_seq
\cs_new_protected:Nn \@@_port_of_box:nn {
  \seq_clear:N \l_@@_port_seq
  \str_if_eq:eeT { \use:c { pgf@sh@ns@ #1 } } { box }
    {
      \regex_extract_once:nnN { \A (north|east|south|west) \. ([0-9]+) \Z }
        { #2 } \l_@@_port_seq
    }
}
\cs_new_protected:Nn \@@_no_anchor:nn {
  \@@_port_of_box:nn { #1 } { #2 }
  \seq_if_empty:NTF \l_@@_port_seq
    { \msg_error:nnnn { string-diagrams } { no-anchor } { #1 } { #2 } }
    {
      \@@_side_ports:neN { #1 } { \seq_item:Nn \l_@@_port_seq { 2 } } \l_@@_ports_int
      \msg_error:nnneeV { string-diagrams } { missing-port } { Box~`#1' }
        { \seq_item:Nn \l_@@_port_seq { 2 } } { \seq_item:Nn \l_@@_port_seq { 3 } }
        \l_@@_ports_int
    }
}
\cs_generate_variant:Nn \@@_side_ports:nnN { ne }
\cs_generate_variant:Nn \msg_error:nnnnnn { nnneeV }
\cs_generate_variant:Nn \@@_no_anchor:nn { VV }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_wire_end:nNNNN,\@@_wire_end_side:NNN}
%   Finds the node and anchor of an end of a to path; the anchor is empty for a bare node.
%   An anchor must be on a side, so that the wire has an angle to leave or enter it at; otherwise the end raises an error, whatever the other end.
%   \TikZ\ gives an end as the text that was written, such as \texttt{g.south.1}, or as a point: the start always, unless it is a bare node, and the target when it is computed, as in \texttt{++(0,1)} or \texttt{([xshift=1em]g.south)}.
%   A point is looked up among the anchors resolved in the picture.
%   An end that is no anchor of a node, or a boundary from another picture, raises an error.
%   An end on a node that doesn't exist, or at an anchor its node doesn't have, raises an error only if \TikZ\ hasn't resolved the end, and so reported it, already: then it also sets \cs{l_@@_end_missing_bool}, so that the end isn't resolved later.
%   \TikZ\ resolves the start of a to path while reading the path, and a target only if it computes it.
%   \begin{arguments}
%     \item end, as \TikZ\ gives it
%     \item token list variable to store the node
%     \item token list variable to store the anchor
%     \item boolean variable, set true if the end is fine
%     \item boolean, true if \TikZ\ has already resolved the end
%   \end{arguments}
%    \begin{macrocode}
\msg_new:nnn { string-diagrams } { end-not-anchor }
  {
    Wire~end~`(#1)'~is~not~an~anchor~of~a~node,~so~the~wire~is~not~drawn:~
    wires~go~between~anchors,~as~in~(f.north)~to[wire]~(g.south).
  }
\msg_new:nnn { string-diagrams } { end-no-node }
  { Wire~end~`#1'~is~on~a~node~that~doesn't~exist,~so~the~wire~is~not~drawn. }
\msg_new:nnn { string-diagrams } { end-off-sides }
  {
    Wire~end~`#1.#2'~is~not~on~a~side,~so~the~wire~is~not~drawn:~
    wires~end~at~north,~east,~south~or~west,~or~at~ports.
  }
\msg_new:nnn { string-diagrams } { end-boundary-elsewhere }
  {
    Wire~end~`@.#1'~is~on~the~boundary~of~another~picture,~so~the~wire~is~not~drawn:~
    \iow_char:N\\wires~draws~the~boundary~of~this~picture.
  }
\bool_new:N \l_@@_end_missing_bool
\bool_new:N \l_@@_end_resolved_bool
\cs_new_protected:Nn \@@_wire_end:nNNNN {
  \bool_set_false:N #4
  \bool_set_eq:NN \l_@@_end_resolved_bool #5
  \regex_match:nnTF { \A [^,]* pt , [^,]* pt \Z } { #1 }
    {
      \bool_set_true:N \l_@@_end_resolved_bool
      \@@_find_anchor:NNw #2 #3 #1 \q_stop
    }
    { \@@_split_node_anchor:NNn #2 #3 { #1 } }
  \tl_if_empty:NTF #2
    { \msg_error:nnn { string-diagrams } { end-not-anchor } { #1 } }
    {
      \cs_if_exist:cTF { pgf@sh@ns@ #2 }
        {
          \bool_lazy_and:nnTF
            { \str_if_eq_p:Vn #2 { @ } }
            { ! \@@_if_boundary_in_picture_p: }
            { \msg_error:nnV { string-diagrams } { end-boundary-elsewhere } #3 }
            {
              \tl_if_empty:NTF #3
                { \bool_set_true:N #4 }
                {
                  \@@_if_anchor:VVTF #2 #3
                    { \@@_wire_end_side:NNN #2 #3 #4 }
                    {
                      \bool_if:NF \l_@@_end_resolved_bool
                        {
                          \@@_no_anchor:VV #2 #3
                          \bool_set_true:N \l_@@_end_missing_bool
                        }
                    }
                }
            }
        }
        {
          \bool_if:NF \l_@@_end_resolved_bool
            {
              \regex_match:nnTF { [,:] } { #1 }
                { \msg_error:nnn { string-diagrams } { end-not-anchor } { #1 } }
                {
                  \msg_error:nnn { string-diagrams } { end-no-node } { #1 }
                  \bool_set_true:N \l_@@_end_missing_bool
                }
            }
        }
    }
}
\tl_new:N \l_@@_end_angle_tl
\cs_new_protected:Nn \@@_wire_end_side:NNN {
  \@@_anchor_to_angle:NV \l_@@_end_angle_tl #2
  \tl_if_empty:NTF \l_@@_end_angle_tl
    { \msg_error:nnVV { string-diagrams } { end-off-sides } #1 #2 }
    { \bool_set_true:N #3 }
}
\cs_generate_variant:Nn \@@_wire_end:nNNNN { V }
\cs_generate_variant:Nn \@@_anchor_to_angle:Nn { NV }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_start_node_tl,\l_@@_start_anchor_tl,\l_@@_end_node_tl,\l_@@_end_anchor_tl,\l_@@_start_ok_bool,\l_@@_end_ok_bool,\l_@@_start_x_dim,\l_@@_start_y_dim,\l_@@_end_x_dim,\l_@@_end_y_dim,\l_@@_start_in_bool,\l_@@_end_in_bool}
%   Scratch variables for \cmd{\@@_wire:}: the two ends of a wire, whether each is fine, their points, and whether each faces inwards.
%    \begin{macrocode}
\tl_new:N \l_@@_start_node_tl
\tl_new:N \l_@@_start_anchor_tl
\tl_new:N \l_@@_end_node_tl
\tl_new:N \l_@@_end_anchor_tl
\bool_new:N \l_@@_start_ok_bool
\bool_new:N \l_@@_end_ok_bool
\dim_new:N \l_@@_start_x_dim
\dim_new:N \l_@@_start_y_dim
\dim_new:N \l_@@_end_x_dim
\dim_new:N \l_@@_end_y_dim
\bool_new:N \l_@@_start_in_bool
\bool_new:N \l_@@_end_in_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_point_of_end:nnNN}
%   Stores the point of an end: its anchor, or the center of a bare node.
%   \begin{arguments}
%     \item node
%     \item anchor, or nothing
%     \item dimension variable to store the x coordinate
%     \item dimension variable to store the y coordinate
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_point_of_end:nnNN {
  \tl_if_empty:nTF { #2 }
    { \pgfpointanchor { #1 } { center } }
    { \pgfpointanchor { #1 } { #2 } }
  \dim_set:Nn #3 { \pgf@x }
  \dim_set:Nn #4 { \pgf@y }
}
\cs_generate_variant:Nn \@@_point_of_end:nnNN { VVNN }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_start_west_dim,\l_@@_start_south_dim,\l_@@_start_east_dim,\l_@@_start_north_dim,\l_@@_end_west_dim,\l_@@_end_south_dim,\l_@@_end_east_dim,\l_@@_end_north_dim,\l_@@_start_holds_bool,\l_@@_end_holds_bool}
%   Scratch variables for \cmd{\@@_faces_inwards:}: the bounding boxes of the nodes of the two ends of a wire, and whether each holds the other.
%    \begin{macrocode}
\dim_new:N \l_@@_start_west_dim
\dim_new:N \l_@@_start_south_dim
\dim_new:N \l_@@_start_east_dim
\dim_new:N \l_@@_start_north_dim
\dim_new:N \l_@@_end_west_dim
\dim_new:N \l_@@_end_south_dim
\dim_new:N \l_@@_end_east_dim
\dim_new:N \l_@@_end_north_dim
\bool_new:N \l_@@_start_holds_bool
\bool_new:N \l_@@_end_holds_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_node_box:nNNNN}
%   Stores the bounding box of a node, from its south west and north east anchors, which lie on the outer edge of its stroke, since \TikZ's default \texttt{outer sep} is half the line width.
%   \begin{arguments}
%     \item node
%     \item dimension variable to store the west border
%     \item dimension variable to store the south border
%     \item dimension variable to store the east border
%     \item dimension variable to store the north border
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_node_box:nNNNN {
  \pgfpointanchor { #1 } { south~west }
  \dim_set:Nn #2 { \pgf@x }
  \dim_set:Nn #3 { \pgf@y }
  \pgfpointanchor { #1 } { north~east }
  \dim_set:Nn #4 { \pgf@x }
  \dim_set:Nn #5 { \pgf@y }
}
\cs_generate_variant:Nn \@@_node_box:nNNNN { V }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_if_box_within_p:nn,\@@_if_boxes_apart_p:}
%   \cmd{\@@_if_box_within_p:nn} tests whether the box of one end of a wire, \texttt{start} or \texttt{end}, lies within the box of the other, a shared border counting as inside within 0.01pt.
%   \cmd{\@@_if_boxes_apart_p:} tests whether the two boxes don't overlap, borders that touch included.
%    \begin{macrocode}
\prg_new_conditional:Nnn \@@_if_box_within:nn { p }
  {
    \bool_lazy_all:nTF
      {
        { \dim_compare_p:n { \use:c { l_@@_#2_west_dim } <= \use:c { l_@@_#1_west_dim } + 0.01pt } }
        { \dim_compare_p:n { \use:c { l_@@_#2_south_dim } <= \use:c { l_@@_#1_south_dim } + 0.01pt } }
        { \dim_compare_p:n { \use:c { l_@@_#2_east_dim } >= \use:c { l_@@_#1_east_dim } - 0.01pt } }
        { \dim_compare_p:n { \use:c { l_@@_#2_north_dim } >= \use:c { l_@@_#1_north_dim } - 0.01pt } }
      }
      { \prg_return_true: }
      { \prg_return_false: }
  }
\prg_new_conditional:Nnn \@@_if_boxes_apart: { p }
  {
    \bool_lazy_any:nTF
      {
        { \dim_compare_p:n { \l_@@_start_east_dim <= \l_@@_end_west_dim + 0.01pt } }
        { \dim_compare_p:n { \l_@@_end_east_dim <= \l_@@_start_west_dim + 0.01pt } }
        { \dim_compare_p:n { \l_@@_start_north_dim <= \l_@@_end_south_dim + 0.01pt } }
        { \dim_compare_p:n { \l_@@_end_north_dim <= \l_@@_start_south_dim + 0.01pt } }
      }
      { \prg_return_true: }
      { \prg_return_false: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_faces_inwards:}
%   Tells which ends of a wire face inwards, from the boxes of their nodes.
%   The end on a node that holds the other faces inwards.
%   Otherwise both ends face out: the nodes are apart, or they overlap, or have the same box, and then a bare end is an error, since neither node is the outer one, and the wire is not drawn.
%   A wire from a node to itself faces inwards at both ends on the boundary \texttt{@}, which it crosses, and outwards on any other node, which it loops round.
%    \begin{macrocode}
\msg_new:nnn { string-diagrams } { bare-overlap }
  {
    Wire~from~`#1'~to~`#2'~has~an~end~without~anchor,~and~the~two~nodes~overlap,~
    so~it~is~not~drawn:~give~both~ends~anchors.
  }
\cs_new_protected:Nn \@@_faces_inwards: {
  \bool_set_false:N \l_@@_start_in_bool
  \bool_set_false:N \l_@@_end_in_bool
  \str_if_eq:VVTF \l_@@_start_node_tl \l_@@_end_node_tl
    {
      \str_if_eq:VnT \l_@@_start_node_tl { @ }
        {
          \bool_set_true:N \l_@@_start_in_bool
          \bool_set_true:N \l_@@_end_in_bool
        }
    }
    {
      \@@_node_box:VNNNN \l_@@_start_node_tl
        \l_@@_start_west_dim \l_@@_start_south_dim \l_@@_start_east_dim \l_@@_start_north_dim
      \@@_node_box:VNNNN \l_@@_end_node_tl
        \l_@@_end_west_dim \l_@@_end_south_dim \l_@@_end_east_dim \l_@@_end_north_dim
      \bool_set:Nn \l_@@_start_holds_bool { \@@_if_box_within_p:nn { end } { start } }
      \bool_set:Nn \l_@@_end_holds_bool { \@@_if_box_within_p:nn { start } { end } }
      \bool_if:nTF { \l_@@_start_holds_bool && ! \l_@@_end_holds_bool }
        { \bool_set_true:N \l_@@_start_in_bool }
        {
          \bool_if:nTF { \l_@@_end_holds_bool && ! \l_@@_start_holds_bool }
            { \bool_set_true:N \l_@@_end_in_bool }
            {
              \bool_lazy_or:nnF
                { \@@_if_boxes_apart_p: }
                { ! \tl_if_empty_p:N \l_@@_start_anchor_tl && ! \tl_if_empty_p:N \l_@@_end_anchor_tl }
                {
                  \msg_error:nnVV { string-diagrams } { bare-overlap }
                    \l_@@_start_node_tl \l_@@_end_node_tl
                  \bool_set_false:N \l_@@_start_ok_bool
                }
            }
        }
    }
}
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_flow_tl}
%   The flow of the wires, set by \texttt{wire flow}: a side, or nothing.
%    \begin{macrocode}
\tl_new:N \l_@@_flow_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_set_flow:n}
%   Sets the flow of the wires: \texttt{north}, \texttt{east}, \texttt{south} or \texttt{west}, or \texttt{up}, \texttt{right}, \texttt{down} or \texttt{left}, as \TikZ's \texttt{grow} takes, or \texttt{none} to have none.
%    \begin{macrocode}
\msg_new:nnn { string-diagrams } { flow-value }
  {
    Wire~flow~`#1'~is~not~a~direction:~
    use~north,~east,~south,~west,~up,~right,~down,~left~or~none.
  }
\cs_new_protected:Nn \@@_set_flow:n {
  \str_case:nnF { #1 }
    {
      { north } { \tl_set:Nn \l_@@_flow_tl { north } }
      { east } { \tl_set:Nn \l_@@_flow_tl { east } }
      { south } { \tl_set:Nn \l_@@_flow_tl { south } }
      { west } { \tl_set:Nn \l_@@_flow_tl { west } }
      { up } { \tl_set:Nn \l_@@_flow_tl { north } }
      { right } { \tl_set:Nn \l_@@_flow_tl { east } }
      { down } { \tl_set:Nn \l_@@_flow_tl { south } }
      { left } { \tl_set:Nn \l_@@_flow_tl { west } }
      { none } { \tl_clear:N \l_@@_flow_tl }
    }
    { \msg_error:nnn { string-diagrams } { flow-value } { #1 } }
}
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_dir_int,\l_@@_side_tl}
%   Scratch variables for the inference of anchors: the direction of travel of a wire, in degrees, and a side.
%    \begin{macrocode}
\int_new:N \l_@@_dir_int
\tl_new:N \l_@@_side_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_side_of:n,\@@_opposite:n,\@@_side_anchor:NNnn}
%   Turn directions into sides and sides into anchors.
%   \cmd{\@@_side_of:n} expands to the side a direction, a multiple of 90 degrees, points to, and \cmd{\@@_opposite:n} to the side opposite a side.
%   \cmd{\@@_side_anchor:NNnn} gives the anchor of a bare end on a side: the side without ports, its only port, and an error asking which if it has more, which leaves the anchor empty and stops the wire.
%    \begin{macrocode}
\cs_new:Nn \@@_side_of:n
  {
    \int_case:nn { \int_mod:nn { #1 + 720 } { 360 } }
      { { 0 } { east } { 90 } { north } { 180 } { west } { 270 } { south } }
  }
\cs_new:Nn \@@_opposite:n
  { \str_case:nn { #1 } { { north } { south } { south } { north } { east } { west } { west } { east } } }
\msg_new:nnn { string-diagrams } { bare-many-ports }
  {
    Wire~end~`#1'~has~no~anchor,~and~its~#2~side~has~#3~ports,~so~the~wire~is~not~drawn:~
    name~one,~as~in~#1.#2.1.
  }
\cs_new_protected:Nn \@@_side_anchor:NNnn {
  \@@_side_ports:nnN { #3 } { #4 } \l_@@_ports_int
  \int_case:nnF { \l_@@_ports_int }
    {
      { 0 } { \tl_set:Nn #1 { #4 } }
      { 1 } { \tl_set:Nn #1 { #4.1 } }
    }
    {
      \msg_error:nnnnV { string-diagrams } { bare-many-ports } { #3 } { #4 } \l_@@_ports_int
      \bool_set_false:N #2
    }
}
\cs_generate_variant:Nn \@@_side_anchor:NNnn { NNVe }
\cs_generate_variant:Nn \msg_error:nnnnn { nnnnV }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_snap:,\@@_both_bare_direction:,\@@_infer_anchors:}
%   Infer the anchors of bare ends from the direction a wire travels in.
%   With one end anchored, the wire travels as it leaves that end, or as it enters it; a diagonal, from a corner, is snapped to the axis of the flow, if set, else to the axis that separates the two ends more.
%   With both ends bare, it travels along the flow, if set, else along the axis that separates the centers of the two nodes more, and nodes with the same center are an error, as is a wire from a node to itself.
%   Then a bare start is the side the wire leaves through, and a bare target the side it enters through; an end that faces inwards uses the opposite side, so that a wire from a node around the other starts behind it, and a wire to that node ends ahead of it.
%    \begin{macrocode}
\msg_new:nnn { string-diagrams } { bare-no-direction }
  {
    Wire~from~`#1'~to~`#2'~has~no~anchors,~and~their~centers~coincide,~so~it~is~not~drawn:~
    give~an~end~an~anchor,~or~set~a~wire~flow.
  }
\msg_new:nnn { string-diagrams } { bare-self-no-direction }
  {
    Wire~from~`#1'~to~itself~has~no~anchors,~so~it~is~not~drawn:~
    give~an~end~an~anchor,~or~set~a~wire~flow.
  }
\cs_new_protected:Nn \@@_snap: {
  \int_compare:nNnF { \int_mod:nn { \l_@@_dir_int + 720 } { 90 } } = { 0 }
    {
      \bool_if:nTF
        {
          \tl_if_empty_p:N \l_@@_flow_tl
          && \dim_compare_p:n
            {
              \dim_abs:n { \l_@@_end_x_dim - \l_@@_start_x_dim }
              >= \dim_abs:n { \l_@@_end_y_dim - \l_@@_start_y_dim }
            }
          || \str_if_eq_p:Vn \l_@@_flow_tl { east }
          || \str_if_eq_p:Vn \l_@@_flow_tl { west }
        }
        { \int_set:Nn \l_@@_dir_int { \fp_eval:n { cosd ( \l_@@_dir_int ) > 0 ? 0 : 180 } } }
        { \int_set:Nn \l_@@_dir_int { \fp_eval:n { sind ( \l_@@_dir_int ) > 0 ? 90 : 270 } } }
    }
}
\cs_new_protected:Nn \@@_both_bare_direction: {
  \tl_if_empty:NTF \l_@@_flow_tl
    {
      \bool_if:nTF
        {
          \dim_compare_p:n { \dim_abs:n { \l_@@_end_x_dim - \l_@@_start_x_dim } < 0.1pt }
          && \dim_compare_p:n { \dim_abs:n { \l_@@_end_y_dim - \l_@@_start_y_dim } < 0.1pt }
        }
        {
          \str_if_eq:VVTF \l_@@_start_node_tl \l_@@_end_node_tl
            { \msg_error:nnV { string-diagrams } { bare-self-no-direction } \l_@@_start_node_tl }
            {
              \msg_error:nnVV { string-diagrams } { bare-no-direction }
                \l_@@_start_node_tl \l_@@_end_node_tl
            }
          \bool_set_false:N \l_@@_start_ok_bool
        }
        {
          \dim_compare:nNnTF
            { \dim_abs:n { \l_@@_end_x_dim - \l_@@_start_x_dim } }
            >
            { \dim_abs:n { \l_@@_end_y_dim - \l_@@_start_y_dim } }
            {
              \dim_compare:nNnTF { \l_@@_end_x_dim } > { \l_@@_start_x_dim }
                { \int_set:Nn \l_@@_dir_int { 0 } }
                { \int_set:Nn \l_@@_dir_int { 180 } }
            }
            {
              \dim_compare:nNnTF { \l_@@_end_y_dim } > { \l_@@_start_y_dim }
                { \int_set:Nn \l_@@_dir_int { 90 } }
                { \int_set:Nn \l_@@_dir_int { 270 } }
            }
        }
    }
    {
      \int_set:Nn \l_@@_dir_int
        { \str_case:Vn \l_@@_flow_tl { { east } { 0 } { north } { 90 } { west } { 180 } { south } { 270 } } }
    }
}
\cs_new_protected:Nn \@@_infer_anchors: {
  \tl_if_empty:NTF \l_@@_start_anchor_tl
    {
      \tl_if_empty:NTF \l_@@_end_anchor_tl
        { \@@_both_bare_direction: }
        {
          \int_set:Nn \l_@@_dir_int
            { \l_@@_iangle_tl \bool_if:NF \l_@@_end_in_bool { + 180 } }
        }
    }
    {
      \int_set:Nn \l_@@_dir_int
        { \l_@@_oangle_tl \bool_if:NT \l_@@_start_in_bool { + 180 } }
    }
  \bool_if:NT \l_@@_start_ok_bool
    {
      \@@_snap:
      \tl_if_empty:NT \l_@@_start_anchor_tl
        {
          \tl_set:Ne \l_@@_side_tl { \@@_side_of:n { \l_@@_dir_int } }
          \bool_if:NT \l_@@_start_in_bool
            { \tl_set:Ne \l_@@_side_tl { \@@_opposite:V \l_@@_side_tl } }
          \@@_side_anchor:NNVV \l_@@_start_anchor_tl \l_@@_start_ok_bool
            \l_@@_start_node_tl \l_@@_side_tl
        }
      \tl_if_empty:NT \l_@@_end_anchor_tl
        {
          \tl_set:Ne \l_@@_side_tl { \@@_side_of:n { \l_@@_dir_int } }
          \bool_if:NF \l_@@_end_in_bool
            { \tl_set:Ne \l_@@_side_tl { \@@_opposite:V \l_@@_side_tl } }
          \@@_side_anchor:NNVV \l_@@_end_anchor_tl \l_@@_end_ok_bool
            \l_@@_end_node_tl \l_@@_side_tl
        }
    }
}
\cs_generate_variant:Nn \@@_opposite:n { V }
\cs_generate_variant:Nn \@@_side_anchor:NNnn { NNVV }
\cs_generate_variant:Nn \str_case:nn { V }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_end_coordinate:NnnnnN}
%   Calculates where a wire ends at an anchor: the anchor itself, unless the end faces inwards and the anchor is a side without a port, which the wire reaches straight across from the other end, as \cmd{\@@_calctarget:nnnn} finds.
%   The result is stored in the given token list variable, as a point or as \meta{node}\texttt{.}\meta{anchor}.
%   \begin{arguments}
%     \item token list variable
%     \item node of the end
%     \item anchor of the end
%     \item node of the other end
%     \item anchor of the other end
%     \item boolean, true if the end faces inwards
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_end_coordinate:NnnnnN {
  \bool_lazy_and:nnTF { #6 } { \@@_if_side_name_p:n { #3 } }
    {
      \@@_calctarget:nnnn { #4 } { #5 } { #2 } { #3 }
      \tl_set:Ne #1 { \dim_use:N \pgf@x , \dim_use:N \pgf@y }
    }
    { \tl_set:Nn #1 { #2.#3 } }
}
\cs_generate_variant:Nn \@@_end_coordinate:NnnnnN { NVVVVN }
\prg_new_conditional:Nnn \@@_if_side_name:n { p }
  {
    \str_case:nnF { #1 }
      {
        { north } { \prg_return_true: } { east } { \prg_return_true: }
        { south } { \prg_return_true: } { west } { \prg_return_true: }
      }
      { \prg_return_false: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_wire_bool,\l_@@_wire_drawn_bool,\l_@@_start_moved_bool,\l_@@_given_target_tl,\s_@@_auto}
%   Scratch variables for the \texttt{wire} key: whether the to path at hand is a wire, whether it is drawn, whether its start moves, and its target as \TikZ\ gave it; and the scan mark that stands for the angles the key computes.
%    \begin{macrocode}
\bool_new:N \l_@@_wire_bool
\bool_new:N \l_@@_wire_drawn_bool
\bool_new:N \l_@@_start_moved_bool
\tl_new:N \l_@@_given_target_tl
\scan_new:N \s_@@_auto
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_wire:,\@@_wire_ends:,\@@_wire_path:}
%   Turn a to path into a wire, just before \TikZ\ computes its curve, after all the keys of the path, so that \texttt{wire flow} counts wherever it is set.
%   \TikZ\ computes a curve bent by \texttt{bend left} or \texttt{bend right} apart, from angles relative to the line between its ends, so both computations are hooked: the ends are resolved alike, and a bent wire keeps its angles.
%   \cmd{\@@_wire:} finds the ends, infers the anchors of bare ends, and sets the angles that \TikZ's own \texttt{out} and \texttt{in} draw the curve with, unless keys after \texttt{wire} have set them.
%   An end faces inwards when its node holds the other, as \cmd{\@@_faces_inwards:} tells: then its angle turns round, and a side without a port is reached straight across, as on the boundary.
%   An end that becomes a point, or a bare start, moves the start, which \cmd{\@@_wire_path:} does after the computation; it also turns a wire that isn't drawn into a move to its target, or into nothing if its target is on a node that doesn't exist, which \TikZ\ would report again, and drops its labels, the nodes \TikZ\ would place along it.
%   Each wrong end raises an error.
%    \begin{macrocode}
\cs_new_protected:Nn \@@_wire: {
  \bool_set_false:N \l_@@_end_missing_bool
  \bool_set_false:N \l_@@_start_moved_bool
  \tl_set_eq:NN \l_@@_given_target_tl \tikztotarget
  \@@_wire_end:VNNNN \tikztostart
    \l_@@_start_node_tl \l_@@_start_anchor_tl \l_@@_start_ok_bool \c_true_bool
  \@@_wire_end:VNNNN \tikztotarget
    \l_@@_end_node_tl \l_@@_end_anchor_tl \l_@@_end_ok_bool \c_false_bool
  \bool_lazy_and:nnT { \l_@@_start_ok_bool } { \l_@@_end_ok_bool } { \@@_wire_ends: }
  \bool_lazy_and:nnTF { \l_@@_start_ok_bool } { \l_@@_end_ok_bool }
    {
      \bool_set_true:N \l_@@_wire_drawn_bool
      \tl_set_eq:NN \tikztotarget \l_@@_target_tl
      \str_if_eq:eeT { \exp_not:o { \tikz@to@out } } { \exp_not:N \s_@@_auto }
        { \cs_set:Npe \tikz@to@out { \tl_use:N \l_@@_oangle_tl } }
      \str_if_eq:eeT { \exp_not:o { \tikz@to@in } } { \exp_not:N \s_@@_auto }
        { \cs_set:Npe \tikz@to@in { \tl_use:N \l_@@_iangle_tl } }
      \bool_if:NT \l_@@_start_moved_bool
        { \tl_set_eq:NN \tikztostart \l_@@_source_tl }
    }
    {
      \bool_set_false:N \l_@@_wire_drawn_bool
      \cs_set:Npn \tikz@to@out { 0 }
      \cs_set:Npn \tikz@to@in { 180 }
      \tl_set:Nn \tikztostart { 0pt , 0pt }
      \tl_set:Nn \tikztotarget { 0pt , 0pt }
    }
}
\cs_new_protected:Nn \@@_wire_ends: {
  \@@_point_of_end:VVNN \l_@@_start_node_tl \l_@@_start_anchor_tl
    \l_@@_start_x_dim \l_@@_start_y_dim
  \@@_point_of_end:VVNN \l_@@_end_node_tl \l_@@_end_anchor_tl
    \l_@@_end_x_dim \l_@@_end_y_dim
  \@@_faces_inwards:
  \bool_set:Nn \l_@@_start_moved_bool { \tl_if_empty_p:N \l_@@_start_anchor_tl }
  \@@_anchor_to_angle:NV \l_@@_oangle_tl \l_@@_start_anchor_tl
  \@@_anchor_to_angle:NV \l_@@_iangle_tl \l_@@_end_anchor_tl
  \bool_lazy_and:nnT
    { \l_@@_start_ok_bool }
    { \tl_if_empty_p:N \l_@@_start_anchor_tl || \tl_if_empty_p:N \l_@@_end_anchor_tl }
    {
      \@@_infer_anchors:
      \@@_anchor_to_angle:NV \l_@@_oangle_tl \l_@@_start_anchor_tl
      \@@_anchor_to_angle:NV \l_@@_iangle_tl \l_@@_end_anchor_tl
    }
  \bool_lazy_and:nnT { \l_@@_start_ok_bool } { \l_@@_end_ok_bool }
    {
      \bool_if:NT \l_@@_start_in_bool
        { \tl_set:Ne \l_@@_oangle_tl { \int_eval:n { \l_@@_oangle_tl - 180 } } }
      \bool_if:NT \l_@@_end_in_bool
        { \tl_set:Ne \l_@@_iangle_tl { \int_eval:n { \l_@@_iangle_tl - 180 } } }
      \@@_end_coordinate:NVVVVN \l_@@_source_tl
        \l_@@_start_node_tl \l_@@_start_anchor_tl
        \l_@@_end_node_tl \l_@@_end_anchor_tl \l_@@_start_in_bool
      \@@_end_coordinate:NVVVVN \l_@@_target_tl
        \l_@@_end_node_tl \l_@@_end_anchor_tl
        \l_@@_start_node_tl \l_@@_start_anchor_tl \l_@@_end_in_bool
      \bool_lazy_and:nnT { \l_@@_start_in_bool } { \@@_if_side_name_p:V \l_@@_start_anchor_tl }
        { \bool_set_true:N \l_@@_start_moved_bool }
    }
}
\cs_new_protected:Nn \@@_wire_path: {
  \bool_if:NTF \l_@@_wire_drawn_bool
    {
      \bool_if:NT \l_@@_start_moved_bool
        { \tl_put_left:Ne \tikz@computed@path { ( \exp_not:V \tikztostart ) ~ } }
    }
    {
      \cs_set:Npn \tikztonodes { }
      \bool_if:NTF \l_@@_end_missing_bool
        { \tl_clear:N \tikz@computed@path }
        { \tl_set:Ne \tikz@computed@path { ( \exp_not:V \l_@@_given_target_tl ) } }
    }
}
\prg_generate_conditional_variant:Nnn \@@_if_side_name:n { V } { p }
\AddToHook { cmd/tikz@to@compute/before }
  { \bool_if:NT \l_@@_wire_bool { \@@_wire: } }
\AddToHook { cmd/tikz@to@compute/after }
  { \bool_if:NT \l_@@_wire_bool { \@@_wire_path: } }
\AddToHook { cmd/tikz@to@compute@relative/before }
  { \bool_if:NT \l_@@_wire_bool { \@@_wire: } }
\AddToHook { cmd/tikz@to@compute@relative/after }
  { \bool_if:NT \l_@@_wire_bool { \@@_wire_path: } }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{/tikz/wire,/tikz/every wire,/tikz/wire flow}
%   Defines the key that draws a to path or an edge as a wire, the style it applies after it, as \texttt{every edge} or \texttt{every to} would, and the key that sets the flow of the wires.
%   The \texttt{wire} key only marks the path as a wire, draws it as a curve, and leaves its angles to be computed, unless keys after it set them; it undoes a \texttt{bend left} or \texttt{bend right} before it, as it overrides \texttt{out} and \texttt{in}.
%    \begin{macrocode}
\cs_new_protected:Nn \@@_wire_key: {
  \bool_set_true:N \l_@@_wire_bool
  \cs_set:Npn \tikz@to@out { \s_@@_auto }
  \cs_set:Npn \tikz@to@in { \s_@@_auto }
  \tikz@to@relativefalse
  \tikz@to@switch@on
}
\tikzset{
  every~wire/.style = { },
  wire/.code = { \@@_wire_key: \pgfkeysalso { every~wire } },
  wire~flow/.code = { \@@_set_flow:n { #1 } },
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_compass:n,\@@_side_turn:nn}
%   \cmd{\@@_compass:n} expands to the compass anchor of a direction, in degrees, a multiple of 45.
%   \cmd{\@@_side_turn:nn} expands to its first argument for a label to the left of a path, and to its second for one to the right, as \texttt{swap} sets.
%    \begin{macrocode}
\cs_new:Nn \@@_compass:n
  {
    \int_case:nn { \int_mod:nn { #1 + 720 } { 360 } }
      {
        { 0 } { east } { 45 } { north~east } { 90 } { north } { 135 } { north~west }
        { 180 } { west } { 225 } { south~west } { 270 } { south } { 315 } { south~east }
      }
  }
\cs_new:Nn \@@_side_turn:nn
  { \str_if_eq:VnTF \tikz@auto@anchor@direction { left } { #1 } { #2 } }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_label_fp,\l_@@_label_angle_tl}
%   Scratch variables for the placement of labels: a direction of travel, and the angle of the anchor a label belongs to.
%    \begin{macrocode}
\fp_new:N \l_@@_label_fp
\tl_new:N \l_@@_label_angle_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_label_key:nnnnnn,\@@_label_on_path:nnnn}
%   Choose the anchor of a label, as \TikZ's \texttt{auto} does, but turned from the direction of travel by the given angles, the first of each pair to the left and the second to the right.
%   For a label on a path, the direction is the tangent of the path at the label, which \TikZ\ reads when it places the label, as for \texttt{auto}, and the turn depends on which end of the path the label is nearer.
%   For a label of a node, given with \texttt{label}, the direction is that of a wire leaving the anchor the label belongs to, from the anchor's name; an anchor without an angle, such as \texttt{center}, leaves the placement to \TikZ.
%   The label is then placed at the anchor itself, and its own anchor is a macro, expanded when the node is made.
%   In both cases the side is read once all the keys of the label are set, so a \texttt{swap} after the key counts.
%   \begin{arguments}
%     \item turn near the start of a path, to the left
%     \item turn near the start of a path, to the right
%     \item turn near the end of a path, to the left
%     \item turn near the end of a path, to the right
%     \item turn for the label of a node, to the left
%     \item turn for the label of a node, to the right
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_label_key:nnnnnn {
  \cs_set:Npn \tikz@do@auto@anchor
    { \@@_label_on_path:nnnn { #1 } { #2 } { #3 } { #4 } }
  \str_if_eq:VnT \tikz@anchor { @auto }
    {
      \@@_anchor_to_angle:NV \l_@@_label_angle_tl \tikz@label@angle
      \tl_if_empty:NF \l_@@_label_angle_tl
        {
          \pgfkeysalso { label~distance = 0pt }
          \cs_set:Npe \tikz@anchor
            {
              \exp_not:N \@@_compass:n
                { \l_@@_label_angle_tl + \exp_not:N \@@_side_turn:nn { #5 } { #6 } }
            }
        }
    }
}
\cs_new_protected:Nn \@@_label_on_path:nnnn {
  \tikz@auto@pre
    \fp_set:Nn \l_@@_label_fp
      { round ( atand ( \dim_to_fp:n { \pgf@y } , \dim_to_fp:n { \pgf@x } ) / 45 ) * 45 }
    \cs_set:Npe \tikz@anchor
      {
        \@@_compass:n
          {
            \fp_to_int:N \l_@@_label_fp
            + \fp_compare:nNnTF { \tikz@time } < { 0.5 }
              { \@@_side_turn:nn { #1 } { #2 } }
              { \@@_side_turn:nn { #3 } { #4 } }
          }
      }
  \tikz@auto@post
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{/tikz/beyond,/tikz/beside,/tikz/beyond end,/tikz/beyond start,/tikz/beside end,/tikz/beside start}
%   Define the keys that place labels of wires and ports, as \TikZ's \texttt{auto} and \texttt{swap} do, but past the end of a wire or beside it, clear of the box.
%   On a path, \texttt{beyond} faces the label back along the path, past its end, and \texttt{beside} turns it 45 degrees further than \texttt{auto} does: away from the box the wire enters, near its end, or leaves, near its start.
%   In \texttt{label}, they place the label of a port as at the start of a wire leaving it: straight out, or beside the wire.
%   The others add the position, as \texttt{at end} and \texttt{at start}.
%    \begin{macrocode}
\tikzset{
  beyond/.code = { \@@_label_key:nnnnnn { 0 } { 0 } { 180 } { 180 } { 180 } { 180 } },
  beside/.code = { \@@_label_key:nnnnnn { -135 } { 135 } { -45 } { 45 } { -135 } { 135 } },
  beyond~end/.style = { at~end, beyond },
  beyond~start/.style = { at~start, beyond },
  beside~end/.style = { at~end, beside },
  beside~start/.style = { at~start, beside },
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[no-user-doc]{\exp_args:Nnnnoo}
%   This helper is required by \cmd{\@@_adj_wire:nNnnn}.
%    \begin{macrocode}
\exp_args_generate:n { nnnoo }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_adj_node_tl,\l_@@_adj_anchor_tl,\l_@@_target_node_tl,\l_@@_target_anchor_tl}
%   Scratch variables for the parser of \cmd\wires: the two parts of an end, and of a target.
%    \begin{macrocode}
\tl_new:N \l_@@_adj_node_tl
\tl_new:N \l_@@_adj_anchor_tl
\tl_new:N \l_@@_target_node_tl
\tl_new:N \l_@@_target_anchor_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\s_@@_keys,\s_@@_stop}
%   Scan marks for the parser of \cmd\wires: the first marks the keys of a bracketed group, and the second ends a loop.
%    \begin{macrocode}
\scan_new:N \s_@@_keys
\scan_new:N \s_@@_stop
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_protect_brackets:Nn,\@@_protect_brackets_out:Nw,\@@_protect_brackets_in:Nw,\@@_protect_brackets_skip:w,\@@_before_closing:w}
%   Replaces each bracketed group that isn't in braces by \cs{s_@@_keys} followed by its keys in braces, so that their commas survive the split of a list into items, and they can't be mistaken for a group in braces.
%   As in \TikZ, a group ends at the first closing bracket outside braces.
%   Each piece is read after a quark, so that a piece in braces keeps them.
%   A bracket outside braces without its match ends the list with an error.
%   \begin{arguments}
%     \item token list variable to store the result
%     \item token list
%   \end{arguments}
%    \begin{macrocode}
\msg_new:nnn { string-diagrams } { unclosed-bracket }
  { A~`['~in~the~wires~has~no~matching~`]',~so~the~rest~of~the~list~is~ignored. }
\msg_new:nnn { string-diagrams } { unopened-bracket }
  { A~`]'~in~the~wires~has~no~matching~`[',~so~the~rest~of~the~list~is~ignored. }
\cs_new_protected:Nn \@@_protect_brackets:Nn {
  \tl_clear:N #1
  \@@_protect_brackets_out:Nw #1 \q_mark #2 [ \q_stop ]
}
\cs_generate_variant:Nn \@@_protect_brackets:Nn { NV }
\cs_new_protected:Npn \@@_protect_brackets_out:Nw #1#2 [ {
  \tl_if_in:onTF { \use_none:n #2 } { ] }
    {
      \msg_error:nn { string-diagrams } { unopened-bracket }
      \tl_put_right:Ne #1 { \@@_before_closing:w #2 \q_stop }
      \@@_protect_brackets_skip:w
    }
    {
      \tl_put_right:No #1 { \use_none:n #2 }
      \@@_protect_brackets_in:Nw #1 \q_mark
    }
}
\cs_new_protected:Npn \@@_protect_brackets_skip:w #1 \q_stop ] { }
\cs_new:Npn \@@_before_closing:w #1 ] #2 \q_stop { \exp_not:o { \use_none:n #1 } }
\cs_new_protected:Npn \@@_protect_brackets_in:Nw #1#2 ] {
  \tl_if_eq:onF { \use_none:n #2 } { \q_stop }
    {
      \tl_if_in:onTF { \use_none:n #2 } { \q_stop }
        { \msg_error:nn { string-diagrams } { unclosed-bracket } }
        {
          \tl_put_right:Ne #1
            { \exp_not:N \s_@@_keys { \exp_not:o { \use_none:n #2 } } }
          \@@_protect_brackets_out:Nw #1 \q_mark
        }
    }
}
\prg_generate_conditional_variant:Nnn \tl_if_eq:nn { on } { F }
\prg_generate_conditional_variant:Nnn \tl_if_in:nn { on } { TF }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_adj_keys_tl,\l_@@_adj_piece_keys_tl,\l_@@_adj_rest_tl,\l_@@_adj_list_tl}
%   Scratch variables for the parser of \cmd\wires: the keys of a piece with those inherited, the keys of the piece alone, the rest of the piece, and a list whose brackets are protected.
%    \begin{macrocode}
\tl_new:N \l_@@_adj_keys_tl
\tl_new:N \l_@@_adj_piece_keys_tl
\tl_new:N \l_@@_adj_rest_tl
\tl_new:N \l_@@_adj_list_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_adj_piece:nn,\@@_adj_piece_loop:,\@@_drop_keys:w}
%   Splits a piece of the adjacencies, such as an item or a value, into the keys in brackets that begin it and the rest.
%   The keys are added to those inherited, in \cs{l_@@_adj_keys_tl}, and the rest goes in \cs{l_@@_adj_rest_tl}, without spaces around it, and without the braces of a group that it would be alone.
%   Keys in brackets anywhere else in the piece are reported and dropped, and so are keys followed by nothing, since they begin no wire.
%   \begin{arguments}
%     \item inherited \TikZ\ keys
%     \item piece, with its brackets protected
%   \end{arguments}
%    \begin{macrocode}
\msg_new:nnn { string-diagrams } { misplaced-keys }
  {
    Keys~in~brackets~after~the~beginning~of~`#1'~are~ignored:~
    keys~begin~an~item~or~a~value,~as~in~a.north~=~[red]~b.south.
  }
\msg_new:nnn { string-diagrams } { keys-without-wire }
  { Keys~`[#1]'~begin~no~wire,~so~they~are~ignored. }
\cs_new_protected:Nn \@@_adj_piece:nn {
  \tl_clear:N \l_@@_adj_piece_keys_tl
  \tl_set:Nn \l_@@_adj_rest_tl { #2 }
  \@@_adj_piece_loop:
  \tl_if_in:NnT \l_@@_adj_rest_tl { \s_@@_keys }
    {
      \tl_set:Ne \l_@@_adj_rest_tl
        {
          \exp_after:wN \@@_drop_keys:w \exp_after:wN \q_mark
            \l_@@_adj_rest_tl \s_@@_keys \s_@@_stop
        }
      \tl_trim_spaces:N \l_@@_adj_rest_tl
      \msg_error:nnV { string-diagrams } { misplaced-keys } \l_@@_adj_rest_tl
    }
  \bool_lazy_and:nnT
    { \tl_if_head_is_group_p:V \l_@@_adj_rest_tl }
    { \int_compare_p:nNn { \tl_count:N \l_@@_adj_rest_tl } = { 1 } }
    { \tl_set:Ne \l_@@_adj_rest_tl { \tl_head:N \l_@@_adj_rest_tl } }
  \tl_set:Nn \l_@@_adj_keys_tl { #1 }
  \tl_if_blank:VF \l_@@_adj_piece_keys_tl
    {
      \tl_if_blank:VTF \l_@@_adj_rest_tl
        {
          \msg_error:nnV { string-diagrams } { keys-without-wire }
            \l_@@_adj_piece_keys_tl
        }
        {
          \tl_if_blank:nF { #1 } { \tl_put_right:Nn \l_@@_adj_keys_tl { , } }
          \tl_put_right:NV \l_@@_adj_keys_tl \l_@@_adj_piece_keys_tl
        }
    }
}
\cs_new_protected:Nn \@@_adj_piece_loop: {
  \tl_trim_spaces:N \l_@@_adj_rest_tl
  \tl_if_head_eq_meaning:VNT \l_@@_adj_rest_tl \s_@@_keys
    {
      \tl_if_blank:VF \l_@@_adj_piece_keys_tl
        { \tl_put_right:Nn \l_@@_adj_piece_keys_tl { , } }
      \tl_put_right:Ne \l_@@_adj_piece_keys_tl
        { \tl_item:Nn \l_@@_adj_rest_tl { 2 } }
      \tl_set:Ne \l_@@_adj_rest_tl { \tl_tail:N \l_@@_adj_rest_tl }
      \tl_set:Ne \l_@@_adj_rest_tl { \tl_tail:N \l_@@_adj_rest_tl }
      \@@_adj_piece_loop:
    }
}
\cs_new:Npn \@@_drop_keys:w #1 \s_@@_keys #2
  {
    \exp_not:o { \use_none:n #1 }
    \str_if_eq:nnF { #2 } { \s_@@_stop } { \@@_drop_keys:w \q_mark }
  }
\prg_generate_conditional_variant:Nnn \tl_if_head_is_group:n { V } { p }
\prg_generate_conditional_variant:Nnn \tl_if_head_eq_meaning:nN { V } { T }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[TF]{\@@_adj_if_name:n}
%   Tests whether a piece of the adjacencies, once its keys are peeled, can be a name: a node, an anchor, or both.
%   A piece with a comma, an equals sign or braces in it is a group in braces where a name belongs, such as \texttt{\{b.south, c.south\}} in a list of targets, which has no beginning to share: it is reported.
%   A blank piece is not a name either, silently, like an empty item of a list.
%    \begin{macrocode}
\msg_new:nnn { string-diagrams } { group-without-beginning }
  {
    `#1'~is~a~group~without~a~beginning,~so~it~is~ignored:~
    a~group~follows~a~node~or~an~anchor~and~=,~as~in~b~=~\{~south.1,~south.2~\}.
  }
\prg_new_protected_conditional:Nnn \@@_adj_if_name:n { T, F, TF }
  {
    \regex_match:nnTF { [,=] | \cB. | \cE. } { #1 }
      {
        \msg_error:nnn { string-diagrams } { group-without-beginning } { #1 }
        \prg_return_false:
      }
      { \tl_if_blank:nTF { #1 } { \prg_return_false: } { \prg_return_true: } }
  }
\prg_generate_conditional_variant:Nnn \@@_adj_if_name:n { V } { T, F, TF }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_adj_map_function:nnN,\@@_adj_top_alone:n}
%   Unrolls the adjacencies given to \cmd\wires\ into wires and maps a function over them, in order.
%   The given list of \TikZ\ keys is passed through to the function, with the two ends of each wire, each as a node and an anchor.
%   A wire is written \meta{node}\texttt{.}\meta{anchor} \texttt{=} \meta{node}\texttt{.}\meta{anchor}, and wires that begin alike can share their beginning: it is written once, followed by \texttt{=} and a group of the rest of each wire.
%   A beginning ends after a node name or at the \texttt{=} of the wire, never inside an anchor, so a group holds anchors after the node a wire starts from, targets after the \texttt{=} of a wire, and anchors after the node of a target.
%   So the entries at the top are read as wires, or as groups after the node they start from; a node listed twice keeps the wires of both entries, and an anchor listed twice fans out.
%   Keys in brackets can begin any item or value: they are added to the keys of the call, and of the groups around them, for every wire they reach, so that the keys closest to a wire come last and win.
%   \begin{arguments}
%     \item \TikZ\ keys to pass through
%     \item adjacencies
%     \item function to be mapped over wires
%   \end{arguments}
%    \begin{macrocode}
\msg_new:nnn { string-diagrams } { node-without-wires }
  { Node~`#1'~has~no~wires:~list~them~as~in~#1~=~\{~north~=~b.south~\}. }
\msg_new:nnn { string-diagrams } { end-without-target }
  { Wire~end~`#1'~has~no~target:~give~it~one~as~in~#2~=~b.south. }
\cs_new_protected:Nn \@@_adj_map_function:nnN {
  \@@_protect_brackets:Nn \l_@@_adj_list_tl { #2 }
  \keyval_parse:nnV
    { \@@_adj_top_alone:n }
    { \@@_adj_source:nNnn { #1 } #3 }
    \l_@@_adj_list_tl
}
\cs_new_protected:Nn \@@_adj_top_alone:n {
  \@@_adj_piece:nn { } { #1 }
  \@@_adj_if_name:VT \l_@@_adj_rest_tl
    {
      \tl_if_in:VnTF \l_@@_adj_rest_tl { . }
        {
          \msg_error:nnVV { string-diagrams } { end-without-target }
            \l_@@_adj_rest_tl \l_@@_adj_rest_tl
        }
        { \msg_error:nnV { string-diagrams } { node-without-wires } \l_@@_adj_rest_tl }
    }
}
\cs_generate_variant:Nn \keyval_parse:nnn { nnV }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_adj_source:nNnn,\@@_adj_source_name:Nn,\@@_adj_node_list:nNnn,\@@_adj_node_item:nNnnn}
%   Reads an entry at the top: a wire, or a group, from the beginning of the wires to the rest of each.
%   A beginning with an anchor ends at the \texttt{=} of its wires, and one without ends after the name of their node.
%   \begin{arguments}
%     \item \TikZ\ keys to pass through
%     \item function to be mapped over wires
%     \item beginning: a node, or a node and an anchor
%     \item rest of each wire
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_adj_source:nNnn {
  \@@_adj_piece:nn { #1 } { #3 }
  \@@_adj_if_name:VT \l_@@_adj_rest_tl { \@@_adj_source_name:Nn #2 { #4 } }
}
\cs_new_protected:Nn \@@_adj_source_name:Nn {
  \tl_if_in:VnTF \l_@@_adj_rest_tl { . }
    {
      \@@_split_node_anchor:NNV \l_@@_adj_node_tl \l_@@_adj_anchor_tl \l_@@_adj_rest_tl
      \@@_adj_targets:VNVVn \l_@@_adj_keys_tl #1
        \l_@@_adj_node_tl \l_@@_adj_anchor_tl { #2 }
    }
    {
      \tl_set_eq:NN \l_@@_adj_node_tl \l_@@_adj_rest_tl
      \@@_adj_piece:Vn \l_@@_adj_keys_tl { #2 }
      \@@_protect_brackets:NV \l_@@_adj_list_tl \l_@@_adj_rest_tl
      \@@_adj_node_list:VNVV \l_@@_adj_keys_tl #1 \l_@@_adj_node_tl \l_@@_adj_list_tl
    }
}
\cs_new_protected:Nn \@@_adj_node_list:nNnn {
  \keyval_parse:nnn
    { \@@_adj_node_alone:nNnn { #1 } #2 { #3 } }
    { \@@_adj_node_item:nNnnn { #1 } #2 { #3 } }
    { #4 }
}
\cs_new_protected:Nn \@@_adj_node_item:nNnnn {
  \@@_adj_piece:nn { #1 } { #4 }
  \@@_adj_if_name:VT \l_@@_adj_rest_tl
    { \@@_adj_targets:VNnVn \l_@@_adj_keys_tl #2 { #3 } \l_@@_adj_rest_tl { #5 } }
}
\cs_generate_variant:Nn \@@_split_node_anchor:NNn { NNV }
\cs_generate_variant:Nn \@@_adj_piece:nn { V }
\cs_generate_variant:Nn \@@_adj_node_list:nNnn { VNVV }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_adj_node_alone:nNnn,\@@_adj_node_alone_name:Nn}
%   Reads an item without \texttt{=} in a group after the node a wire starts from.
%   It is the target of a wire from that node without an anchor, as in \texttt{f = \{ g \}}, if it names a node that exists.
%   Otherwise it is an error: an anchor left without a target, if it is an anchor of the node the wires start from, as the shape of that node tells, or an angle, which pgf reads as a point of the border; or else a node that doesn't exist; if the node the wires start from doesn't exist either, the error names it, since the item can't be told apart.
%   \begin{arguments}
%     \item \TikZ\ keys to pass through
%     \item function to be mapped over wires
%     \item node the wires start from
%     \item item
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_adj_node_alone:nNnn {
  \@@_adj_piece:nn { #1 } { #4 }
  \@@_adj_if_name:VT \l_@@_adj_rest_tl { \@@_adj_node_alone_name:Nn #2 { #3 } }
}
\cs_new_protected:Nn \@@_adj_node_alone_name:Nn {
  \@@_split_node_anchor:NNV \l_@@_adj_node_tl \l_@@_adj_anchor_tl \l_@@_adj_rest_tl
  \cs_if_exist:cTF { pgf@sh@ns@ \l_@@_adj_node_tl }
    { \@@_adj_wire:VNnnV \l_@@_adj_keys_tl #1 { #2 } { } \l_@@_adj_rest_tl }
    {
      \cs_if_exist:cTF { pgf@sh@ns@ #2 }
        {
          \@@_if_anchor:nVTF { #2 } \l_@@_adj_rest_tl
            {
              \msg_error:nnee { string-diagrams } { end-without-target }
                { \exp_not:n { #2 } . \exp_not:V \l_@@_adj_rest_tl }
                { \exp_not:V \l_@@_adj_rest_tl }
            }
            { \msg_error:nnV { string-diagrams } { end-no-node } \l_@@_adj_rest_tl }
        }
        { \msg_error:nnn { string-diagrams } { end-no-node } { #2 } }
    }
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_adj_targets:nNnnn,\@@_adj_target_list:nNnnn,\@@_adj_target_alone:nNnnn,\@@_adj_target_node:nNnnnn,\@@_adj_target_node_name:Nnnn,\@@_adj_target_anchors:nNnnnn,\@@_adj_target_anchor:nNnnnn}
%   Reads the rest of the wires from an end: their targets, or groups after the node of a target, which hold its anchors.
%   A group can't begin inside an anchor, as in \texttt{b.east = \{ 1, 2 \}}, and the anchors of a target take no value.
%   \begin{arguments}
%     \item \TikZ\ keys to pass through
%     \item function to be mapped over wires
%     \item node the wires start from
%     \item anchor the wires start from
%     \item targets
%   \end{arguments}
%    \begin{macrocode}
\msg_new:nnn { string-diagrams } { target-group-in-anchor }
  {
    Wire~end~`#1'~can't~begin~a~group:~
    targets~share~their~node,~as~in~b~=~\{~east.1,~east.2~\},~not~part~of~an~anchor.
  }
\msg_new:nnn { string-diagrams } { target-anchor-with-value }
  {
    Wire~end~`#1.#2'~has~a~value:~
    list~the~anchors~of~`#1'~alone,~as~in~#1~=~\{~east.1,~east.2~\}.
  }
\cs_new_protected:Nn \@@_adj_targets:nNnnn {
  \@@_adj_piece:nn { #1 } { #5 }
  \@@_protect_brackets:NV \l_@@_adj_list_tl \l_@@_adj_rest_tl
  \@@_adj_target_list:VNnnV \l_@@_adj_keys_tl #2 { #3 } { #4 } \l_@@_adj_list_tl
}
\cs_new_protected:Nn \@@_adj_target_list:nNnnn {
  \keyval_parse:nnn
    { \@@_adj_target_alone:nNnnn { #1 } #2 { #3 } { #4 } }
    { \@@_adj_target_node:nNnnnn { #1 } #2 { #3 } { #4 } }
    { #5 }
}
\cs_new_protected:Nn \@@_adj_target_alone:nNnnn {
  \@@_adj_piece:nn { #1 } { #5 }
  \@@_adj_if_name:VT \l_@@_adj_rest_tl
    { \@@_adj_wire:VNnnV \l_@@_adj_keys_tl #2 { #3 } { #4 } \l_@@_adj_rest_tl }
}
\cs_new_protected:Nn \@@_adj_target_node:nNnnnn {
  \@@_adj_piece:nn { #1 } { #5 }
  \@@_adj_if_name:VT \l_@@_adj_rest_tl
    { \@@_adj_target_node_name:Nnnn #2 { #3 } { #4 } { #6 } }
}
\cs_new_protected:Nn \@@_adj_target_node_name:Nnnn {
  \tl_if_in:VnTF \l_@@_adj_rest_tl { . }
    { \msg_error:nnV { string-diagrams } { target-group-in-anchor } \l_@@_adj_rest_tl }
    {
      \tl_set_eq:NN \l_@@_adj_node_tl \l_@@_adj_rest_tl
      \@@_adj_piece:Vn \l_@@_adj_keys_tl { #4 }
      \@@_protect_brackets:NV \l_@@_adj_list_tl \l_@@_adj_rest_tl
      \@@_adj_target_anchors:VNnnVV \l_@@_adj_keys_tl #1 { #2 } { #3 }
        \l_@@_adj_node_tl \l_@@_adj_list_tl
    }
}
\cs_new_protected:Nn \@@_adj_target_anchors:nNnnnn {
  \keyval_parse:nnn
    { \@@_adj_target_anchor:nNnnnn { #1 } #2 { #3 } { #4 } { #5 } }
    { \msg_error:nnnn { string-diagrams } { target-anchor-with-value } { #5 } }
    { #6 }
}
\cs_new_protected:Nn \@@_adj_target_anchor:nNnnnn {
  \@@_adj_piece:nn { #1 } { #6 }
  \@@_adj_if_name:VT \l_@@_adj_rest_tl
    {
      \use:e
        {
          \exp_not:N #2 { \exp_not:V \l_@@_adj_keys_tl }
            { \exp_not:n { #3 } } { \exp_not:n { #4 } }
            { \exp_not:n { #5 } } { \exp_not:V \l_@@_adj_rest_tl }
        }
    }
}
\cs_generate_variant:Nn \@@_adj_targets:nNnnn { VNVV , VNnV }
\cs_generate_variant:Nn \@@_adj_target_list:nNnnn { VNnnV }
\cs_generate_variant:Nn \@@_adj_target_anchors:nNnnnn { VNnnVV }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_adj_wire:nNnnn}
%   Applies a function to one wire, splitting its target into node and anchor.
%   \begin{arguments}
%     \item \TikZ\ keys to pass through
%     \item function to be applied
%     \item source node
%     \item source anchor
%     \item target
%   \end{arguments}
%    \begin{macrocode}
\cs_new_protected:Nn \@@_adj_wire:nNnnn {
  \@@_split_node_anchor:NNn \l_@@_target_node_tl \l_@@_target_anchor_tl { #5 }
  \exp_args:Nnnnoo #2
    { #1 } { #3 } { #4 } { \l_@@_target_node_tl } { \l_@@_target_anchor_tl }
}
\cs_generate_variant:Nn \@@_adj_wire:nNnnn { VNnnV }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\wires}
%   \changes{0.2.1}{2023/06/13}{now correctly handles optional style parameter}
%   \changes{0.3.0}{2026/11/07}{loose ends are wires to the boundary node \texttt{@}, replacing the third argument}
%   \changes{0.3.0}{2026/11/07}{every listed wire is drawn, so nodes can be listed twice and anchors can fan out}
%   \changes{0.3.0}{2026/11/07}{wires leave corners diagonally}
%   \changes{0.3.0}{2026/11/07}{wire ends off the sides or without an anchor are errors}
%   Define our superstar.
%    \begin{macrocode}
\NewDocumentCommand{\wires}{ O{} m }
{
  \@@_draw_boundary:
  \@@_adj_map_function:nnN
    { #1 } { #2 } \@@_draw_wire:nnnnn
}
%    \end{macrocode}
% \end{macro}
%
% Close the \pkg{DocStrip} guards and call it a day.
%    \begin{macrocode}
%</package>
%    \end{macrocode}
%
% \end{implementation}
