Edit notes:
Inspired by Kpym's answer but thinking that the possibilities of pgfparser are insufficient, I wrote my own parser, that should be similar to pgf's, but allows the parsed elements to grab arguments. The following arguments are supported:
m a mandatory argument
r{<delim>} a mandatory argument delimited by <delim>
o an optional argument in brackets (you can test for it with \myIfNoValueTF)
O{<default>} an optional argument defaulting to <default>
d<delim1><delim2> an optional argument delimited by <delim1> and <delim2> (you can test for it with \myIfNoValueTF)
D<delim1><delim2>{<default>} an optional argument delimited by <delim1> and <delim2> defaulting to <default>
t<token> an optional token (you can test for it with \myIfBooleanTF)
Despite the fact that the argument names are inspired by xparse, none of them care for balanced delimiters (e.g., [[ab]] would not be grabbed as [ab], but as [ab with an orphaned ], so you'd have to use [{[ab]}], this is like the LaTeX2e behaviour). Also all arguments are long by nature unlike xparse where you'd need to specify that using +.
You can define a parser using \myparserdef{<name>}{<state>}{<meaning>}[<args>]{<code>}, with <name> the name of the parser, <state> the state, <meaning> the meaning of a token, <args> an argument string (built from the arguments listed above, up to 9 arguments are supported) and <code> the code which should be executed for that combination of <name>, <state> and <meaning>. You can switch states using \myparserstate{<state>}, if you switch to final parsing is ended, the initial state is initial. All in all this is pretty similar to pgfparsers way of doing things. The biggest difference is, that my parser ignores blanks by default, however you can define an action for blanks with \myparserdef{<name>}{<state>}{blank space}[<args>]{<code>}. Many consecutive blanks are considered as one.
You can also use a different syntax for \myparserdef namely: \myparserdef{<name>}{<state>}<token>[<args>]{<code>} with <token> being a single token not surrounded by {} (spaces are ignored here, it is not checked whether <token> is really a single token, so be careful). In that case the same will be done as if you'd typed the \meaning of the <token>, so \myparserdef{foo}{bar}a{baz} does the same as \myparserdef{foo}{bar}{the letter a}{baz}.
A parser is executed using \myparserrun{<name>}. A parser needs at least one rule, else an error is thrown.
This parser system allows me to place coordinates inside of \sheet, which was the main reason why I found pgfparser insufficient.
I did all of this without using any packages, only LaTeX-Kernel macros, just for procrastinating reasons. Using for example xparse would shorten the code considerably.
\documentclass[tikz,border=7pt]{standalone}
% my parser >>>
\makeatletter
% logic helpers >>>
\long\def\myfi@An#1\fi#2{\fi}
\long\def\myfi@Ay#1\fi#2{\fi#2}
\long\def\myfi@By\fi#1{\fi#1}
\long\def\myfi@BTb\fi#1#2#3{\fi#2}
% <<<
% mynewdef >>>
\@ifdefinable\myifundefTF%>>>
{%
\def\myifundefTF#1%
{%
\ifdefined#1%
\myfi@Ay
\else
\myfi@BTb
\fi
{%
\ifx#1\relax
\myfi@BTb
\fi
\@secondoftwo
}%
}%
}%<<<
\@ifdefinable\mynewdef%>>>
{%
\protected\def\mynewdef{\@ifnextchar[{\mynewdef@a}{\mynewdef@a[]}}%
}%<<<
\@ifdefinable\mynewdef@a%>>>
{%
\long\def\mynewdef@a[#1]#2#3#{\mynewdef@b{#1}#2{#3}}%
}%<<<
\@ifdefinable\mynewdef@b%>>>
{%
\protected\long\def\mynewdef@b#1#2#3#4%
{%
\myifundefTF#2%
{%
#1\def#2#3{#4}%
}
{%
\PackageError{my}{Command \string#2\space already defined}{}%
}
}%
}%<<<
\mynewdef\myifcsundefTF#1%>>>
{%
\ifcsname #1\endcsname
\myfi@Ay
\else
\myfi@BTb
\fi
{%
\expandafter\ifx\csname #1\endcsname\relax
\myfi@BTb
\fi
\@secondoftwo
}%
}%<<<
% <<<
% opt arg parsing >>>
\mynewdef[\long]\my@ifmark#1%>>>
{%
\ifx\my@mark#1%
\myfi@BTb
\fi
\@secondoftwo
}%<<<
\mynewdef[\protected\long]\myoarg@oarg#1%>>>
{%
\@ifnextchar[{\myoarg@oarg@{#1}}{#1{\my@mark}}%
}%<<<
\mynewdef[\long]\myoarg@oarg@#1[#2]%>>>
{%
#1{#2}%
}%<<<
\mynewdef[\protected\long]\myoarg@Oarg#1#2%>>>
{%
\@ifnextchar[{\myoarg@oarg@{#2}}{#2{#1}}%
}%<<<
\mynewdef[\protected\long]\myoarg@darg#1#2#3%>>>
{%
\long\def\myoarg@darg@##1#1##2#2{##1{##2}}%
\@ifnextchar#1{\myoarg@darg@{#3}}{#3{\my@mark}}%
}%<<<
\mynewdef[\protected\long]\myoarg@Darg#1#2#3#4%>>>
{%
\long\def\myoarg@darg@##1#1##2#2{##1{##2}}%
\@ifnextchar#1{\myoarg@darg@{#4}}{#4{#3}}%
}%<<<
% <<<
% macros for string comparison >>>
\begingroup\def\:{\endgroup\let\my@sptoken= }\:
\mynewdef\my@mark{\my@mark@if@you@see@this@report@it}
\mynewdef\my@stop{\my@stop@if@you@see@this@report@it}
\mynewdef\myparser@final{final}
\mynewdef\myparser@noarg{noarg}
\mynewdef\myparser@blankspace{blank space}
% <<<
\mynewdef[\protected]\myparserdef#1#2%>>>
{%
\def\myparserdef@twoargs{{#1}{#2}}%
\myparserdef@a
}%<<<
\mynewdef[\protected]\myparserdef@a%>>>
{%
\futurelet\myparserdef@arg\myparserdef@b
}%<<<
\mynewdef\myparserdef@b%>>>
{%
\ifx\myparserdef@arg\my@sptoken
\myfi@BTb
\fi
\@secondoftwo
{\myparserdef@gobble@space}
{\myparserdef@c}%
}%<<<
\mynewdef[\protected]\myparserdef@gobble@space%>>>
{%
\afterassignment\myparserdef@a
\let\myparserdef@arg= % space after = to get spaces, too
}%<<<
\mynewdef[\protected]\myparserdef@c#1%>>>
{%
\ifx\myparserdef@arg\bgroup
\myfi@BTb
\fi
\@secondoftwo
{%
\def\myparserdef@arg{#1}%
\ifx\myparser@blankspace\myparserdef@arg
\myfi@BTb
\fi
\@secondoftwo
{\expandafter\myparserdef@d\myparserdef@twoargs{blank space \space}}
{\expandafter\myparserdef@d\myparserdef@twoargs{#1}}%
}
{\expandafter\myparserdef@d\myparserdef@twoargs{\meaning\myparserdef@arg}}%
}%<<<
\mynewdef[\long]\myparserdef@d#1#2#3%>>>
{%
\myoarg@oarg{\myparserdef@e{#1}{#2}{#3}}%
}%<<<
\mynewdef[\protected\long]\myparserdef@e#1#2#3#4#5%>>>
{%
% if that is the first rule for the parser, define space to be a no-op
\myifcsundefTF{\myparser@name{#1}}%
{%
\expandafter\def
\csname \myparser@name{#1} all blank space \space\space type\endcsname
{noarg}%
\expandafter\def
\csname \myparser@name{#1} all blank space \space\space code\endcsname
{\myparser@getnexttoken}%
% use a macro as a flag that there is at least one parser rule defined
\expandafter\def\csname\myparser@name{#1}\endcsname{}%
}
{}%
% check whether the optional argument was used
\my@ifmark{#4}
{%
% use a macro as flag that there is a rule for the specified state
\expandafter\def\csname \myparser@name{#1} #2 #3 type\endcsname{noarg}%
\def\myparserdef@arg@count{0}%
}
{%
\edef\myparserdef@arg@count
{\the\numexpr\myparserdef@argcount#4\my@mark}%
\ifnum\myparserdef@arg@count>9%
\PackageError{my}{Too many arguments for parser rule}
{A maximum of 9 parameters is supported.}%
\else
\ifnum\myparserdef@arg@count=0%
\expandafter\def\csname \myparser@name{#1} #2 #3 type\endcsname
{noarg}%
\else
\expandafter\def\csname \myparser@name{#1} #2 #3 type\endcsname{#4}%
\fi
\fi
}%
% define what the code does
\expandafter\def\csname \myparser@name{#1} #2 #3 code\endcsname{}%
\expandafter\renewcommand\csname \myparser@name{#1} #2 #3 code\endcsname
[\myparserdef@arg@count]{#5\myparser@getnexttoken}%
}%<<<
\mynewdef\myparserdef@argcount#1%>>>
{%
\ifx\my@mark#1%
\myfi@An
\else
\myfi@By
\fi
{\csname myparserdef@argcount@#1\endcsname}%
}%<<<
\mynewdef\myparserdef@argcount@m%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparserdef@argcount@o%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparserdef@argcount@O#1%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparserdef@argcount@d#1#2%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparserdef@argcount@D#1#2#3%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparserdef@argcount@r#1%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparserdef@argcount@t#1%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparser@name#1%>>>
{%
myparser #1%
}%<<<
\mynewdef\myparser@rule#1%>>>
{%
\myparser@current\space #1\space \meaning\myparser@token
}%<<<
\mynewdef\myparser@type#1%>>>
{%
\myparser@rule{#1} type%
}%<<<
\mynewdef\myparser@code#1%>>>
{%
\myparser@rule{#1} code%
}%<<<
\mynewdef[\protected]\myparserrun#1%>>>
{%
\myifcsundefTF{\myparser@name{#1}}
{\PackageError{my}{No parser named '#1' defined}{}}
{%
\edef\myparser@current{\myparser@name{#1}}%
\def\myparser@usersname{#1}%
\def\myparser@state{initial}%
\myparser@getnexttoken
}%
}%<<<
\mynewdef[\protected]\myparser@getnexttoken%>>>
{%
\ifx\myparser@state\myparser@final
\myfi@An
\else
\myfi@By
\fi
{%
\afterassignment\myparser@getnexttoken@a
\let\myparser@token= % space after = to get spaces, too
}%
}%<<<
\mynewdef[\protected]\myparser@getnexttoken@a%>>>
{%
\myifcsundefTF{\myparser@type{\myparser@state}}
{%
\myifcsundefTF{\myparser@type{all}}
{%
\PackageError{my}
{%
No rule for parser '\myparser@usersname' in state
'\myparser@state' for '\meaning\myparser@token'.
Ignoring token%
}
{}%
\myparser@getnexttoken
}
{\myparser@handle{all}}%
}
{\myparser@handle{\myparser@state}}%
}%<<<
\mynewdef[\protected]\myparser@handle#1%>>>
{%
\expandafter\ifx\csname\myparser@type{#1}\endcsname\myparser@noarg
\myfi@BTb
\fi
\@secondoftwo
{\csname\myparser@code{#1}\endcsname}
{\myparser@handle@args{#1}}%
}%<<<
\mynewdef[\protected]\myparserstate#1%>>>
{%
\def\myparser@state{#1}%
}%<<<
\mynewdef[\protected]\myparser@handle@args#1%>>>
{%
\def\myparser@stateorall{#1}%
\expandafter\expandafter\expandafter\myparser@handle@args@a
\csname\myparser@type{#1}\endcsname\my@mark\my@stop
}%<<<
\mynewdef\myparser@handle@args@a%>>>
{%
\myparser@handle@args@b{}%
}%<<<
\mynewdef[\long]\myparser@handle@args@b#1#2#3\my@stop%>>>
{%
\my@ifmark{#2}
{\csname\myparser@code{\myparser@stateorall}\endcsname#1}
{%
\myifcsundefTF{myparser@handle@args@#2}
{\PackageError{my}{Unknown argument type '#2'}{}}
{\csname myparser@handle@args@#2\endcsname{#1}{#3}}%
}%
}%<<<
\mynewdef[\long]\myparser@handle@args@c#1#2%>>>
{%
\my@ifmark{#2}
{\csname\myparser@code{\myparser@stateorall}\endcsname#1}
{\myparser@handle@args@b{#1}#2\my@stop}%
}%<<<
\mynewdef[\long]\myparser@handle@args@m#1#2#3%>>>
{%
\myparser@handle@args@c{#1{#3}}{#2}%
}%<<<
\mynewdef[\protected\long]\myparser@handle@args@o#1#2%>>>
{%
\myoarg@oarg{\myparser@handle@args@m{#1}{#2}}%
}%<<<
\mynewdef[\long]\myparser@handle@args@O#1#2%>>>
{%
\myparser@handle@args@O@{#1}#2\my@stop
}%<<<
\mynewdef[\protected\long]\myparser@handle@args@O@#1#2#3\my@stop%>>>
{%
\myoarg@Oarg{#2}{\myparser@handle@args@m{#1}{#3}}%
}%<<<
\mynewdef[\long]\myparser@handle@args@d#1#2%>>>
{%
\myparser@handle@args@d@{#1}#2\my@stop
}%<<<
\mynewdef[\protected\long]\myparser@handle@args@d@#1#2#3#4\my@stop%>>>
{%
\myoarg@darg{#2}{#3}{\myparser@handle@args@m{#1}{#4}}%
}%<<<
\mynewdef[\long]\myparser@handle@args@D#1#2%>>>
{%
\myparser@handle@args@D@{#1}#2\my@stop
}%<<<
\mynewdef[\protected\long]\myparser@handle@args@D@#1#2#3#4#5\my@stop%>>>
{%
\myoarg@Darg{#2}{#3}{#4}{\myparser@handle@args@m{#1}{#5}}%
}%<<<
\mynewdef[\long]\myparser@handle@args@r#1#2%>>>
{%
\myparser@handle@args@r@a{#1}#2\my@stop
}%<<<
\mynewdef[\protected\long]\myparser@handle@args@r@a#1#2#3\my@stop%>>>
{%
\def\myparser@handle@args@r@b##1#2{\myparser@handle@args@c{#1{##1}}{#3}}%
\myparser@handle@args@r@b
}%<<<
\mynewdef[\long]\myparser@handle@args@t#1#2%>>>
{%
\myparser@handle@args@t@a{#1}#2\my@stop
}%<<<
\mynewdef[\protected\long]\myparser@handle@args@t@a#1#2#3\my@stop%>>>
{%
\@ifnextchar#2%
{\@firstoftwo{\myparser@handle@args@c{#1{\my@mark}}{#3}}}
{\myparser@handle@args@c{#1{\my@stop}}{#3}}%
}%<<<
\let\myIfNoValueTF\my@ifmark
\let\myIfBooleanTF\my@ifmark
\makeatother
% <<<
% the sheet path parser >>>
\makeatletter
\def\insertpath#1{\edef\sheetpath@{\unexpanded\expandafter{\sheetpath@#1}}}
\def\sheetpathcurve#1#2#3#4%
{%
to[out=0,in=180] ++({.5*\sheetlength},{#1*\sheetheight})
\myIfBooleanTF{#3}{}{node[scale=\sheetdotsize,inner sep=0pt]{.}}
\myIfNoValueTF{#4}{}{coordinate({#4})}
to[out=0,in=180] ++({.5*\sheetlength},{#2*\sheetheight})
}
\def\outputpath{\draw\sheetpath@;}
\def\sheet{\def\sheetpath@{}\myparserrun{sheet path}}
\makeatother
\myparserdef{sheet path}{initial}{the character (}[r,r)]
{\insertpath{({#1},{#2*\sheetheight})}\myparserstate{started}}
\myparserdef{sheet path}{initial}{the character =}
{\insertpath{(0,0)--++(\sheetlength,0)}\myparserstate{started}}
\myparserdef{sheet path}{started}{the character (}[r)]
{\insertpath{coordinate({#1})}}
\myparserdef{sheet path}{started}{the character =}
{\insertpath{--++(\sheetlength,0)}}
\myparserdef{sheet path}{started}{the letter u}[txd()]
{\insertpath{\sheetpathcurve{.5}{-.5}{#1}{#2}}}
\myparserdef{sheet path}{started}{the letter U}[txd()]
{\insertpath{\sheetpathcurve{1}{-1}{#1}{#2}}}
\myparserdef{sheet path}{started}{the letter d}[txd()]
{\insertpath{\sheetpathcurve{-.5}{.5}{#1}{#2}}}
\myparserdef{sheet path}{started}{the letter D}[txd()]
{\insertpath{\sheetpathcurve{-1}{1}{#1}{#2}}}
% making '.' a no-op, that way we can use it to end the search for an optional
% argument for instance
\myparserdef{sheet path}{started} .{}
\myparserdef{sheet path}{started}{the letter x}[d()]
{%
\myIfNoValueTF{#1}
{%
\insertpath
{--node[scale=\sheetdotsize,inner sep=0pt]{.}++(\sheetlength,0)}%
}
{%
\insertpath
{%
--node[scale=\sheetdotsize,inner sep=0pt]{.}
coordinate({#1})
++(\sheetlength,0)
}%
}%
}
\myparserdef{sheet path}{all}{the character ;}{\myparserstate{final}\outputpath}
% <<<
% sheet height and sheet length >>>
\pgfset
{
,sheet height/.store in=\sheetheight
,sheet height=1
,sheet length/.store in=\sheetlength
,sheet length=1
,sheet dot size/.store in=\sheetdotsize
,sheet dot size=4
}
%<<<
\begin{document}
\begin{tikzpicture}
\begin{scope}[thick, sheet height=0.75]
\sheet === ===d(sp1)=;
\sheet (0,-1) ==d(sp2)===ux =;
\sheet (0,-2) ==ux ===Dx =;
\sheet (0,-3) === ==== =;
\sheet (0,-4) === ===U(sp3)=;
\sheet (0,-5.5) ==x(b1) ===x(b2) =;
\end{scope}
\draw[dashed]
(b2) -- (sp1)
(b1) -- (sp2)
;
\end{tikzpicture}
\end{document}

curvylinks,tikz-timingcould be useful to draw this kind of diagrams. – Ignasi Mar 25 '19 at 08:20