Lex和Yacc应用方法(六).语法树打印
草木瓜 20070525
一、序
没有直观的语法树显示界面,理解前面两篇文章会比较难一些。(语法树的示例见
《Lex和Yacc应用教程(四).语法树的应用》) 其实语法树显示程序在Tom Niemann的
《A Compact Guide to Lex & Yacc》文中已有完整的示例,不过我很不喜欢,也许
是无法适应别人的代码习惯吧,这里针对《Lex和Yacc应用方法(五).再识语法树》,
完全重写了打印语法树的程序代码。我不敢说算法有多高明,起码十分便于理解和掌握。
<注:本站文章难免有错误疏漏之处。Lex,Yacc系列文章 http://blog.csdn.net/liwei_cmg/category/207528.aspx>
二、示例代码
老方法,先给出测试通过的完整代码。
liwei.c
001 #include
002 #include
003 #include "node.h"
004 #include "lexya_e.tab.h"
005
006 /* 节点最大文本宽度 */
007 #define MAX_NODE_TEXT_LEN 10
008 /* 节点最大子节点个数 */
009 #define MAX_SUBNODE_COUNT 5
010
011 /* 节点宽度 */
012 #define NODE_WIDTH 4
013
014 #define MAX_NODE_COUNT 100
015
016 /* 排序后 树结点 */
017 #define MAX_TREE_WIDTH 20
018 #define MAX_TREE_DEEP 10
019
020
021 /* 树结点 图信息 */
022 struct NodePoint {
023
024 int x; /* 标准坐标X */
025 int y; /* 标准坐标Y */
026
027 char text[MAX_NODE_TEXT_LEN]; /* 显示内容 */
028 int textoffset1;
029 int textoffset2;
030
031 int parent; /* 父结点索引 */
032 int idx; /* 当前结点索引 */
033
034 Node * node; /* 实际内存树节点 */
035
036 int oppx; /* 相对坐标 */
037 int oppx_mid;/* 相对坐标中值 */
038
039 int childnum; /* 子结点个数 */
040 int child[MAX_SUBNODE_COUNT]; /* 子结点索引 */
041
042 };
043
044 struct NodePoint G_TreeNodePoint[MAX_NODE_COUNT]; /* 树结点全局全量 */
045
046 int G_iNodeCount; //存储树结点个数
047 int G_iNodeParent;//存储树的父结点
048
049 struct NodePoint * G_pTreeNodeOrder[MAX_TREE_DEEP][MAX_TREE_WIDTH]; /* 树结点按层次的排序数组 */
050 int G_iTreeNodeOrderCount[MAX_TREE_DEEP]; /* 每层树结点个数 */
051
052 int G_iDeepCount; /* 层次深度 */
053 int G_iMinNodeXValue; /* 树结点最小x值 */
054 int G_iGraphNum=-1; /* 图个数 */
055
056 /* 函数定义 */
057
058 void GraphNode(Node *, int, int, int);
059 void GraphNode_Set(int, int, int, char *, Node *);
060 void GraphNode_PrintVars();
061
062 void GraphNode_Order();
063 void GraphNode_Adjust();
064 void GraphNode_FillPos();
065
066 void GraphNode_Print();
067
068 struct NodePoint * NodeFind(struct NodePoint *, struct NodePoint *);
069 void NodeAdjust(struct NodePoint *, int tmp);
070
071 void PrintInfo(int, char *);
072 void InitVars();
073
074 int GetOffset(int, int, int);
075
076 char * itoa(int,char*);
077
078 /* 供内部调用函数 */
079 int NodeExecute(Node *p) {
080
081 G_iNodeCount=-1;
082 G_iNodeParent=-1;
083 G_iMinNodeXValue=0;
084
085 InitVars();
086
087 GraphNode(p, 0, 0, G_iNodeParent);
088
089 GraphNode_Order();
090 GraphNode_PrintVars();
091 GraphNode_Adjust();
092 GraphNode_FillPos();
093 GraphNode_PrintVars();
094
095 GraphNode_Print();
096
097 return 0;
098 }
099
100 /* 主递归函数,用于填充全局变量值 */
101 void GraphNode(Node *p, int xoffset, int yoffset, int parent) {
102
103 char sWord[MAX_NODE_TEXT_LEN];
104 char *sNodeText;
105 int i;
106
107 G_iNodeCount++;
108
109 if(parent!=-1) {
110 G_TreeNodePoint[parent].child[G_TreeNodePoint[parent].childnum]=G_iNodeCount;
111 G_TreeNodePoint[parent].childnum++;
112 }
113
114 switch(p->type) {
115
116 case TYPE_CONTENT:
117 sprintf (sWord, "c(%g)", p->content);
118 sNodeText = sWord;
119 GraphNode_Set (xoffset, yoffset, parent, sNodeText, p);
120 break;
121
122 case TYPE_INDEX:
123 sprintf (sWord, "idx(%s)",G_Var[p->index].mark);
124 sNodeText = sWord;
125 GraphNode_Set (xoffset, yoffset, parent, sNodeText, p);
126 break;
127
128 case TYPE_OP:
129 switch(p->op.name){
130 case WHILE: sNodeText = "while"; break;
131 case IF: sNodeText = "if"; break;
132 case FOR: sNodeText = "for"; break;
133 case PRINT: sNodeText = "print"; break;
134 case ';': sNodeText = "[;]"; break;
135 case '=': sNodeText = "[=]"; break;
136 case UMINUS: sNodeText = "[_]"; break;
137 case '+': sNodeText = "[+]"; break;
138 case '-': sNodeText = "[-]"; break;
139 case '*': sNodeText = "[*]"; break;
140 case '/': sNodeText = "[/]"; break;
141 case '<': sNodeText = "[<]"; break;
142 case '>': sNodeText = "[>]"; break;
143 case GE: sNodeText = "[>=]"; break;
144 case LE: sNodeText = "[<=]"; break;
145 case NE: sNodeText = "[!=]"; break;
146 case EQ: sNodeText = "[==]"; break;
147 case AND: sNodeText = "[&&]"; break;
148 case OR: sNodeText = "[||]"; break;
149 case ADD_T: sNodeText = "[++v]"; break;
150 case MUS_T: sNodeText = "[--v]"; break;
151 case ADD_TT: sNodeText = "[v++]"; break;
152 case MUS_TT: sNodeText = "[v--]"; break;
153
154 }
155 GraphNode_Set (xoffset, yoffset, parent, sNodeText, p);
156
157 for (i=0; i
158 GraphNode(p->op.node[i], GetOffset(p->op.num,i+1,2), yoffset+1, GetNodeIndex(p));
159 }
160 break;
161 }
162
163 }
164
165 /* 树结点赋值函数 */
166 void GraphNode_Set(int xoffset, int yoffset, int parent, char * text, Node * p ) {
167
168 int iBaseValue;
169
170 if(parent<=-1)
171 iBaseValue=0;
172 else
173 iBaseValue=G_TreeNodePoint[parent].x;
174
175 G_TreeNodePoint[G_iNodeCount].x = (iBaseValue + xoffset) ;
176 G_TreeNodePoint[G_iNodeCount].y = yoffset;
177
178 strcpy(G_TreeNodePoint[G_iNodeCount].text, text);
179
180 iBaseValue = strlen(text);
181 if(iBaseValue&1) {
182 G_TreeNodePoint[G_iNodeCount].textoffset1 = strlen(text)/2 ;
183 G_TreeNodePoint[G_iNodeCount].textoffset2 = strlen(text) - G_TreeNodePoint[G_iNodeCount].textoffset1 ;
184 }
185 else {
186 G_TreeNodePoint[G_iNodeCount].textoffset1 = strlen(text)/2 - 1;
187 G_TreeNodePoint[G_iNodeCount].textoffset2 = strlen(text) - G_TreeNodePoint[G_iNodeCount].textoffset1 ;
188 }
189
190 G_TreeNodePoint[G_iNodeCount].parent = parent;
191 G_TreeNodePoint[G_iNodeCount].idx = G_iNodeCount;
192 G_TreeNodePoint[G_iNodeCount].node = p;
193
194 G_TreeNodePoint[G_iNodeCount].oppx = 0;
195 G_TreeNodePoint[G_iNodeCount].oppx_mid = 0;
196
197 G_TreeNodePoint[G_iNodeCount].child[0] = 0;
198 G_TreeNodePoint[G_iNodeCount].childnum = 0;
199
200 /* 记录最小值 */
201 if(G_TreeNodePoint[G_iNodeCount].x
203
204 }
205
206 /* 根据树结点层次排序 */
207 void GraphNode_Order() {
208
209 int i;
210 int iDeep;
211
212 G_iDeepCount=-1;
213
214 for(i=0;i<=G_iNodeCount;i++) {
215 G_TreeNodePoint[i].x = G_TreeNodePoint[i].x - G_iMinNodeXValue + 1;
216 iDeep=G_TreeNodePoint[i].y;
217 G_iTreeNodeOrderCount[iDeep]++;
218 G_pTreeNodeOrder[iDeep][G_iTreeNodeOrderCount[iDeep]]=&G_TreeNodePoint[i];
219 if(iDeep>G_iDeepCount)G_iDeepCount=iDeep;
220 }
221
222 }
223
224 /* 填充树结点真实坐标,相对坐标 */
225 void GraphNode_FillPos() {
226
227 int iInt;
228 int iBlank;
229 int idx;
230 int i,j;
231
232 for(j=0;j<=G_iDeepCount;j++) {
233 iBlank = 0;
234 for(i=0;i<=G_iTreeNodeOrderCount[j];i++) {
235 idx=G_pTreeNodeOrder[j][i]->idx;
236 if(i!=0) {
237 iInt = (G_TreeNodePoint[idx].x - G_TreeNodePoint[G_pTreeNodeOrder[j][i-1]->idx].x) * NODE_WIDTH ;
238 iBlank = iInt - G_TreeNodePoint[idx].textoffset1 - G_TreeNodePoint[G_pTreeNodeOrder[j][i-1]->idx].textoffset2;
239 }
240 else {
241 iInt = (G_TreeNodePoint[idx].x) * NODE_WIDTH ;
242 iBlank = iInt - G_TreeNodePoint[idx].textoffset1;
243 }
244 G_TreeNodePoint[idx].oppx = iInt ;
245 G_TreeNodePoint[idx].oppx_mid = iBlank ;
246 }
247 }
248
249 }
250
251 /* 调整树结点位置 */
252 void GraphNode_Adjust() {
253
254 int i,j;
255 int tmp;
256
257 for(i=G_iDeepCount;i>=0;i--)
258
259 for(j=0;j<=G_iTreeNodeOrderCount[i];j++)
260
261 if(j!=G_iTreeNodeOrderCount[i]) {
262
263 if(j==0) {
264 tmp = G_pTreeNodeOrder[i][j]->textoffset1 / NODE_WIDTH ;
265 if(tmp>=1)
266 NodeAdjust(NodeFind(G_pTreeNodeOrder[i][j], G_pTreeNodeOrder[i][j+1]), tmp);
267 }
268
269 tmp = G_pTreeNodeOrder[i][j]->x - G_pTreeNodeOrder[i][j+1]->x + ( G_pTreeNodeOrder[i][j]->textoffset2 + G_pTreeNodeOrder[i][j+1]->textoffset1 ) / NODE_WIDTH + 1;
270 if(tmp>=1)
271 NodeAdjust(NodeFind(G_pTreeNodeOrder[i][j], G_pTreeNodeOrder[i][j+1]), tmp);
272
273 }
274
275 }
276
277 /* 查找需要调整的子树的根结点
278 struct NodePoint * NodeFind(struct NodePoint * p) {
279
280 while(p->parent!=-1 && G_TreeNodePoint[p->parent].child[0]==p->idx) {
281 p=&G_TreeNodePoint[p->parent];
282 }
283 return p;
284
285 }
286 */
287
288 /* 查找需要调整的子树的根结点 */
289 struct NodePoint * NodeFind(struct NodePoint * p1, struct NodePoint * p2) {
290
291 while(p2->parent!=-1 && p1->parent!=p2->parent) {
292 p1=&G_TreeNodePoint[p1->parent];
293 p2=&G_TreeNodePoint[p2->parent];
294 }
295 return p2;
296
297 }
298
299 /* 递归调整坐标 */
300 void NodeAdjust(struct NodePoint * p, int tmp) {
301
302 int i;
303 if(p->childnum==0)
304 p->x=p->x+tmp;
305 else {
306 p->x=p->x+tmp;
307 for(i=0;i<=p->childnum-1;i++)
308 NodeAdjust(&G_TreeNodePoint[p->child[i]], tmp);
309 }
310
311 }
312
313 /* 打印内存变量 */
314 void GraphNode_PrintVars() {
315
316 printf("/n");
317 int i,j;
318 for(i=0;i<=G_iNodeCount;i++) {
319 printf("ID:%2d x:%2d y:%2d txt:%6s ofs:%d/%d rx:%2d b:%2d pa:%2d num:%2d child:",
320 i,
321 G_TreeNodePoint[i].x,
322 G_TreeNodePoint[i].y,
323 G_TreeNodePoint[i].text,
324 G_TreeNodePoint[i].textoffset1,
325 G_TreeNodePoint[i].textoffset2,
326 G_TreeNodePoint[i].oppx,
327 G_TreeNodePoint[i].oppx_mid,
328 G_TreeNodePoint[i].parent,
329 G_TreeNodePoint[i].childnum
330 );
331 for(j=0;j<=G_TreeNodePoint[i].childnum-1;j++)
332 printf("%d ",G_TreeNodePoint[i].child[j]);
333 printf("/n");
334 }
335 printf("/n");
336 }
337
338 /* 打印语法树 */
339 void GraphNode_Print() {
340
341 G_iGraphNum++;
342 printf("
343
344 int idx;
345 int i,j;
346
347 for(j=0;j<=G_iDeepCount;j++) {
348
349 /* 打印首行结点 [] */
350 for(i=0;i<=G_iTreeNodeOrderCount[j];i++) {
351 idx=G_pTreeNodeOrder[j][i]->idx;
352 PrintInfo( G_TreeNodePoint[idx].oppx_mid , G_TreeNodePoint[idx].text);
353 }
354 printf("/n");
355
356 if(j==G_iDeepCount)return; /* 结束 */
357
358 /* 打印第二行分隔线 | */
359 int iHave=0;
360 for(i=0;i<=G_iTreeNodeOrderCount[j];i++) {
361 idx=G_pTreeNodeOrder[j][i]->idx;
362 if(G_pTreeNodeOrder[j][i]->childnum) {
363 if(iHave==0)
364 PrintInfo( G_TreeNodePoint[idx].oppx , "|");
365 else
366 PrintInfo( G_TreeNodePoint[idx].oppx - 1 , "|");
367 iHave=1;
368 }
369 else
370 PrintInfo( G_TreeNodePoint[idx].oppx , "");
371 }
372 printf("/n");
373
374 /* 打印第三行连接线 ------ */
375 for(i=0;i<=G_iTreeNodeOrderCount[j+1];i++) {
376 idx=G_pTreeNodeOrder[j+1][i]->idx;
377 int k;
378 if(i!=0 && G_pTreeNodeOrder[j+1][i]->parent==G_pTreeNodeOrder[j+1][i-1]->parent) {
379 for(k=0;k<=G_pTreeNodeOrder[j+1][i]->oppx - 2; k++)
380 printf("-");
381 printf("|");
382 }
383 else if(i==0) {
384 PrintInfo( G_TreeNodePoint[idx].oppx , "|");
385 }
386 else {
387 PrintInfo( G_TreeNodePoint[idx].oppx - 1 , "|");
388 }
389 }
390 printf("/n");
391
392 /* 打印第四行分割连接线 | */
393 for(i=0;i<=G_iTreeNodeOrderCount[j+1];i++) {
394 idx=G_pTreeNodeOrder[j+1][i]->idx;
395 if(i==0)
396 PrintInfo( G_TreeNodePoint[idx].oppx , "|");
397 else
398 PrintInfo( G_TreeNodePoint[idx].oppx - 1 , "|");
399 }
400 printf("/n");
401
402 }
403
404
405 }
406
407 /* 获取节点位移 */
408 int GetOffset(int count, int idx, int base) {
409
410 if(count&1)
411 return (idx-(count+1)/2)*base;
412 else
413 return idx*base-(count+1)*base/2;
414
415 }
416
417 /* 根据节点地址获取内存索引 */
418 int GetNodeIndex(Node * p) {
419
420 int i;
421 for(i=G_iNodeCount;i>=0;i--) {
422 if(p==G_TreeNodePoint[i].node)return G_TreeNodePoint[i].idx;
423 }
424
425 }
426
427 /* 初始化变量 */
428 void InitVars() {
429
430 /*
431 int i,j;
432 for(j=0;j<=MAX_TREE_DEEP-1;j++)
433 for(i=0;i<=MAX_TREE_WIDTH-1;i++)
434 G_pTreeNodeOrder[j][i]=0;
435 */
436
437 int i;
438 for(i=0;i<=MAX_TREE_DEEP-1;i++)
439 G_iTreeNodeOrderCount[i]=-1;
440 }
441
442 /* 打印固定信息 */
443 void PrintInfo(int val, char * str) {
444
445 char sInt[10];
446 char sPrint[20];
447 itoa( val , sInt);
448 strcpy(sPrint, "%");
449 strcat(sPrint, sInt);
450 strcat(sPrint,"s");
451 printf(sPrint,"");
452 printf(str);
453
454 }
455
456 /* int 转 char */
457 char * itoa(int n, char *buffer) {
458
459 int i=0,j=0;
460 int iTemp; /* 临时int */
461 char cTemp; /* 临时char */
462
463 do
464 {
465 iTemp=n%10;
466 buffer[j++]=iTemp+'0';
467 n=n/10;
468 }while(n>0);
469
470 for(i=0;i
472 cTemp=buffer[i];
473 buffer[i]=buffer[j-i-1];
474 buffer[j-i-1]=cTemp;
475 }
476 buffer[j]='/0';
477 return buffer;
478
479 }
这个文件需要与《Lex和Yacc应用方法(五).再识语法树》中提到的node.h,lexya_e.l,lexya_e.y
一起编译,生成可执行文件。我这里编译的可执行文件名是graph,下文皆使用这个名称。
三、总体思路说明
打印语法树的难点在于,要通过十分原始的printf来打印,而且操作对象是任意的
递归树。对齐和空白是十分令人头痛的事情,比较遗憾的是,没能读懂Tom Niemann的算
法思想(也许根本就不想去花时间读),这里姑且凭空想象,从0开始。
本示例代码没有进行什么优化,个人感觉重点不在于此,主要是描述整体思路,实现
这一非常有趣的事情。
示例的执行过程可大致划分为四大部分:构造内存树,排序内存树,调整内存树和
打印内存树四个部分。
A.构造内存树
顾名思义,就是在内存中再次构造一棵树,这个树不同于《Lex和Yacc应用方法(五).再识语法树》
中的语法树。我们这里的树记录的都是打印显示信息,确切是叫显示树。不过在结构上,
两者是很类似的。
行<22-42>是内存结点的结构定义。
行<44>是全局内存变量,存储所有的内存结点。
行<100> GraphNode 是构造内存树的主函数,构造方法与语法树相同。主要调用
GraphNode_Set进行赋值。这里有个细节,构造过程会记录最小的x。
下面对结点里相对难以理解的内容含义进行说明。
结构里除了oppx,oppx_mid之外,均需要在GraphNode_Set赋值。
x 表述的原始坐标值,跟据本结点和子结点的位置确定。如下图(图中可能显示不对齐,
可复制到记事本观看),
5
|
|---|
4 6
| |
| |---|
4 5 7
第一结点如x为5,第一个子节点便是-1,后面的子节点就是+1,依此类推,主要
保证树的对称性。GetOffset这个函数就是用来取节点的位移值。当然用这些值打印
是很可出现交叉现象的,这个问题我们将在后面讨论。交叉图如下:
5
|
|---|
4 6
| |
|---|---|
3 55 7
y 表述的是节点深度
textoffset1 文本前位移
textoffset2 文本后位移
[=]
|
|-------|
| |
idx(b) c(-1)
这两个offset表述了“|”的位置。“|”前为offset1,“|”后包括“|”为offset2
[=] offset1 为 1 offset2 为 2
idx(b) offset1 为2 offset2 为4
c(-1) offset1 为2 offset2 为3
offset会在调整内存树和打印内存树时使用。
B.排序内存树
这个过程比较简单,是将构造完的所有内存结点,按深度依此排序到G_pTreeNodeOrder中,
代码见行<207>。
在构造内存树时,我们传递的基本位移值是0,整个树中肯定会有负值,显然负值是不能
打印出来的,而且在GraphNode_Set中已经存储了整个树的最小值。在排序的过程,对所有树
结点也进行了坐标平移。
C.调整内存树
在介绍“构造内存树”时提及,会存在树结点交叉的现象,这里采用的算法是从最深层,最
左边节点依此遍历处理。(当然也可从最高层,最左边节点依此遍历)
处理的方法是找出要调整的结点的最大子树,此子树不能包括参考结点,对最大子树所有节
点进行向右平移。
举个例子,如下图:
while
|
|---------------|
| |
[<=] [;]
| |
|-------| |-----------|
| | | |
idx(i) c(2) print [=]
| |
| |-------|
| | |
idx(i) idx(i) [+]
|
|-------|
| |
idx(i) c(1)
现在若处理到c(2),发现print和c(2)严重重合,通过比较两者x和offset,算出调整
值(具体算法可查看详细代码<261-273>)。要调整print的结点,需要向上追溯,真至找出
最大子树,这个子树不能包括参考节点c(2)。具体算法可参见行<289>的NodeFind函数。
查找最大子树的根结点后,调用<299>递归函数NodeAdjust,进行平移。
保证无交叉后,还需要根据x,textoffset1,textoffset2和#define NODE_WIDTH,算
出绝对坐标值和树结点间相对值,即oppx,oppx_mid。具体算法见<225>GraphNode_FillPos。
这些细节需要结合“打印内存树”理解。
这里需要明确一十分重要的规则,每个“|”前面的字符都是NODE_WIDTH的整数倍,如上
图中while是12,[<=]是8,idx(i)是4 等等。
没有这个规则,是很难打印出标准,对称且美观的语法图。
D.打印内存树
行<339>GraphNode_Print,负责将内存变量绘制到屏幕上。由于采用printf这么
原始的命令行方式,需要从顶部开始绘图。具体可参考上述重要规则进行理解。
四、结束
本示例也提供了内存变量的打印函数(GraphNode_PrintVars),用于调试分析。
输入文件:
if(1>1||2>2)print(1);
else
if(3>1&&2>2)print(2);
else
print(3);
编译运行:
./graph < input
if
|
|---------------|-------------------|
| | |
[||] print if
| | |
|---------------| | |---------------|-------|
| | | | | |
[>] [>] c(1) [&&] print print
| | | | |
|-------| |-------| |---------------| | |
| | | | | | | |
c(1) c(1) c(2) c(2) [>] [>] c(2) c(3)
| |
|-------| |-------|
| | | |
c(3) c(1) c(2) c(2)
此外也测试过更复杂的输入:
k=9;
if((1>1)||(-9>-1))
for(i=0;i<=9;i=i+1)
print(i);
else
if(3>1&&2>1) {
for(j=-1.1;j<=3;j++)
print(j);
for(jdd=1;jdd<=3;++jdd)
print(jdd);
while(k<=9) {
print(k++);
print(++k);
}
if(1==1) {
d=9;
d=d--;
print(d);
#dd
}
}
输出内容太多,不再列出,用户可自行尝试。页面显示的常有错位,最好复制到记
事本或UltraEdit查看。