语义分析概述 符号表 第六章 语义分析
必要性 分类 语义错误 功能 1 语义分析
1.1语义分析的必要性 语法和语义的区别 语法:关于什么样的字符串才是该语言在组成结构上合法的程序的法则。 语义:关于结构上合法的程序的意义法则。
1.2语义分析的分类 语义种类 指称语义 操作语义 公理语义 静态语义:在编译阶段(从程序文本上)可以检查的语义。 动态语义:通过程序的执行才能检查的语义。
1.3语义错误 各种条件表达式的类型是不是boolean型? 运算符的分量的类型是否相容? 赋值语句的左右部的类型是否相容? 形参和实参的类型是否相容? 下标表达式的类型是否为所允许的类型? 函数说明中的函数类型和返回值的类型是否一致? V[E]中的V是不是变量,而且是数组类型? V.id中的V是不是变量,而且是记录类型? id是不是该记录类型中的域名? y+f(…)中的f是不是函数名?形参个数和实参个数是否一致? p(…)语句中的p是不是过程名?形参个数和实参个数是否一致? V↑中的V是不是指针或文件变量? 变体记录中表示情形的常量是否为合法类型? 子界类型中的下界和上界类型是否相容?下界是否小于等于上界?
语义错误 cont. 每个使用性标识符是否都有声明?在同层内有无标识符被声明多次? 标号是否有声明?有无重复声明和重复定位错误?有无非法转入错误?
1.4语义分析的功能 语义分析的内容: 语义分析的功能: 语义分析的实现: 类型分析; 标识符相关信息提取; 检查语义错误 构造标识符属性表(符号表) 语义分析的实现: 与语法分析相结合
语义分析的功能图示 语法分析树 语义分析 符号表 TokenList 语义定义 判定语义错误 自然语言描述规定
void, function, (int i), …… 符号名 属性 x int, variable, …… p void, function, (int i), …… … ……. 2 符号表
2.1地位 词法分析 语法分析 语义分析 中间代码生成 符号名 类型 指针 大小 … x fun M
2.2符号的内部表示 值的内部表示 类型的内部表示 标识符的内部表示
2.2.1值的内部表示 基本类型:整型,实型 无序类型:指针,数组,结构体 有序类型:布尔,字符,枚举,数组下标 布尔常量:ord(false)=0, ord(true)= 1 字符常量:ord(C) = ASCⅡ(C) 枚举常量:设有枚举类型(D,A,B),则有ord(D)=0,ord(A)=1,ord(B)=2
2.2.2类型的内部表示 类型的种类:标准、子界、枚举、数组、记录、集合、文件、指针类型等 内部表示:(TypeIR) int/bool/ Type=(intTy,boolTy,charTy,realTy, subTy,enumTy, arrayTy,recordTy,setTy,fileTy,pointerTy) 内部表示:(TypeIR) int/bool/ char/real: enum: array: pointer: 2 intTy Size Type 2 enumTy intPtr 3 Size Type ElementList Length 10 arryTy 4 intPtr Size Type Low Up ElementType size(pointer) pointerTy intPtr Size Type TypeName
实例 int f(real x, char y){ bool t=true; enum a {a1,a2,a3}; real b[M]; char c*; return t; } 写出各类型的内部表示。
类和结构体 结构体 类 Size Kind FieldList name Kind Type Off
实例 class Student{ String name; int age; real mark; public Student(String n,int a, real m){ this.name=n; this.age=a; this.mark = m; } get… set… } Size Kind FieldList name Kind Type Off fieldKind charPtr age intPtr 1 mark realPtr 3 Student Ptr 7 get
2.2.3标识符的内部表示 常见标识符种类:常量名、类型名、变量名、函数名、过程名、域名。 内部表示(AttributeIR): 常量名: Kind=(consKind, typeKind, varKind,fieldKind, funcKind,procKind) 内部表示(AttributeIR): 常量名: 变量名: 类型名: 过函名: 域名名: M constKind intPtr ^10 Name Kind Type Value a varKind realPtr direct 1 3 Null/^100 Name Kind Type Acces Level Off Value Name Kind Type mat typeKind Ptr Name Kind Type Class Level Off f funcKind intPtr formal 1 5 f funcKind intPtr actual 1 parList ptrF t/f Para Code Size For mark varKind IntPtr 3 Name Kind Type off
层数和偏移 层数(level) 偏移量(offset) 过程/函数的定义(不)可以嵌套; 最外层程序的层数为1(0); 原因:执行过程/函数的调用时, 需要为其中的变量分配空间; 计算:偏移量指的是变量针对其所在过程/函数的空间的首地址的偏移量;
实例 #define Pi 3.14 bool g(real x, int * y){ int v[10];real z;return 0;} enum gender {male,female}; Main(){…} 令当前层数和偏移分别为L和0,构造标识符Pi,g,x,y,v,z,gender的属性表。
Name Kind Type Value Class Access Level Off/Para Code Size For Pi cons real ^3.14 L g func bool actua paraListP null size False x varK direct L+1 4 y p1 indirec 2 v a1 6 20 z 26 gend typeK e1 Ptr Size Kind Typ Up EleType intPtr 2 boolP 1 charP realPt 4 p1 poin intPt a1 20 arra 9 e1 enu Name Value male 1 female 2
2.3符号表 标识符在不同位置的不同作用: 必要性 声明部分:定义了各种对象及对应的属性和使用规则。 程序体:对所定义的对象进行各种操作。 Token序列: 符号表(种类、类型等信息): $ID idname … idname Attribute1 …
2.3.1建立和访问 有关符号表的操作: 添加、作用域删除、查询 处理符号表的模块: 定义符号表数据结构 定义符号表上的操作
2.3.2符号表的处理 符号表的作用:为语义检查和代码生成提供标识符的语义信息。 标识符的处理思想: 遇到定义性标识符时,在符号表中填写被定义标识符的符号项; 遇到使用性标识符时,用该标识符查符号表求得其属性。
标识符的特点 标识符的作用域:标识符有效的最大程序段 嵌套作用域规则:当存在标识符的嵌套声明时,最近定义的属性为标识符的当前属性 局部化单位:允许有声明的程序段 P: Var x ,y,z x:=0; Q: Var x,m,n x:=1; m:=x+1; y:=x+1;
局部化区入口 Proc p(… Func f(… Record begin…
标识符处理的原则 符号表的种类:全局符号表、局部符号表 处理原则: 进入一个局部化区时,记录本层符号表的位置 遇到定义性标识符时,构造其语义信息,查本层符号表,若存在,则有重复声明错误,否则将语义信息填入表中 遇到一个使用性标识符时,查表(从里层到外层),查不到则有未定义标识符错误,否则构造新的TOKEN 退出一个局部化区时,作废本层符号表
语义分析例子 int x,y; void swap(int *a,int *b){ int tmp; temp=*a; *a=*b;*b=tmp; } void main(){ x=10; y=20; swap(&x,&y);
Name Kind Type Access Level Off ParaList Code Forward x varKind intPtr dir y 1 swap funcKind Null actural swapParaList swapC false a a-ptr indir b b-ptr tmp 2 # main actual mainC False ptr size kind eleType a-ptr 1 pointerTy intPtr b-ptr
符号表的分类 单表结构 vs. 多表结构 局部表 vs. 全局表 单表:标识符都放在同一张符号表中 多表:常量、变量、类型、函数、等分别存放 局部式:每个局部化单位建立一个独立符号表 全局式:整个程序的符号表作为一个表
全局符号表的局部化 删除法 vs. 驻留法 思想 特点 删除法 离开局部化区时删除 思路简单,实现容易,信息不留存 驻留法 离开局部化区时标记 信息留存,实现相对复杂(辅助数据结构)
局部化实例 name kind type access level off extra for main funcK intPtr int main() { int a; float b,d; { int c; float a; int d; float c; float d; //... a=b+c+d; } char d; return 0; name kind type access level off extra for main funcK intPtr actural null false a varKi dir 1 b realPt d 3 c 5 2 6 8 9 11 4 # charP
局部符号表管理——Scope栈 用于存放当前有效的局部化区的局部符号表首地址; 进入新局部化区时新符号表首地址入栈; 退出局部化区时栈顶地址出栈。
标号的语义分析 标号出现的位置: 标号部分的语义错误: 标号声明:label 1, 2, …, n; 标号定位(语句前):i:Statement; 标号使用(Goto后):goto i; 标号部分的语义错误: 标号重复声明; 标号重复定位; 标号有定位而无声明; 标号有使用而无定位; Goto语句有非法转入.