用了很久的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.
这次终于修改成功了。