print语句的续行问题

文章讲述了作者如何在编程中遇到问题,即希望print语句在同一行输出。通过分析代码的语法树结构,尤其是字符串节点和NT_PS节点,作者发现需要访问运行时栈的字符串常量表。经过调整和测试,最终成功实现了按需控制print语句的换行行为。
摘要由CSDN通过智能技术生成

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


for(i=0; i<10; i++) a[i]=i;
for(i=0; i<10; i++) print a[i];

解释程序会自动分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; }

语法节点标记为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;
                        }

它没有直接绑定数据元素,而是生成了一个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;
                       }

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

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

	s->left->medium

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

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

        case NT_PS:
                do_e_print_op(s->left, exception); EXCEPTION_CHK;
				printf("\n"); 
                return NULL;

改为

        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;

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;
        }
...
}


这里的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;
}

程序初始化的时候,栈上生成了一个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;

再次编译之后运行,

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.

这次终于修改成功了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值