200 likes | 427 Views
è¯æ³•åˆ¶å¯¼å®šä¹‰ï¼Œç¿»è¯‘æ–¹æ¡ˆï¼Œä»¥åŠ antlr 实现. 赵建åŽ. è¯æ³•åˆ¶å¯¼å®šä¹‰. 一般æ¥è¯´ï¼Œåœ¨æ–‡æ³•ä¸çš„æ¯ä¸ªéžç»ˆç»“符å·éƒ½æœ‰å¯¹åº”于特定的文法结构。 对于æ¯ä¸ªç»“构,都å¯ä»¥å®šä¹‰ç›¸åº”的属性。比如:æ¯ä¸ªè¯å¥å¯¹åº”的代ç ,æ¯ä¸ªè¡¨è¾¾å¼å¯¹åº”的类型,ç‰ç‰ 在ä¸åŒçš„时候,我们对ä¸åŒçš„属性感兴趣,也就给出ä¸åŒçš„属性定义。. è¯æ³•åˆ¶å¯¼å®šä¹‰. è¯æ³•åˆ¶å¯¼å®šä¹‰ä¸ï¼Œè§„定了æ¯ä¸ªè¯æ³•å•ä½çš„属性值时如何由其他的è¯æ³•å•ä½çš„属性值确定。但是并ä¸ç»™å‡ºå…·ä½“的计算次åºã€‚ 实际上,我们å¯ä»¥æŠŠè¯æ³•åˆ¶å¯¼å®šä¹‰çœ‹ä½œæ˜¯ä¸€ç§ specification。 它规定了å„ä¸ªå±žæ€§åº”è¯¥æ»¡è¶³æ€Žä¹ˆæ ·çš„è¦æ±‚。. è¯æ³•åˆ¶å¯¼å®šä¹‰çš„例å. é‡å†™è§„则. è¯ä¹‰è§„则.
E N D
语法制导定义 • 一般来说,在文法中的每个非终结符号都有对应于特定的文法结构。 • 对于每个结构,都可以定义相应的属性。比如:每个语句对应的代码,每个表达式对应的类型,等等 • 在不同的时候,我们对不同的属性感兴趣,也就给出不同的属性定义。
语法制导定义 • 语法制导定义中,规定了每个语法单位的属性值时如何由其他的语法单位的属性值确定。但是并不给出具体的计算次序。 • 实际上,我们可以把语法制导定义看作是一种specification。它规定了各个属性应该满足怎么样的要求。
语法制导定义的例子 重写规则 语义规则 D::=TL D.idTable = {(id, T.type)|id isin L.idList} T::=int T.type = int T::=real T.type = real L::=L1, id L.idList = L1.idList + {id} L::=id L.idList = {id}
语法制导定义 • 在上面的例子中,D有一个属性idTable,表示在D对应的申明中定义的所有标志符。 • 我们感兴趣的是D的idTable。为了定义idTable,我们引入了L的idList, T的type。 • 书上面的定义实际上已经夹杂了一些实现方面的内容:addtype(id.entry, L.in)。但是还是比较好理解的。
语法制导定义与翻译方案 • 上面的例子里面没有规定如何计算各个属性的值。给定一个D对应的短语,我们可以画出相应的语法树,然后得到各个属性的值。 • 而翻译方案给出了语法制导定义的具体实现。对于D的一个短语,在建立这个短语对应的语法树的过程中就可以按照翻译方案得到各个属性的值。
翻译方案的抽象级别 • 按照综合属性和继承属性的定义,我们可以确定每个属性应该在什么地方计算。 • 比如,计算四则运算的翻译方案如下 • E::=E1+T {E.value = E1.value + T.value;} • … • 但是这样做有的时候效率很低下: • 如果属性的值占用的内存非常大。 • 有些工具不支持属性的引用(比如yacc?)。
翻译方案的更加具体的实现 • 有些值可以通过栈的方式来来传输: • 在LR(k)的分析过程中,栈里面的包含有归约得到的文法符号。我们可以在栈中每个符号的信息中加入一个指针,指向存放相应属性值的内存区域。 • 也可以另外设立独立的栈来存放相应的值。比如书上的page 219的例子。此时,翻译方案的操作顺序就非常重要了。
翻译方案的更加具体的实现 • 一些值可以通过全局变量来传递: • 有些属性的值所占用的内存非常大,如果使用拷贝的方式(包括栈或者直接传递)来传递,那么效率将非常低下。 • St ::= if E then St1 else St2 {} 关于St的code属性。 • 通过全局变量传递的时候要求能够做到增量计算。比如:语法制导定义要求St.code = E.code || St1.code || St2.code, …实现的时候不能够把这些code属性反复拷贝。
翻译方案 • 在写翻译方案的时候,如果对效率的要求不高,那么可以都使用属性直接应用和直接计算的方法。 • 这样的翻译方案比较容易理解。
关于代码生成的翻译方案 • 书上面出现了两个版本: • 对code属性进行直接引用的方式。 • 使用全局变量存放code的方式。 • 两个版本的区别仅仅在于实现细节的不同。
P248 • S::= IF E THEN S1 ELSE S2 {E.true = newlabel; E.false = newlabel; S1.next = S.next; S2.next:=S.next; S.code = E.code || gencode(‘CMP’, E.place, “#1”) ||gencode(‘CJ=‘, E.true) ||gencode(‘GOTO’, E.false) ||gencode(E.true, ‘:’) || S1.code || gencode(‘GOTO’, S1.next) ||gencode(E.false, ‘:’) ||S2.code ||gencode(S.next, ‘:’) }
P252 • S::=IF E THEN M1 S1 N ELSE M2 S2 { backpatch(E.truelist, M1.pos); backpatch(E.falselist, M2.pos); S.nextlist = merge(S1.nextlist, S2.nextlist); S.nextlist = merge(S.nextlist, N.nextlist);} • E::=id1 relop id2 {E.truelist:= makelist(nextpos+2); E.falselist = makelist(nextpos+3); t:=newtemp; emit(‘MOV’, id1.place, t); emit(‘CMP’, t, id2.place); emit(‘CJ’||relop, ‘_’); emit(‘GOTO’, ‘_’)} • 实际存在一个全局变量存放code, 前面的||操作实际上有emit函数不停添加代码的过程中完成了。
P248和P252 • p252的方式可以使得我们避免使用标号,而直接使用代码的序号来完成跳转。原因是每个代码在全局中的位置已经确定。 • 但是P252的更大的好处在于运行效率有很大的提高:不需要做code的合并操作。
用antlr实现翻译方案 • 如果使用对属性直接引用的方式,那么在antlr中有非常直接的对应方式。 typeExp returns [TypeInfo *tp] : tp = simpTypeExp | tp = structTypeExp ; • 使用全局数据传递的实现基本上也很直接。
对于左递归的处理 • EE1+T {E.val = E1.val + T.val} | T {E.val = T.val} • 由于antlr允许使用EBNF的方式,所以上面的规则可以改写成为: • ET (+T)* • 计算的规则成为 • ET{tmp = T.val;} (+T{tmp = tmp+T.val;})* {E.val = tmp;}
关于LR技术 • 主要目的是确定规范句型的句柄。 • 文法G[E]: EE+T | T TT*F | F Fi | (E) • 对于规范句型的语法树,遍历其叶子节点的时候,在扫描完句柄之前,没有完备的语法子树出现。 E T E + T * …
关于LR技术 • 在句柄之前的部分称为活前缀。 • 活前缀需要满足的条件是什么呢? • 一个符号串是活前缀,iff我们可以构造这样的一个语法树的片断:其语法树的左边的叶子符合该符号串,并且不包含完备的子树。
关于LR技术 • 我们可以这样来画出一个活前缀的部分语法树: 选择EE+T, 然后选择TT*F。 • 我们可以构造一个NFA来识别活前缀: • 初始状态对应于E.E+T, • 对于每个状态Vx.ay,有一个标记为a的弧到达Vxa.y。 • 对于每个状态Vx.Uy,如果U是非终结符号,有一个空弧到达U.w。 • 一个符号串是活前缀 iff 它是该NFA的一个路径。 • 恰巧包含句柄的符号到达一个状态Vx.
关于LR技术 • 如果,我们把这个NFA确定化就可以得到特征自动机CFSM。 • CFSM中的不适定状态表示在NFA中,可能到达Vx.,也可能到达非接受状态。 • 此时需要依据尚未输入的符号进行判断。因此衍生出SLR, LR(k)和LALR技术。