您的位置:首页 > 其它

编译原理学习笔记(五)语法制导定义

2017-04-12 21:06 429 查看
在初学编译原理时的第二章中就给了制导翻译程序的例子,在第五章中又再次提到,不过更加详细。

上次做的预测分析器还有不完善的地方——目前只能进行语法分析,不能产生语义动作。可以使用语法制导定义的方法来完成。

语法制导定义是上下文无关文法和属性以及规则的结合,属性与文法符号相关联,规则和产生式相关联。非终结符具有两种属性:综合属性和继承属性。分析树上的非终结符的综合属性是由产生式所关联的语义规则来定义的,其来源为自身属性以及其子结点的属性。继承属性是由父结点上所关联的语义规则定义,其属性值的来源是父结点、本身以及兄弟结点。

各个结点的属性值是存在依赖关系的,如3+2这样一个简单的表达式,以语法树的形式表示出来时,最终求出值的父结点的值依赖于两个子结点的值(先得到3、2,然后才计算得到5)。因此求值必须要按照一定的顺序。书中提到的具体求值顺序的算法涉及到拓扑排序,但因为我所要编的编译器的语法是比较简单的,只需要求综合属性即可,因此无需该算法。

如何将语义的内容附到之前的预测分析结构中去?我的思路是这样的:

建立一个语义单元结构,用以对应于某一个产生式,它关联一个语义动作,这个动作将在配置文件中定义。这个语义单元的对象在每一个非终结符的产生式展开时创建,创建后压入一个栈中,表示当前结点的父结点,当终结符匹配时,进行求值,并将结果通过栈返回给父结点,当父结点子结点均求值完毕时,弹出并将结果继续返回,直到栈空,求出最终值。这其实就是一个用栈模拟先序遍历的过程。代码如下:

void nc_parser::translate(string code,string mode){//mode为起点的选择 通常为产生式的起始符 但由于NC程序的特殊,这里当默认为语句stmt(指令的翻译占大多数)
syn_unit end(0);
syn_unit start(mode, mesh_table[mode]->src.lex, 0, syn_unit::NONTERMINATOR);
stack<syn_unit> stk;
stack<smt_unit> smt_stk;//维护一个语义栈 当产生式展开时将其压入栈中(作为父节点存在) 用于获取匹配的值
stk.push(end);
stk.push(start);
int pos = 0;
auto lx_unit = lex->getNextToken(code,pos);

while (stk.top() != end){// && pos < code.length()
auto&cur_sig = stk.top();//当前
//查表决定展开式:
auto type = cur_sig.type;
switch (cur_sig.type){//符号类型
case syn_unit::EMPTY://匹配空符前 先清算父结点
case syn_unit::LEX://终结符
if (cur_sig.type == syn_unit::EMPTY || lx_unit->compare(cur_sig.token)){//类型是否匹配
cur_sig.buf = lx_unit->get_result();//匹配语法单元
auto*sem = &smt_stk.top();//当前语义处理块
sem->calculate(&cur_sig);//计算属性值
while (!smt_stk.empty() && sem->is_calcu_over){
//如果该语义块计算完了 就输出/把值通过栈交给父结点
sem->output();
auto p = *sem;//临时存储当前弹出值
smt_stk.pop();
if (!smt_stk.empty()){
sem = &smt_stk.top();
sem->calculate(&p);
}
}//弹出栈顶所有计算完毕的父节点
stk.pop();
}
else{//失配 尝试别的?
report("不匹配的类型:"+lx_unit->token);
}
if (type!=syn_unit::EMPTY)//句子没扫描完才继续读token
lx_unit = lex->getNextToken(code,pos);//匹配后才后移
break;
case syn_unit::NONTERMINATOR:
auto&prod = mesh_table[cur_sig.token];//获取产生式组
if (prod->drivetable.find(lx_unit) != prod->drivetable.end() || prod->drivetable.find(NULL) != prod->drivetable.end()){
//该单词在驱动表中
stk.pop();
int choice;
if (prod->drivetable.find(lx_unit) != prod->drivetable.end())
choice = prod->drivetable[lx_unit];
else
choice = prod->drivetable[NULL];
auto&dst = prod->dst[choice];//选择产生式
for (auto&var = dst.rbegin(); var != dst.rend(); var++)
stk.push(*var);
//展开并逆序压入栈
smt_stk.push(smt_unit(prod, choice));
//维护语义栈
}
break;

}
}
}


目前面临的难题是如何来定义配置文件,使之可以表达出想要的语义动作,并且能够检查语义规范。

struct smt_unit{
public:
static map<production*,smt_action*>table;
static void init();
private:
production*pd;//产生式指针
int cho;//选择哪一个产生式
int cur;//当前计算完成数
string result;//计算结果
public:
vector<string>value;
void calculate(syn_unit*);//求值 传入语法单元
void calculate(smt_unit*);//求值 传入其他语义单元的结果
bool is_calcu_over;
void output();//输出值
smt_unit(production*,int);
};


目前在语义单元结构中定义了一个映射表,将产生式与动作相关联,但smt_action还没有定义。目前只能将各个字符串连接并输出:



可以看出是一个深度遍历的过程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  编译原理
相关文章推荐