• print语句的续行问题


    用了很久的y_tab程序,有一个地方特别不满意。就是每一条print语句都自动换行了。比如输出数组,

    
    for(i=0; i<10; i++) a[i]=i;
    for(i=0; i<10; i++) print a[i];
    
    
    • 1
    • 2
    • 3
    • 4

    解释程序会自动分10行输出a[i]。如果希望输出在同一行上,对不起,没办法。所以又把代码拿出来,想改一改。

    希望做成这样,如果print语句的最后一个元素是字符串,且是单个退格符"\b",则控制print语句不换行。

    谁知好久不碰这个源码,生疏了,随便一碰程序就崩了。又费了很大劲看代码。

    print语句编译之后最后生成了ps语法树::

    ps: printop ';' {
                            struct node *p;
                            p= getnode();
                            p->node_type= NT_PS;
                            p->left = (struct node*)$1;
                            p->medium = NULL;
                            p->right= NULL;
                            $$ = p; }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    语法节点标记为NT_PS,说明这个语法树子树是个打印语句,它只有左指针有内容,指向实际要输出的数据。

    printop包含数字和字符串,其中的字符串部分是这样的:

    printop :
    	...
            | PRINT STR  {
                            struct node *p, *q;
                            p= getnode();
                            p->node_type= NT_STRING;
                            p->left = NULL;
                            p->medium = (struct node *)$2;
                            p->right= NULL;
                            q= p;
                            p= getnode();
                            p->node_type= NT_POP;
                            p->left = q;
                            p->medium = NULL;
                            p->right= NULL;
                            $$ = p;
                            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    它没有直接绑定数据元素,而是生成了一个NT_POP节点,在节点左指针中存放数据元素,这里是字符串数据,NT_STRING节点。

    printop的递归生成过程是,

    printop :
    	...
            | printop ','  STR {
                           struct node *p, *q;
                           p= getnode();
                           p->node_type= NT_STRING;
                           p->left = NULL;
                           p->medium = (struct node *)$3;
                           p->right= NULL;
                           q= p;
                           p= getnode();
                           p->node_type= NT_POP;
                           p->left = (struct node*) $1;
                           p->medium = q;
                           p->right= NULL;
                           $$ = p;
                           }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    它把前序的printop放在左指针上,随后数据放在中间指针上,右指针不用。所以除了第一个数据在左指针上,此外数据都在中间指针上,但是语句可以只输出一个数据,中间指针可以为空。

    所以从ps语句起始节点查找句末续行符的语法是:

    	s->left->medium
    
    
    • 1
    • 2

    这个medium可以为空,但如果有句末续行符,这个print语句至少有2个元素,所以只要看medium就可以了。

    到这里,已经把数据结构理解清楚了。打算把原来的解释执行代码,

            case NT_PS:
                    do_e_print_op(s->left, exception); EXCEPTION_CHK;
    				printf("\n"); 
                    return NULL;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    改为

            case NT_PS:
                    do_e_print_op(s->left, exception); EXCEPTION_CHK;
                    if (s->left->medium &&s->left->medium->node_type==NT_STRING){
                            if (strcmp("\b", (const char*)s->left->medium->medium)==0) {
                                    s->right=(YYSTYPE)1;
                            }
                    }
                    if(s->right==NULL) printf("\n"); else printf(" ");
                    return NULL;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    NT_PS的right指针空闲,在这就地利用一下。看似这样修改已经大功告成了。不料编译一运行程序又崩了。

    进一步查看代码,发现字符串节点中存放的不是字符串指针!引入用户自定义函数后,字符串不再存放在语法树结点,而是统一放入函数的字符串常量表中。而全局也有一个这样的没有名字的函数框架。

    int yylex() {
    ...
            begin = cp;
            if (*cp=='"') {
                    int len;
                    char *sp;
                    do { ++cp; } while (*cp!='"' && isprint(*cp) && cp<&buffer[bc]);
                    len = cp - begin-1;
                    sp = (char *)malloc(len+1);
                    strncpy(sp, begin+1, len);
                    sp[len] ='\0';
                    strtrans(sp);
                    funxat_compile[dmc].strtab[
                            funxat_compile[dmc].strcount]= sp;
                    yylval = (YYSTYPE)funxat_compile[dmc].strcount;
                    funxat_compile[dmc].strcount++;
                    while(*cp !='"' && cp<&buffer[bc]) ++cp;
                    begin = cp+1;
                    return STR;
            }
    ...
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这里的funxat_compile[0].strtab[]就是这个全局的字符串常量表。而语法节点中存的是这个表的索引值。

    void init_whatever()
    {
            int i;
            int k;
    
            for(k=0; k<2; k++) {
                    memset(&symtab_compile[k],0,sizeof(struct symtab));
                    memset(&funxat_compile[k],0,sizeof(struct funxat));
                    for(i=0; i<MAXARRAY; i++) {
                            symtab_compile[k].avartab[i] =
                                    symtab_compile[k].array[i];
                    }
    
                    symtab_compile[k].used_const0=0;
                    funxat_compile[k].strtab = symtab_compile[k].strtab;
                    funxat_compile[k].consttab = symtab_compile[k].consttab;
                    funxat_compile[k].labeltab = symtab_compile[k].gototab;
                    funxat_compile[k].consttab[0].type = 0;
                    funxat_compile[k].consttab[0].u.value = 1;
                    funxat_compile[k].constcount = 1;
                    funxat_compile[k].labelcount = 0;
            }
            tmpnamecount=0;
            dmc=0;
            top=0;
            swc= -1;
            funxvt_main = &stk->e[0];
            funxvt_main->func = &funxat_compile[0];
            funxvt_main->bp = symtab_compile[0].var;
            funxvt_main->abp = symtab_compile[0].avartab;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    程序初始化的时候,栈上生成了一个funxvt类型的e[0]。这是一个函数运行框架。funxvt代表函数的动态数据,只有在函数运行时分配。funxat代表函数的静态数据,包括语法树(即代码),常数表和字符串表。funxvt中的func指针指向这个funxat结构。

    所以,最后引用的字符串要到运行栈的栈顶,找到当前正在执行的函数,通过它的func指针找到它的字符串常量表,然后才能引用到这个字符串。最后代码改为:

            case NT_PS:
                    do_e_print_op(s->left, exception); EXCEPTION_CHK;
                    if (s-right==NULL&&s->left->medium &&s->left->medium->node_type==NT_STRING){
                            if (strcmp("\b", 
                                    stk->e[top].func->strtab[(int)s->left->medium->medium])==0) {
                                    s->right=(YYSTYPE)1;
                            }
                    }
                    if(s->right==NULL) printf("\n"); else printf(" ");
                    return NULL;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    再次编译之后运行,

    for(i=0; i<10; i++) a[i]=i;
    for(i=0; i<10; i++) print a[i];
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    for(i=0; i<10; i++) print a[i],"\b"; print "END.";
    0 1 2 3 4 5 6 7 8 9 END.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这次终于修改成功了。

  • 相关阅读:
    【C/C++内功心法】剖析预处理过程,详解预处理指令,提升C/C++内功
    simulink创建全局变量&MATLAB function引用
    Hadoop3:MapReduce源码解读之Map阶段的数据输入过程整体概览(0)
    集美大学 - 2840 - 实验2-1
    OpenGL ES 学习(二) -- 渲染模式和GLSL
    完全分布式运行模式
    数据库设计三大范式
    基础课4——客服中心管理者面临的挑战
    Prompt Engineering(提示工程)
    【SQL注入】宽字节注入:原理、利用
  • 原文地址:https://blog.csdn.net/aaasssdddd96/article/details/133463217