Lex和Bison文件可以扩展以处理上下文敏感信息.例如,为简单起见,假设我们想要求这样,我们要求变量在引用前必须先声明.因此,语法分析器应该可以把变量引用与变量声明对比.(看是否一致)
为了完成这些,一种方法是当语法分析到一个变量构造一个变量列表,而当分析到变量引用时从变量列表中检测变量引用.这样的列表称为符号表.符号表可以用链表,树和哈希表实现.
我们修改Lex文件,把标识符的字符串信息赋给全局变量yylval,因为这些信息将在属性文法用到.
符号表模块
为了保存属性文法所需要的信息,我们构造一个符号表.一个符号表包含关于各种程序设计语言结构的属性的环境信息.特别地,应包含类型和作用域信息.
符号表将被作为一个模块包含在yacc/bison的文件中.
Simple的符号表包含一条标识符的链表,初始为空.这里是一个链表结点的声明,初始链表为空,和两个操作:putsym把一个标识符放入表中,而getsym返回一个与标识符一致的符号表项的指针.
struct symrec
{
char * name; /*name of symbol*/
struct symrec *next; /*link field*/
};
typedef struct symrec symrec;
symrec *sym_table = (symrec *)0;
symrec *putsym();
symrec *getsym();
symrec*
putsym ( char *sym_name) /*头插入*/
{
symrec *ptr;
ptr = (symrec *)malloc(sizeof(symrec));
ptr -> name =(char *)malloc(strlen(stm_name)+1);
strcpy(ptr->name,sym_name);
ptr->next = (struct symrec *)sym_table;
sym_table = ptr;
return ptr;
}
symrec *
getsym ( char *sym_name)
{
symrec *ptr;
for (ptr=sym_table;ptr!=(symrec *)0;
ptr = (symrec *)ptr->next)
if(strcmp(ptr->name,sym_name)==0)
return ptr;
return 0;
}
语法分析器修改
Yacc/Bison文件被修改成包含符号表和其相应的操作函数,以执行把标识符装入到符号表中和上下文检测.
%{
#include <stdlib.h> /*For malloc in symbol table*/
#include <string.h> /*For strcmp in symbol table*/
#include <stdio.h> /*For error message */
#include "ST.h" /* The Symbol Table Module */
#define YYDEBUG 1 /*For debugging */
install ( char *sym_name)
{
symrec *s;
s = getsym (sym_name);
if (s==0)
s = putsym (sym_name);
else{errors++;
printf ( "%s is already defined/n",sym_name);
}
}
context_check( char *sym_name)
{if ( getsym( sym_name ) = =0)
printf( "%s is an undeclared identifier/n",sym_name);
}
%}
Parser declarations
%%
Grammar rules and actions
%%
C subroutines
因为扫瞄器(Lex文件)将返回标识符,所以要求用静态语义记录保存其值,而IDENT与这静态语义记录关联.
C declarations
%union{ /*SEMANTIC RECORD*/
char *id; /*For returning identifier */
}
%token INT SKIP IF THEN ELSE FI WHILE DO END
%token <id> IDENT /* Simple identifier */
%left '-' '+'
%left '*' '/'
%right '^'
%%
Grammar rules and actions
%%
C subroutines
非终结符...
上下文无关文法修改包含intall和上下文检测函数调用.$n是Yacc的内部变量,它指引用与产生式右部第n个符号一致的语义记录.$$指与产生式左部非终结符一致的语义录.
C and parser declarations
%%
...
declarations : /* empty */
| INTEGER id_seq IDENTIFIER '.' { install( $3); }
;
id_seq : /* empty */
| id_seq IDENTIFIER ',' {install($2);}
;
command : SKIP
| READ IDENTIFIER {context_check( $2 );}
| IDENT ASSGNOP exp {context_check( $2 );}
...
exp : INT
| IDENT { context_check( $2 ); }
...
%%
C subroutines
语法分析树的实现隐式包含关于一个变量在表达式引用之前是否已被赋值的注释信息.语法树的注释信息被收集到符号表中.
扫瞄器修改
扫瞄器必须修改成返回与标识符相关联的字符常量.(记号的语义值).语义值通过yylval返回.yylval的类型是一个在语法分析文件中用%union定义的联合体.语义值必须存放在合适的联合体成员中.因为联合体声明如下:
%union{ char *id;
}
所以语义值是由全局变量yytext(包含输入文本)复制到yylval.id中.因为函数strdup被用到(string.h中定义),所以这个头文件必须包含.扫瞄器文件最终改为:
%{
#include <string.h> /*for strdup */
#include "Simple.tab.h" /* for token definitions and yylval */
%}
DIGIT [0-9]
ID [a-z][a-z0-9]
%%
":=" {return (ASSGNOP); }
{DIGIT}+ {return (NUMBER); }
do {return (DO ) ; }
else {return (ELSE) ; }
end { return (END) ;}
fi { return (FI) ; }
if { return (IF) ; }
in { return (IN) ; }
integer { return (INGETER);}let { return (LET) ; }
read { return (READ) ; }
skip { return (SKIP) ; }
then { return (THEN) ; }
while { return (WHILE) ; }
write { return (WRITE) ; }
{ID} { yylval.id = (char *) strdup(yytext);
return (IDENTIFIER) ;}
[ /t/n]+ /* eat up whiteespace */
. { return (yytext[0] ) ; }
%%
中间表示
许多编译器在语法分析阶段把源代码转为中间表示.在我们的例子中,语法表示是一个语法树.语法树保留在栈中但它可以显示构造.另一些中间表示通用的选择包括抽象语法树,三元地址,当然也有四元地址,和后缀码.在我们的例子中,我们选择跳过中间表示而直接生成代码.这节说到有关代码生成的原则也适用于中间代码生成.