LaTeX中的TikZ绘图功能很强,为了方便以后用到的时候好查询,所以这里把自己绘制过的内容记录在CSDN博客中。
在以下环境测试过
原图选自裘巍老师所著的《编译器设计之路》。下面介绍我用LaTeX绘制的过程、代码与一些说明。
过程介绍:
完整代码
\documentclass{article}
%
\usepackage{ctex}
\usepackage{geometry}
\usepackage[dvipsnames, svgnames, x11names]{xcolor}
\usepackage{tikz}
%
\usetikzlibrary{positioning, arrows.meta}
%
\begin{document}
%
% 定义方框样式
\tikzset{
rect1/.style = {
shape = rectangle,
draw = green,
text width = 3cm,
align = center,
minimum height = 1cm,
}
}
% 定义箭头样式
\tikzset{
arrow1/.style = {
draw = purple, thick, -{Latex[length = 4mm, width = 1.5mm]},
}
}
% 双箭头
\tikzset{
arrow2/.style = {
draw = purple, thick, {Latex[length = 4mm, width = 1.5mm]}-{Latex[length = 4mm, width = 1.5mm]},
}
}
\begin{center}
\begin{tikzpicture}
% 绘制中间方框
\node[rect1, fill = green!60!white](词法){词法分析};
\node[rect1, fill = green!40!white, below = of 词法](语法){语法分析};
\node[rect1, fill = green!20!white, below = of 语法, text width = 5cm](语义中间){语义分析、中间表示生成};
\node[rect1, fill = green!60!black, below = of 语义中间](优化){\color{white}代码优化};
\node[rect1, fill = green!30!black, below = of 优化](生成){\color{white}代码生成};
% 绘制两侧方框
\node[rectangle, fill = red!20!white, draw = red, text width = 0.7cm, minimum height = 4cm, align = center, left = 2cm of 语义中间](符号){符号表管理};
\node[rectangle, fill = blue!20!white, draw = blue, text width = 0.7cm, minimum height = 4cm, align = center, right = 2cm of 语义中间](出错处理){出错处理};
% 绘制中间连线
\draw[arrow1](0, 50pt)node[right, yshift = -15pt]{输入源程序} -- (词法);
\draw[arrow1](词法) -- node[right]{单词流}(语法);
\draw[arrow1](语法) -- node[right]{文法}(语义中间);
\draw[arrow1](语义中间) -- node[right]{中间表示}(优化);
\draw[arrow1](优化) -- node[right]{优化后中间表示}(生成);
\draw[arrow1](生成) -- node[right, yshift = 5pt, xshift = 5pt]{目标代码}++(0, -50pt);
% 绘制两侧连线
\draw[arrow2](符号) -- (词法.west);
\draw[arrow2](符号) -- (语法.west);
\draw[arrow2](符号) -- (语义中间);
\draw[arrow2](符号) -- (优化.west);
\draw[arrow2](符号) -- (生成.west);
\draw[arrow2](出错处理) -- (词法.east);
\draw[arrow2](出错处理) -- (语法.east);
\draw[arrow2](出错处理) -- (语义中间);
\draw[arrow2](出错处理) -- (优化.east);
\draw[arrow2](出错处理) -- (生成.east);
% 绘制虚线框
\draw[thick, dashed, draw = purple](-125pt, 27pt)node[below right]{编译器前端} -- (125pt, 27pt) -- (125pt, -250pt) -- (-125pt, -250pt)node[above right]{编译器后端} -- (-125pt, 27pt);
\draw[thick, dashed, draw = purple](-125pt, -135pt) -- (125pt, -135pt);
\end{tikzpicture}
\\\heiti 图 1-2 编译器结构图\songti
\end{center}
%
\end{document}
\node[样式](名称){标题};
注意结尾的分号,绘制线条的语句最后先要加分号。名称是调用时使用,可以用汉字,标题是显示出来的文字,二者不一样。
2. 样式的内容挺多的,方框中一般会用到形状(shape)、边框颜色(draw)、填充色(fill)、宽度(text width)、最小高度(minimum height)。颜色方面可以参考xcolor宏包的说明文档。长度单位有好几个,我一般常用的是pt与cm,1pt=0.351mm。
3. 位置的表示也包含在样式定义中,一般形式是:方位 = 尺寸 of 参考对象。方位包括left、right、below、above、below left、below right、above left、above right 8种,要注意below left这样的顺序不能颠倒。
4. 这里我们绘制箭头的库选用的是arrows.meta(24种箭头),这个库比arrows(14种)要丰富些。箭头的形状有很多种,详情请参考arrows.meta的24种箭头。本例中箭头样式选的是Latex样式,其后的length = 4mm, width = 1.5mm代表箭头的长宽,可以通过这两个参数调整箭头的锋锐程度。大家可以根据需要设置。
5. 在线条上加文字的方法是通过加入node来实现,注意node在连线(即–)中的位置,如果放在–左边就表示文字是在线段的左边,反之在右边。实际上我们可以通过xshift、yshift两个参数来随意调整位置。
6. 语句:
\draw[arrow1](0, 50pt)node[right, yshift = -15pt]{输入源程序} -- (词法);
其中的(0, 50pt)是指在绝对坐标0, 50pt处开始绘制,图形坐标的原点(0, 0)就是代码中第一个方框的中心处。如果我们在(0, 50pt)前加两个加号,例如++(0, 50pt),那就变成了相对坐标,是相对于上一个节点。例如语句:
\draw[arrow1](生成) -- node[right, yshift = 5pt, xshift = 5pt]{目标代码}++(0, -50pt);
++(0, -50pt)的意思就是相对于“生成”这个节点向下移动50pt的坐标。当我们不清楚绝对坐标时就用相对坐标来绘制,挺方便的。
完整代码
\documentclass{article}
%
\usepackage{ctex}
\usepackage{geometry}
\usepackage[dvipsnames, svgnames, x11names]{xcolor}
\usepackage{tikz}
%
\usetikzlibrary{positioning, arrows.meta, calc}
%
% 定义箭头样式
\tikzset{
arrow1/.style = {
draw = purple, thick, -{Latex[length = 4mm, width = 1.5mm]},
}
}
% 非终端
\tikzset{
nonterminal/.style = {
rectangle,
align = center,
minimum size = 6mm,
very thick,
draw = red!50!black!50,
top color = white,
bottom color = red!50!black!20,
}
}
% 终端
\tikzset{
terminal/.style = {
rectangle,
align = center,
minimum size = 6mm,
rounded corners = 3mm,
very thick,
draw = black!50,
top color = white,
bottom color = black!20,
}
}
%
\begin{document}
\begin{center}
\begin{tikzpicture}[node distance = 0.5cm]
\node[terminal, text width = 1.6cm](uses){USES};
\node[nonterminal, text width = 1.6cm, right = 1cm of uses](unit){单元名};
\node[terminal, right = 1cm of unit](semicolon){;};
\node[terminal, below = of unit](comma){,};
\draw[arrow1](uses)--(unit);
\draw[arrow1](unit)--(semicolon);
\draw[arrow1](semicolon)--++(30pt, 0);
\draw[arrow1]($ (unit.east)+(3mm, 0) $)|-(comma);
\draw[arrow1](comma)-|($ (unit.west)-(6mm, 0) $);
\end{tikzpicture}
\end{center}
\begin{center}
\begin{tikzpicture}
\node[terminal, text width = 1.6cm](var){VAR};
\node[nonterminal, text width = 1.6cm, right = 1cm of var](list){变量标识符列表};
\node[terminal, right = 1cm of list](colon){:};
\node[nonterminal, text width = 1.6cm, right = 1cm of colon](type){类型};
\node[terminal, right = 1cm of type](semicolon1){;};
\node[terminal, below = of list](semicolon2){;};
\node[terminal, below = 2cm of colon](semicolon3){;};
\draw[arrow1](var)--(list);
\draw[arrow1](list)--(colon);
\draw[arrow1](colon)--(type);
\draw[arrow1](type)--(semicolon1);
\draw[arrow1](semicolon1)--++(30pt, 0);
\draw[arrow1]($ (list.east)+(3mm, 0) $)|-(semicolon2);
\draw[arrow1](semicolon2)-|($ (list.west)-(6mm, 0) $);
\draw[arrow1]($ (type.east)+(3mm, 0) $)|-(semicolon3);
\draw[arrow1](semicolon3)-|($ (list.west)-(6mm, 0) $);
\end{tikzpicture}
\end{center}
\end{document}
效果如下:
说明
1. 本例中因为使用了一些计算,所以引入了TikZ中的calc库,大家在usetikzlibrary命令处可以看见。
2. 圆角矩形的绘制就是加入rounded corners参数,当文字很少时,如上例中的分号与逗号,这个圆角矩形就变成了圆圈(实际上,这与minimum size的设置也有关)。
3. 从线段中间开始画线的方法,大家在代码中可以看见,代码意思很明显,不再解释。
4. 通过top color与bottom color的设置,实现了过渡效果。
5. 在语句“…(3mm, 0) $)|-(comma);”中,两个节点间的“|-”表示线段是先画竖线,再画横线,画多长,系统会自动判断。
6. 在“变量标识符列表”的定义语句中设置了text width参数,如果不设置的话,文字就不会换行。
代码
\hspace{-3cm}{
\begin{minipage}{15.4cm}
\begin{tikzpicture}[node distance = 0.7cm]
\node[terminal](function){FUNCTION};
\node[nonterminal, right = of function](name1){函数名};
\node[terminal, right = of name1](left){(};
\node[terminal, right = of left](var){VAR};
\node[nonterminal, right = of var](para){形参名};
\node[terminal, right = of para](colon1){:};
\node[nonterminal, right = of colon1](type1){类型名};
\node[terminal, right = of type1](right){)};
\node[terminal, right = of right](colon2){:};
\node[nonterminal, right = of colon2](type2){类型名};
\node[terminal, right = of type2](semicolon1){;};
\node[terminal, below = of para](semicolon2){;};
% ----------------------------------------------------------
\draw[arrow1](function)--(name1);
\draw[arrow1](name1)--(left);
\draw[arrow1](left)--(var);
\draw[arrow1](var)--(para);
\draw[arrow1](para)--(colon1);
\draw[arrow1](colon1)--(type1);
\draw[arrow1](type1)--(right);
\draw[arrow1](right)--(colon2);
\draw[arrow1](colon2)--(type2);
\draw[arrow1](type2)--(semicolon1);
\draw[arrow1]($ (var.west)+(-3mm, 0) $)--++(0, 6mm)-|($ (var.east)+(3mm, 0) $);
\draw[arrow1]($ (type1.east)+(3mm, 0) $)|-(semicolon2);
\draw[arrow1](semicolon2)-|($ (var.west)-(3mm, 0) $);
\draw[arrow1]($ (name1.east)+(3mm, 0) $)--++(0, -20mm)-|($ (right.east)+(3mm, 0) $);
% ----------------------------------------------------------
\node[nonterminal, below = 2.7cm of function, xshift = 1cm](description){说明部分};
\node[terminal, right = of description](begin){BEGIN};
\node[nonterminal, right = of begin, text width = 1.6cm](statement){语句};
\node[terminal, right = of statement](semicolon3){;};
\node[terminal, right = of semicolon3, text width = 1.6cm](end){END};
\node[terminal, right = of end](semicolon4){;};
% ----------------------------------------------------------
\draw[arrow1](description)--(begin);
\draw[arrow1](begin)--(statement);
\draw[arrow1](statement)--(semicolon3);
\draw[arrow1](semicolon3)--(end);
\draw[arrow1](end)--(semicolon4);
\draw[arrow1](semicolon4)--++(30pt, 0);
\draw[arrow1](semicolon1)--++(0, -2.5cm)-|(description);
\draw[arrow1]($ (semicolon3.east)+(3mm, 0) $)--++(0, -6mm)-|($ (begin.east)+(3mm, 0) $);
\end{tikzpicture}
\end{minipage}
效果如下:
说明
1. 此例的导言区部分与示例二的一样。
2. 因为这幅图比较长,正常的A4纸显示不全,所以我用\hspace{-3cm}命令向左边距移了3厘米以保证完整显示。