我真的很好奇是否有人愿意花时间解释如何创建与pgf/TiKz
捆绑包中的某些宏类似的宏。
我特别感兴趣的是如何创建一个命令,其作用如下
\<csname> <content> [bare word directive] <more content>;
为了清楚地表达我的意思,我指的是如何(\path
以及\draw
其它)被解析并且可以包含光说不练node
如下图所示的命令edge
:
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}
\begin{document}
\begin{tikzpicture}
\path[blue] node (B) at (0,3) {CORNER} edge [->] (0,0) edge [->] (2,4);
\draw (0,0) -- (2,4) node [midway] {HELLO} edge [->] (B);
\draw (2,4) -- (4,2) edge [->] (B) node [midway] {HELLO} ;
\end{tikzpicture}
\end{document}
我是谁不是感兴趣的是如何node
知道使用什么或如何edge
知道从哪里绘制。
相反,我很好奇的是,人们LaTeX
最终是如何将光秃秃的单词node
看作本质上,另一个命令,同样如此edge
。所以这是一个关于解析参数并解释这些参数内容的问题。
我查阅了大量tikz.code.tex
其他文件,试图弄清楚如何实现这种解析,但很快就迷失在代码中。
我甚至无法使如下这样简单的事情发挥作用:
\documentclass{article}
\makeatletter
\newcommand\hello{Hello World!}
\newcommand\ciao{ciao?}
\newcommand\secretmessage{\ae@message}
\def\ae@message(#1)#2;{%%
\def\ae@continue{}%%
\hspace{1em}\emph{#1}%%
\expandafter\ifx\expandafter\relax\detokenize\expandafter{#2}\relax
\else
\def\ae@continue{\ae@parse #2 ;}%%
\fi
\ae@continue
\def\ae@continue{}%%
}
\def\ae@parse#1 ;{\typeout{===>"#1"}\csname#1\endcsname}
\def\ae@trim#1{\ignorespaces#1\unskip}
\makeatother
\setlength\parskip{2ex}
\setlength\parindent{0pt}
\begin{document}
\secretmessage(This is my message.);
\secretmessage(This is my message.) hello;
\secretmessage(This is my message.) ciao;
\secretmessage(This is my message.);
The above should have been interpreted as equivalent to:
\secretmessage(This is my message.);
\secretmessage(This is my message.); \hello
\secretmessage(This is my message.); \ciao
\secretmessage(This is my message.);
\end{document}
我不知道如何才能把上面的空格去掉,而不会产生像
\csname\space hello\endcsname
虽然我真的很想做一些更复杂的事情,例如:
\secretmessage(This is my message.) hello ciao;
这实际上应该被解释为
\secretmessage(This is my message.); \hello\space \ciao
尽管我强烈怀疑,如果没有进一步论证的帮助,hello
这些ciao
相邻的裸词指令不太可能在没有大量工作的情况下得到正确解析。
答案1
简短回答:它确实需要一个工作量极大
长答案:不,你不想知道细节。我不想写它们。真的……(更新:@percusse 有一个很好的解释这里)
Tikz 不使用 TeX 宏引擎。它构建自己的解析器,该解析器基本上处理给定命令(例如\tikz
, \path
)之后的所有字符,期望一些有效的关键字,并逐个字母检查该关键字是否确实存在,直到;
找到 a(这标志着此解析器的输入结束)。它还会进行特殊处理(
等等。大多数字符的含义都与上下文相关,因此它必须根据先前解析的内容调用适当的宏来处理它们。
为了让您了解我们正在讨论的复杂程度,这里有一小段代码,tikz.code.tex
它试图确定以下“标记”(不是 TeX 意义上的)是否是单词“坐标”。Tikz 在期望节点作为路径的一部分时“调用”此宏。
\def\tikz@parse@child@node{%
\pgfutil@ifnextchar n{\tikz@parse@child@node@n}%
{\pgfutil@ifnextchar c{\tikz@parse@child@node@c}%
{\tikz@parse@child@node@rest}}}
\def\tikz@parse@child@node@rest#1\pgf@stop{\def\tikz@child@node@rest{#1}}
\def\tikz@parse@child@node@c c{\pgfutil@ifnextchar o{\tikz@parse@child@node@co}{\tikz@parse@child@node@rest c}}
\def\tikz@parse@child@node@co o{\pgfutil@ifnextchar o{\tikz@parse@child@node@coordinate}{\tikz@parse@child@node@rest co}}
\def\tikz@parse@child@node@coordinate ordinate{%
\pgfutil@ifnextchar ({\tikz@@parse@child@node@coordinate}{%
\def\tikz@child@node@text{[shape=coordinate]{}}%
\tikz@parse@child@node@rest}}%}
\def\tikz@@parse@child@node@coordinate(#1){%
\pgfutil@ifnextchar a{\tikz@p@c@n@c@at(#1)}{%
\def\tikz@child@node@text{[shape=coordinate,name=#1]{}}%
\tikz@parse@child@node@rest}}
\def\tikz@p@c@n@c@at(#1)at#2({%
\def\tikz@child@node@text@pre{[shape=coordinate,name=#1]at}%
\tikz@scan@one@point\tikz@p@c@n@c@at@math(%
}
如您所见(您看不出来吗?:-))它首先检查字母是否n
存在。如果存在,可能是因为单词node
来了,因此它使用宏\tikz@parse@child@node@n
继续。如果不存在,它会检查它是否是字母c
。如果是,那么很可能是单词的开头coordinate
,因此它会调用宏\tikz@parse@child@node@c
继续,检查下一个字符是否是o
,依此类推。
\expandafter
这是、\edef
和\pgfutil@ifnextchar
\ifx
的巨大噩梦中的 \t 一小部分\tikz@really@long@macro@name@s
。我不知道 Till Tantau 在写完这些之后是如何保持理智的,除非这些代码是用工具生成的。