语义分析之一:属性文法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/roger_ranger/article/details/78768451

编译原理的几个核心阶段:词法分析、语法分析和语义分析,其实编译的本质便是翻译,其各个阶段便是承担不同的翻译任务,词法分析阶段的任务是将程序输入的字符串流翻译成语言认可的字符流(剔除空格和注释等部分);语法分析便是将程序按照语言文法的规则构建成语法树;语义分析便是在语法树构建的基础上完成语言规则的语义动作(类型检查、作用域和可视性检查、一致性检查等)。

现今的程序语言绝大多数都是图灵完备的,故而语法设计上几乎很难出现决定性的差异,更多的语言特征区别便是在语义动作上设计的不同所带来的效果。其实语言的诸多优美特征都是在语义阶段添加进去的,比如C语言规定变量声明部分必须在操作部分之前全部完成,而C++则可以在程序中随时声明变量,比如一些隐藏的类型转换都是在语义分析阶段完成的,所以某种意义上,语义分析也是语言设计和程序编程间的“银弹”。所以相比于语法阶段的树扩展或图搜索之类的图论知识的枯燥,语义分析阶段可以看到很多和语言特性鲜活的对应关系。

我们知道,对于语言而言,无论变量、函数、过程在程序中都是用一个标识符来代替,但如果给定了一个标识符,我们如何确定这个标识符的意义呢?其实这便引导出属性文法的概念(其实语义分析的公式化有多种方式,比如操作语义学、公理语义学、属性文法等,其中属性文法最为直观,也是当前绝大多数编译器采用的编译方式),比如变量有int\float\double之类的区别,那显然给定一个变量标识符,必须要指明该标识符的“数据类型属性”,所以必须给所有标识符配备一系列的属性。利用标识符的这些属性,便可以用来配合此前构建的语法树进行一系列的语义动作(类型检查、可见性是否合法等)。

语义分析一般是和语法分析组合在一起执行的,语法分析完成前一步语法树分析的构建(调用某个产生式完成一步规约,形成当前的树节点),然后语义分析便接着调用相应产生式配备的语义动作或子程序,完成属性文法所要求的语义动作(比如类型转换或生成中间代码)。所以对于属性文法而言,属性的加工和使用过程便是语义处理的意义。

形式上讲,一个属性文法是一个三元组,A=(G,V,F),一个上下文无关文法G;一个属性的有穷集V和关于属性的谓词函数的有穷集F。每个断言与文法的某产生式相联。如果对G中的某一输入串而言(句子),A中的所有断言对该输入串的语法树结点的属性全为真,则该串也是A语言中的句子,如下便是一个关于属性文法的例子

1. E→T1+T2  {T1.t=int AND T2.t=int}//谓词,要求若是符合该产生式,则进行相加的两元素必须都是整型
2. E→T1orT2 {T1.t=bool AND T2.t=bool}
3. T→num    {T.t∶=int} //属性加工,声明该变量为整型,该标识符数据类型属性被赋值为“整型”
4. T→true   {T.t∶=bool}
5. T→false  {T.t∶=bool}

归根而言,属性文法便是为所有标识符(无论是否是终结符)配备一些属性的文法,这些属性不仅可以描述设计语言的语法有效补充,又可以为语义分析提供足够的数据支持。在推导语法树的过程中,标识符的属性值也在不断加工并通过赋值规则不断层层传递。

既然介绍完了属性文法的属性重要性,那么如何利用这些属性,便是语义分析的重要所在。下面便摘录《编译原理》一书中的几个例子来演示属性文法的语义处理加工。

1. 赋值语句的语义翻译

(1) S→id∶=E {p∶=lookup( id.name); //在符号表中利用id标识符的符号名查找,如果不存在该标识符,则报错,该变量未定义
        if p ≠ nil then
        Emit(p′∶=′E.place) //如果存在,则生成中间代码,该中间代码便是赋值的意义
        else error}
(2) E→E1+E2 {E.place∶=newtemp; //定义一个临时变量,用来存储两操作数的和,生成中间代码
           if lookup(E1.name) != nil and lookup(E2.name) != nil then
           Emit(E.place′∶=′E1.place′+′E2.place)}
       else
           error}
(3) E→E1*E2 {E.placeE∶=newtemp;//同上
       Emit(E.place′∶=′E1.place′*′E2.place)}
(4) E→-E1 {E.placeE∶=newtemp; //同上
       Emit(E.place′∶=′′uminus′E1.place )}
(5) E→(E1) {E.place∶=E1.place}
(6) E→id {p:=lookup(id.name)}; //赋值语句
      if p ≠ nil then
      E.place=p.place
      else error}

2. 类型转换的语义处理

//对 E→E1*E2 进行类型转换,比如如果int和float相乘,应该先将int转换为float
E.place∶=newtemp;
if E1.typeint AND E2typeint then
begin emit(E.place,′∶=′,E1.place,′*i′, E2.place);
      E.type∶=int
end

else if E1.typereal AND E2typereal then
begin emit (E.place,′∶=′,E1.place,′*r′,E2.place);
      E.type∶=real
end

else if E1.typeint and E2type=real  then
begin t∶=newtemp;
   emit(t,′∶=′,′itr′,E1.place); //先将E1通过itr操作转换为float浮点数,利用临时变量t存储
   emit(E.place,′∶=′,t,′*r′,E2.place);
   E.type∶=real
end

else  /*E1·typereal and E2typeint*/
begin t∶=newtemp;
   emit(t,′∶=′;′itr′,E2.place);
   emit(E.place,′∶=′,E1.place,′*r′,t);
   E.type∶=real
end;
}

3. 用数值表示bool值的语义翻译

//有些语言中,0/1true/false是可以混用的,有没有奇怪为啥?其实这编译语义分析阶段的转换工作
E→E1 or E2
  {E.place∶=newtemp;
  emit(E.place ′∶=′ E1.place ′or′ E2.place)}

E→E1 and E2
  {E.place∶ =newtemp;
  emit(E.place ′∶=′ E1.place ′and′ E2.place)}

E→not E1
  {E.place∶ =newtemp:;
  emit(E.place ′∶=′ ′not′ E1.place)}

E→(E1)
  {E.place∶=E1.place}

E→id1 relop id2  //relop是指 < = >三操作符的任意一个
  {E.place∶=newtemp;
  emit(′if′id1.place relop id2.place ′goto′ nextstat+3);
  emit(E.place′∶=′′0′);
  emit(′goto′nextstat+2);
  emit(E.place′∶=′′1′)}

E→true
  {E.place∶=newtemp;
  emit(E.place′∶=′ ′1′)} //可以看到在语义翻译阶段,将true翻译成了1
 
E→false
  {E.place∶=newtemp; 
  emit(E.place ′∶=′ ′0′)} //将false翻译成了0
阅读更多

没有更多推荐了,返回首页