选自:https://zhuanlan.zhihu.com/p/581999989
LaTeX3真的离谱,可谓是又爱又恨。语法太那啥,反人类了。可能是我第一次接触宏编程吧,反正就是各种的不适应。反观C,C++, Python, 甚至是linux下的SHELL脚本,我的初次学习体验,LaTeX3 就把我虐的体无完肤。还是给出我的学习文档和源码吧,仅供参考。
初次接触LATEX3,写的代码垃圾的一匹。但是我觉得对于新手了解LATEX3还是有一定的帮助的,欢迎交流。
\documentclass[fontset=windows]{article}
\usepackage{expl3}
\usepackage{My}
\usepackage{framed}
\title{\LaTeX 3 入门}
\author{Puppy}
\date{}
\begin{document}
\maketitle
\tableofcontents
\newpage
\section{\LaTeX3的相关主要组成}
\begin{itemize}
\item 1. l3kernel:包含expl3的各个部分
\item 2. l3packages:提供设计层和文本标记层接口
\begin{itemize}
\item 2.1 l3keys2e
\item 2.2 xfp
\item 2.3 xfrac
\item 2.4 xparse
\item 2.5 xtemplate
\end{itemize}
\item 3. l3experimental:一些实验性的尝试,构建接口(不如上一个稳定)
\item 4. l3backend:提供与后端(底层驱动)的交互代码。处理颜色,绘图, PDF。目前支持的驱动
\begin{itemize}
\item 4.1 dvipdffmx
\item 4.2 dvips
\item 4.3 dvisvgm
\item 4.4 xdvipdfmx
\item 4.5 PDF模式(pdflatex和LuaTeX)
\end{itemize}
\item 5. l3build:\LaTeX3的构建系统
\end{itemize}
\section{\LaTeX3的尝试}
\subsection{基础知识铺垫}
\textbf{函数与变量}:在\LaTeX3中函数和变量都是以$\backslash{}$开头
\textbf{函数}
\begin{itemize}
\item 1. 函数可以吃掉一些参数,并进行相关的操作
\item 2. 函数要么是可以被展开的,要么就是可以被执行的
\end{itemize}
\textbf{变量}\par
\begin{itemize}
\item 变量可以用来存储数据
\end{itemize}
\textbf{定义的方法}
\subsection{ReMark:什么是token?}
最开始我也挺奇怪的,为什么有这个token这种说法。因为我取网上查看了这个单词的翻译
然而网上最合理的翻译就是:{\ttfamily 象征,标志,记号}。是不是感觉这个翻译听了都头疼,感觉看了更加的不知道token
是一个啥玩意了?下面我们就展示 D E K 在它的 tex 源码中给的定义:
{
\ttfamily A TEX token is either a character or a control sequence ...
}
也就是说token其实就是一个单独的{\bf 字符},(a, A, \#, 1, \^, \&, ...);又或者是一个单独的控制序列(以 \textbackslash 开头的命令)
下面再来看一下文档中对于 {\bf token list} 的定义:
\texttt{ A token list is a singly linked list of one-word nodes in mem, where each word contains a token
and a link. Macro definitions, output-routine definitions, marks, \textbackslash write texts, and a few other things are
remembered by TEX in the form of token lists, usually preceded by a node with a reference count in its
token ref count field. The token stored in location p is called info(p).}
不严谨的说,我们就可以直接的把 token list 看作由 \{\} 包裹的一系列的 token 的集合
其实这个 token 真的不是那么好直译的,但是项子越老师把它译作了 {\bf 凭据},对应的 token list 就是{\bf 凭据表}。
感觉这个翻译还是蛮好的。
\subsection{基本数据类型}
命令定义格式:
\begin{verbatim}
\<module>_<description>:<arg-spec>
\end{verbatim}
\begin{itemize}
\item 1. module: 模块名
\item 2. description:描述
\item 3. arg-spec:指定的{\bf 参数类型}
\begin{itemize}
\item 3.1 n:接收一个凭据表【一组被\{\}包围的记号】
\item 3.2 x:与 n 类似, 但是对凭据表内的内容进行递归展开。
\item 3.3 N:接收一个命令,传递命令本身
\item 3.4 V:与 N 类似,但是传递命令的值
\item 3.5 p:原始的\TeX 的形参指定(就是原来\LaTeX2 $\varepsilon$中使用 $\backslash$ def时的\#1, \#2)
\item 3.4 T/F:n的特殊情形,提供条件判断分支语句
\end{itemize}
\end{itemize}
\textbf{注意}\par
n/x 与 N/V 的区别涉及到宏展开的细节问题,后面会讨论。
\textbf{开启\LaTeX3编程接口}
\begin{itemize}
\item 1. 在\verb!\ExplSyntaxOn ** \ExplSyntaxOff!中间的即可以使用\LaTeX3语法
\item 2. 由于硬空格,下划线\verb!~!,\verb!_!和冒号\verb!:!被转义了。
\item 3. 可以分别使用\verb!\~!,\verb!\c_math_subscript_token!和\verb!\c_colon_str!来替代
\end{itemize}
% 没有expl3这个环境
% \begin{expl3}
% \end{expl3}
\ExplSyntaxOn
% \subsection{函数}
% 声明函数\par
% \cs_new:Npn \my_fun: #1 {Hello #1}
% 使用函数\par
% \my_fun("Tomz")
\subsection{变量}
\vspace*{2em}
变量(整形和浮点型用法相同, 改为fp_new:N即可)\par
声明一个整形变量(var):~~\int_new:N \var \verb !\int_new:N \var! \par
变量var的赋值:\hspace*{4em}\int_set:Nn \var {2} \verb !\int_set:Nn \var {2} ! \par
使用变量:\int_use:N \var \hspace*{6.5em}\verb !\int_use:N \var!\par
输出值:\int_eval:n {\var*1000} \hspace*{6em}\verb!\int_eval:n {\var*1000}!
\par
\noindent 注:\verb!_new!:声明一个变量 \hspace*{4em} \verb!_set!:变量的赋值
\par
\verb |_use|:使用变量的值 \hspace*{4em}\verb|_eval|:计算表达式的值
\subsection{函数}
函数的是以一个命令:{\verb |\cs_set:Npn|} 来声明的,下面就是一个示例:
\par
\begin{framed}
\verb|\|cs_set:\textcolor{blue}{Npn} ~~\verb |\|my_function:nn ~~\verb |#1#2| ~~\{\\
\hspace*{4em}两个数的和为: \verb |\|int_eval:n ~~\verb |{#1 + #2}|\\
\hspace*{2em}\}\\
\hspace*{2em}函数的调用\\
\hspace*{2em}\verb |\|my_function:nn ~~\{1\}\{2\}
\end{framed}
\par
\bigskip
\textbf{定义的解释说明}
\par
在这里我定义了一个用于计算两个整数和的函数 my_function,它接受两个整数,然后输出这两个整数的和。其中蓝色部分各个参数的对应关系如下:
\par
N ~~$\rightarrow$~~ \verb |\my_function:nn|\par
p ~~$\rightarrow$~~ \verb |#1#2|\par
n ~~$\rightarrow$~~ \verb |{**}|(后面大括号中的全部内容【凭据表】)
\ExplSyntaxOff
\ExplSyntaxOn
\cs_set:Npn \my_function:nn #1#2{
两个数的和为:\int_eval:n {#1+#2}
}
\my_function:nn {1}{2}
\ExplSyntaxOff
\textbf{Remark}
同一个函数根据{\bf 参数类型}的不同,可以产生不同的{\bf 变体}。具体的每一函数可以接受的参数类型可以参见文档:interface3, 只需要使用如下的命令即可:
\verb|texdoc interface3|
\subsection{变量和函数的定义和使用方式的反思}
个人的感觉是,由于在 \LaTeX 3中没有类似其他语言中{\bf 类型系统}的概念,就导致你声明的所有变量本质上都是一个token。所以你在使用这个变量的时候就必须要说明你的变量的类型。
对于变量的操作也一样,例如你要给变量赋值,你就必须说明你这个变量的类型还必须使用与之相匹配的赋值函数(int\_set, fp\_set)。
函数也类似,不同的是,你在使用函数的时候必须要声明传入的参数的 ``类型''(n, x,N, V等),然后一个类型对应一个实际的输入参数,一个不能多,一个也不能少。
所以就有了命名规范这么一说,就是说把你的变量的作用域,类型,变量名全部写入变量名中。
LATEX3 命名法提倡将函数的来源以及参数类型、变量的类型编码到其名字内。
\begin{itemize}
\item 可以更方便地让用户区别命令与变量
\item 可以避免不同宏包之间命令的冲突
\item LATEX 并不拥有真正的类型系统,这样的命名方式可以让用户在编程时自行检查错误
\item 只是一套指导意见,并不是强制性的要求
\end{itemize}
\ExplSyntaxOn
\subsection{自带的库函数}
先定义几个变量:\par
\int_new:N \var_a \verb!\int_new:N \var_a!\par
\int_new:N \var_b \verb!\int_new:N \var_b!\par
\int_set:Nn \var_a {5} \verb!\int_set:Nn \var_a {5}!\par
% 检验可不可以赋值和变量的创建同时进行
% \int_set:Nn \Test {1}
% 不可以同时进行
取模函数\par
\int_set:Nn \var_b {\int_mod:nn {\var_a}{3}} \verb!\int_set:Nn \var_b {\int_mod:nn {\var_a}{3}}!\par
\verb!\vara_b! = \int_use:N \var_b\par
\vspace*{2em}
最值函数\par
$\max\{var_a, var_b\}$ = \int_min:nn {\var_a} {\var_b} \hspace*{2em} \verb!\int_min:nn {\var_a} {\var_b}!\par
$\min\{var_a, var_b\}$ = \int_max:nn {\var_a} {\var_b} \hspace*{2em} \verb!\int_max:nn {\var_a} {\var_b}!\par
\vspace*{2em}
自增自减函数\par
a自增的结果:$a++$= \int_incr:N \var_a \int_use:N \var_a \hspace*{2em} \verb!\int_incr:N \var_a \int_use:N \var_a! \par
a自减的结果:$a--$= \int_decr:N \var_a \int_use:N \var_a \hspace*{2em} \verb!\int_decr:N \var_a \int_use:N \var_a! \par
\vspace*{2em}
随机数函数\par
产生$1-n$随机数:\int_rand:n {20} \hspace*{2em} \verb!\int_rand:n {20}! \par
产生$m-n$随机数:\int_rand:nn {34}{89} \hspace*{1.5em} \verb!\int_rand:n {34}{89}!\par
注意:\verb *\int_eval*仅支持$+,-,\times,\backslash$,但是\verb!\fp_eval:n!还支持三目运算符\par
使用样例:\par
\fp_eval:n {1+2>4?10:20} \hspace*{2em} \verb!\fp_eval:n {1+2>4?10:20}! \par
\vspace*{2em}
数学函数\par
exp(), $\sin, \cos, acos, atand, \cdots$\par
\fp_new:N \var_c
\fp_set:Nn \var_c {\c_pi_fp}
% \c_pi_fp表示pi
计算$e^{\pi}$=\fp_eval:n {exp(\var_c)} \hspace*{7.5em}\verb!\fp_eval:n {exp(\var_c)}!\par
计算$\arcsin(\frac{\pi}{10})$ = \fp_eval:n {acos(\var_c/10)} \hspace*{4em} \verb |\fp_eval:n {acos(\var_c/10)}|
\vspace*{2em}
\subsection{字符串}
创建字符串:\str_new:N \str_test \hspace*{2em} \verb!\str_new:N \str_test!\par
字符串初始化:\str_set:Nn \str_test {"你好Hello!@t#&*LaTeX} \hspace*{1em} \verb .\str_set:Nn \str_test {"你好Hello!@#&*LaTeX} .\par
显示字符串: \hspace*{2.5em} \str_test \hspace*{2em} \verb!\str_test!\par
\vspace*{1em}
字符串操作函数\par
\str_put_left:Nn \str_test {这部分内容在左边}
\str_test \par
索引操作(和Python类似)\par
str_test[2] = \str_item:Nn \str_test {2} \hspace*{2.5em} \verb!\str_item:Nn \str_test {2}!\par
str_test[2,-3] = \str_range:Nnn \str_test {-5}{-3} \hspace*{1em} \verb!\str_range:Nnn \str_test {2}{-3}!\par
\subsection{序列}
序列(类似数组):有tl(token list), clist(Comma separete lists), seq(推荐使用)\par
1. 声明(创建一个空的序列lis):\seq_new:N \lis \hspace*{2em} \verb!\seq_new:N \lis!\par
2. 使用set创建初始化序列:\seq_set_split:Nnn \lis_b {,} {a, {ef}, 你好} \hspace*{2em} \verb!\seq_set_split:Nnn \lis_b {,} {a, {ef}, 你好}!\par
3. 显示序列:lis_b = \seq_use:Nn \lis_b {} \hspace*{2em} \verb!\seq_use:Nn \lis!\par
4. 访问特定元素: $lis_b[2]$=\seq_item:Nn \lis_b {2} \hspace*{2em} \verb!\seq_item:Nn \lis_b {1}!\par
\section{流程控制}
\subsection{if语句}
if判断语句本质上是判断布尔表达式的真假,在\LaTeX3中我们使用将bool表达式转化为布尔值的Predicate函数
.在\LaTeX3中一般的数据类型都实现了各自的Predicate,甚至是自己的条件判断函数。我们在这里仅仅只有是用到比较灵活的api\par
\centerline{\textbf{-bool_if:nTF + Predicates}}
调用格式:\par
\hspace*{4em}{\verb *\bool_if:nTF{<bolean expression>} {<true code>} {<false code>}*}\par
一个具体的例子\par
\str_set:Nn \str_d {LaTeX3}
\bool_if:nTF{
!\int_compare_p:n {4<=3<=2} || \str_if_eq_p:nn {latex2} {\str_d}
}{
\str_uppercase:f{\str_d}
% 为真就转为大写
}{
\str_lowercase:f{\str_d}
% 为假就转为小写
}
\newpage
\subsection{for循环}
调用格式:\par
\hspace*{4em}\verb |\int_step_function:nN {<integer>} {\do_somthing}|\par
使用样例:\par
\vspace*{2em}
\parbox[l][10em][l]{0.3\linewidth}{
\int_step_inline:nnnn {10}{2}{20}{
{\par This~is~#1}
}}
\hfill
\parbox[l][10em][l]{0.5\linewidth}{
\textbackslash int_step_inline:nnnn~~ \{10\}\{2\}\{20\}\{\\
\hspace*{4em}\{\textbackslash par~This~is~\#1\}\\
\}
}
\vspace*{2em}
\section{一些绘图例子}
下面这个绘图例子就充分的发挥了 latex 3 的优势:循环判断。尽管原始的tikz中已经了相关的宏能够实现类似的功能,但是实现起来绝对没有 latex 3优雅。
\par
% \begin{tikzpicture}
% \draw[red,thick] (0, 0) circle (5pt);
% \int_step_inline:nnnn {0}{90}{180}{
% \node[minimum~width=1.5mm, fill=blue,
% draw=none, circle, inner~sep=0pt](A) at
% (\fp_eval:n {sin(#1)},
% \fp_eval:n {cos(#1)})
% {};
% }
% \node[red, circle](B) at (0, 0){};
% \end{tikzpicture}
\vspace*{2em}
\begin{figure}[!htb]
\centering
\begin{tikzpicture}
\int_step_inline:nnn {0} {17}{
\fp_set:Nn \l_tmpa_fp {20 * (#1) *\c_one_degree_fp}
\node[minimum~width=1.5mm, fill=blue,
draw=none, circle, inner~sep=0pt] at
(\fp_eval:n {cos(\l_tmpa_fp)},
\fp_eval:n {sin(\l_tmpa_fp)}) {};
}
\end{tikzpicture}
\end{figure}
\vspace*{2em}
\seq_set_split:Nnn \g_my_color_seq {,} {red,cyan,green,brown,orange,purple}
\begin{figure}[!htb]
\centering
\subfloat[Rotate~$60^\circ$]{
\begin{tikzpicture}[scale=0.6]
\fp_step_inline:nnnn {1}{1}{360}{
\draw [\seq_item:Nn \g_my_color_seq {\int_mod:nn{#1}{6}+1}]
(\int_mod:nn{#1-1}{6}*60+#1 \c_colon_str \fp_eval:n{(#1-1)/60}) --
(\int_mod:nn{#1}{6}*60+#1 \c_colon_str #1/60);
}
\end{tikzpicture}
}
\subfloat[Rotate~$30^\circ$]{
\begin{tikzpicture}[scale=0.6]
\fp_step_variable:nnnNn {1}{1}{360}{\i}{
\draw [
\int_case:nn {\int_mod:nn{\i}{6}}
{
{0} {magenta}
{1} {lime}
{2} {olive}
{3} {orange}
{4} {pink}
{5} {violet}
}
]
(\int_mod:nn{\i-1}{6}*30+\i \c_colon_str \fp_eval:n{(\i-1)/60}) --
(\int_mod:nn{\i}{6}*30+\i \c_colon_str \i/60);
}
\end{tikzpicture}
}
\end{figure}
\ExplSyntaxOff
\newpage
\section{最后反思}
目前感觉latex 3 最大的用处就是能够大幅度的简化我们绘制tikz的难度,要是叫我在正文中使用latex 3, 我觉得并不是那么的方便。
\end{document}