Lex和Yacc使用教程(六).语法树打印

 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; iop.num; 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    202 
    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("/n", G_iGraphNum);
    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    471    {
    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查看。

你可能感兴趣的:(C++/C/C#)