符号表系统看起来还算是比较好用,不过为了方便起见,现在在它之上再加一层套。考虑到日后一些类型的语法节点,如VariableNode
以及DeclarationNode
,它们直接与符号表打交道的话,就会出现这样的问题:一方面它们要执行语法制导的指令生成,另一方面还需要处理各种跟符号表有关的错误,这样违反了单一责任原则──特别是,当使用 if-else 分支来处理这些错误时,内部的缩进层次太多会使得代码看起来很丑陋。
为了更方便地使用符号表,我们需要在符号表和语法节点的指令生成之间增加一个装饰层,这个装饰器将提供以下便利:
> 容错。当查找(无论是查找类型、首地址,还是维度信息)的符号不存在,或者重复定义符号,它直接将错误送达错误处理模块,并返回一个看似正确的结果。
> 管理符号表层次。当进入一个基本块后,需要在符号表栈内压入一个符号表,而出符号表则相反;另外,在查询变量时,这个中间层会循栈而下,在每个符号表中搜索一遍。
> 编译时的地址计算。当请求一个变量的地址时,特别是数组中的某一个时,要尽可能根据上下文计算其地址。
要达到这些目的,可以考虑这样一组接口
/* st-manager.h */
/* 初始化及反初始化符号表管理模块。 */
void initialSymTabManager(void);
void finalizeSymTabManager(void);
/*
* BasicBlockNode 在进行指令生成时,应该调用这两个函数
* 它们分别表示进入基本块和离开基本块
*/
void enterBasicBlock(void);
void leaveBasicBlock(void);
/*
* 声明一个变量并返回其首地址
* 如果一个变量在声明时初始化,那么这样一个返回值将很有帮助
*/
int declare(struct VariableNode* var, AcceptType type);
/* get the type of a variable. */
AcceptType typeOf(struct VariableNode* var);
/*
* 给出一个变量节点和一个整数数组,返回其地址
* 对于数组,如果它的第 d 维偏移量是常数,那么返回后,offsets[d] = -1
* 否则 offsets[d] 的值为该数组在这一维的偏移。
*/
int staticOffset(struct VariableNode* var, int* offsets);
设置初始化及反初始化是要初始化和反初始化符号表栈。不过,考虑另外一个功能,即当出现未声明的符号时,针对该符号仅报一次错,以后再引用该符号则无视它──gcc就有这个功能──那么我们还得维护一个表,这个表与符号表栈一同初始化。
/* 符号表栈 */
static struct Stack* symTabStack = NULL;
/* 未找到的变量链表 */
static struct LinkedList notFoundVars;
void initialSymTabManager(void)
{
symTabStack = newStack();
initLinkedList(¬FoundVars);
}
void finalizeSymTabManager(void)
{
struct SymbolTable* table;
int i = 0;
for (i = 0; i < symTabStack->height(symTabStack); ++i) {
table = (struct SymbolTable*)(symTabStack->peekAt(symTabStack, i));
finalizeSymbolTable(table);
}
symTabStack->finalize(symTabStack);
while (0 != notFoundVars.count(¬FoundVars)) {
revert(notFoundVars.popElementAt(¬FoundVars, 0));
}
notFoundVars.finalize(¬FoundVars);
}
接下来是进入和离开符号表
void enterBasicBlock(void)
{
struct SymbolTable* table = (struct SymbolTable*)
(symTabStack->peek(symTabStack));
symTabStack->push(symTabStack, newSymbolTable(table->used + table->baseAddr));
}
void leaveBasicBlock(void)
{
finalizeSymbolTable((struct SymbolTable*)(symTabStack->pop(symTabStack)));
}
声明变量
int declare(struct VariableNode* var, AcceptType type)
{
struct SymbolTable* table = (struct SymbolTable*)
(symTabStack->peek(symTabStack));
int addr = table->used + table->baseAddr;
struct AbstractValueNode* dimSize;
int nrDim = var->dimInfor->length(var->dimInfor), i;
int* dims = NULL; // 临时存放维度信息用
if (0 != nrDim) {
dims = (int*)allocate(sizeof(int) * nrDim);
for (i = 0; i < nrDim; ++i) {
dimSize = (struct AbstractValueNode*)
(var->dimInfor->peekAt(var->dimInfor, i));
// 必须使用整数来声明数组
// typeOf 是 AbstractValueNode 在语义分析时会加入的一个成员函数,用于确定节点的静态数据类型。
if (INTEGER != dimSize->typeOf(dimSize)) {
// 报错,不是整数
}
// 检查是否为常数
// staticInt 是 AbstractValueNode 在语义分析时会加入的一个成员函数
// 这个函数用于进行编译时常数折叠优化
if (0 != dimSize->staticInt(dimSize, dims + i)) {
// 报错,不是常数
dims[i] = 1;
}
// 每一维都得大于0
if (0 >= dims[i]) {
// 报错
dims[i] = 1;
}
}
}
SymbolTableError errCode = regVar(table, var->ident, type, nrDim, dims);
if (SymTab_MultiDef == errCode) {
// 报错,重复定义
} else if (SymTab_SizeExceeded == errCode) {
symError(tooManySymbols, var);
}
// 注意回收垃圾
if (NULL != dims) {
revert(dims);
}
return addr;
}
在实现相关查询之前,现弄这样一个函数notFound
,当变量未定义时,调用这个函数将变量注册进未定义变量表内。
static void notFound(struct VariableNode* var)
{
struct Iterator* i;
for_each (i, ¬FoundVars) {
if (0 == strcmp(var->ident, i->current(i))) {
i->terminate(i);
return;
}
}
// 以前没有出现过,报错
char* ident = (char*)allocate(strlen(var->ident) * sizeof(char));
strcpy(ident, var->ident);
notFoundVars.add(¬FoundVars, ident);
}
然后是最后两个函数
AcceptType typeOf(struct VariableNode* var)
{
struct SymbolTable* table;
int i;
AcceptType type;
for (i = 0; i < symTabStack->height(symTabStack); ++i) {
table = (struct SymbolTable*)(symTabStack->peekAt(symTabStack, i));
if (SymTab_NotDef != getVarType(table, var->ident, &type)) {
return type;
}
}
notFound(var);
return INTEGER;
}
int staticOffset(struct VariableNode* var, int* offsets)
{
struct SymbolTable* table;
int base, nrDim, i, j, count,
refArrayDims = var->dimInfor->length(var->dimInfor);
struct AbstractValueNode* offset;
for (j = 0; j < refArrayDims; ++j) {
offsets[j] = -1;
}
for (i = 0; i < symTabStack->height(symTabStack); ++i) {
table = (struct SymbolTable*)(symTabStack->peekAt(symTabStack, i));
if (SymTab_NotDef != getVarNrDim(table, var->ident, &nrDim)) {
getVarAddr(table, var->ident, &base);
if (nrDim != refArrayDims) {
// 报错,数组维度不匹配
return base;
}
for (j = 0; j < refArrayDims; ++j) {
getVarDimSize(table, var->ident, offsets + j, j);
offset = (struct AbstractValueNode*)
(var->dimInfor->peekAt(var->dimInfor, j));
// 如果某一维编译时可以求出,那么将这一维的偏移总量加入返回值 base 中
// 寻址相关的详细内容,将会在 VariableNode 的语法制导的指令生成中详细说明,敬请期待^,^
if (0 == offset->staticInt(offset, &count)) {
base += offsets[j] * count;
offsets[j] = -1;
}
}
return base;
}
}
notFound(var);
return 0;
}
分享到:
相关推荐
查填符号表:掌握词法扫描 查填符号表是编译原理中的一种重要技术,用于识别和存储编程语言中的标识符。掌握查填符号表是编译原理的基础,下面是查填符号表的详细知识点: 一、查填符号表的定义 查填符号表是一种...
### 编译原理之设计符号表 #### 一、引言 在计算机科学领域,编译原理是一门核心课程,它研究的是如何将高级语言编写的源代码转换为机器可以理解并执行的目标代码。其中,设计符号表是编译器工作流程中的一个重要...
【符号表工具Android版-使用指南1】 符号表工具Android版是Bugly为开发者提供的一款用于提取和上传符号表文件的实用工具,旨在帮助开发者快速定位App中的Crash问题。这个工具支持Windows、Linux和Mac操作系统,且...
以下是对标题“常用数字逻辑符号对照表”及描述中提到的各种逻辑符号的详细解释: 1. **与门(AND Gate)**: - 国标符号:通常表现为两个输入线在一个交汇点相交,用一个圆圈表示,如 "U" 形状。 - 常用符号:与...
编译原理实验查填符号表(含源代码和运行结果) 本实验的主要目的是通过编写符号表管理程序来熟悉编译过程,并训练编写程序的能力。实验中,我们需要读出源程序中与 C 语言词法规定相一致的标识符,并在符号表中...
step7符号表生成器 在创建S7或M7程序时,将自动创建一个(空的)符号表(“符号”对象)。在符号表中不能定义数据块中的地址(DBD、DBW、DBB和DBX),数据块中的地址应在数据块的声明表中定义。图5-26设置地址优先级要...
在IT领域,尤其是在系统调试和故障排查中,符号表(Symbols)扮演着至关重要的角色。符号表是操作系统或应用程序的内部表示,包含了调试信息,如函数名称、变量名、内存地址与源代码行号的映射关系等。这些信息使得...
符号表的导入导出功能允许用户将符号表从PLC导出到外部文件中,或者将外部文件中的符号表导入到PLC中。这个功能在软件升级、备份以及多人协作开发时非常有用。但是,符号表导出过程中常常会遇到一些问题,其中最常见...
在编译原理中,符号表(Symbol Table)是至关重要的组成部分,它用于存储源程序中所有标识符(如变量、函数、常量等)的相关信息。这个符号表管理功能通常在词法分析和语法分析阶段被引入,为后续的编译过程提供必要...
本话题聚焦于"ELF符号表修改",这涉及到程序链接和执行过程中的核心概念。符号表是ELF文件中的一个重要组成部分,它记录了程序中所有函数和全局变量的信息。以下将详细阐述符号表的结构、作用以及如何进行修改。 1....
在词法分析阶段,符号表开始为空,随着分析的进行,每当遇到新的标识符,就会在表中添加相应条目。在后续的语法分析和代码生成阶段,符号表能帮助验证引用是否有效,避免未声明的变量等问题。 然而,描述中也指出这...
运用所学知识,编程实现符号表管理程序。读出源程序中与C语言词法规定相一致的标识符,并在符号表中进行查找,若存在则输出该标识符及其编号和位置;否则将其填入符号表,并分配编号,确定位置,输出该标识符。输出...
在使用西门子200SMART PLC编程时,符号表是一个重要的工具,它允许程序员以易于理解的变量名称代替实际的寄存器或存储器地址,提高代码的可读性和可维护性。然而,有时在符号表中修改变量的符号名后,程序中可能不会...
编译原理是计算机科学中研究如何将源代码转换成机器代码的重要学科,其中符号表是一个关键的数据结构,用于记录编译过程中各种标识符的相关信息。在编译原理的第10章中,详细介绍了符号表的作用、内容、组织方法以及...
《Windows XP SP3 32位调试符号表离线包详解》 Windows XP Service Pack 3(SP3)是微软发布的针对Windows XP操作系统的重要更新,它包含了大量的安全修复、性能优化以及新功能的添加。然而,对于开发者和系统管理...
在Windows操作系统中,调试符号表是开发者和系统管理员用于深入理解系统行为、定位问题的关键工具。这些符号表包含了操作系统内部函数、系统调用、库函数等的详细信息,如地址、参数、返回值以及源代码行号。"win7...
在编译原理中,符号表和目标程序运行时的存储组织是两个至关重要的概念。它们在编译器设计和程序执行效率中起着关键作用。 首先,我们来讨论符号表。符号表是用来存储源程序中所有标识符(如变量、函数、常量等)的...
在这个主题中,“flex&bison解析简单结构体生成简单符号表”涉及的是如何利用这两个工具来解析包含结构体的简单语言,并构建一个符号表以存储解析过程中识别出的标识符及其相关信息。 首先,`flex` 是一个词法分析...