符号表的建立和操作

主符号表

主符号表要求记录:

  • 种类标志
  • 标识符名字
  • 行号
  • 类型
  • 常量取值
  • 参数个数/数组维数
  • 数组各维上下界
  • 指向函数/过程子符号表的指针

    子符号表

    子符号表的表结构是主符号表的子集。由于不支持函数和过程的嵌套定义,所以子符号表相比于主符号表,少了函数/过程相关的域。子符号表要求记录:

  • 种类标志

  • 标识符名字
  • 行号
  • 类型
  • 常量取值
  • 数组维数
  • 数组各维上下界

    符号表各域的具体介绍

    | 域名 | 介绍 | | —- | —- | | 种类标志
    | 种类标志中记录着标识符的符号种类。
    “value parameter”表示传值参数,”var parameter”表示传引用参数,”normal variant”表示普通变量,”constant”表示常量,”array”表示数组,”procedure”表示过程,”function”表示函数。
    | | 标识符名字
    | 名字作为语义分析部分识别标识符的主键,在进行添加、查找、修改等操作时发挥重要作用。
    | | 行号
    | 词法分析和语法分析时,在进行报错时可以很方便的获取出错的具体位置,但语义分析通常都是在更抽象的树结构上进行的,所以需要记录下每个符号的行号,以便报错包含位置信息。
    | | 类型
    | 对于变量和常量来说,该域记录着变量或常量类型; 对于数组来说,该域记录着数组元素的类型;对于函数来说,该域记录着函数返回值类型。取值为”integer”、”real”、”char”、”boolean”。
    | | 常量取值
    | 需要将常量的取值保存下来,以便后续计算常量表达式的值、进行常量定义时的常数传播、检查除0错误、检查数组下标越界。
    | | 参数个数/数组维数
    | 在参数个数/数组维度部分,对于数组类型的变量,我们将存储其维数;对于函数类型,我们将存储其参数个数。
    | | 数组各维上下界
    | 对于数组而言,在符号表中记录其各维上下界,便于判断其是否越界。
    | | 指向函数/过程子符号表的指针
    | 在该指针域中保存该符号表中指向函数/过程子符号表的指针,便于进行定位和重定位处理。
    |

符号表的管理

定位操作

在每个程序块的入口处我们需要执行定位操作来建立一个符号表的子表,将该块的声明的所有标识符属性记录到该表中。

重定位操作

在每个程序块的出口处我们需要返回到主符号表,实现重定位操作,保证已经执行完的块中声明的局部变量不能再次被引用。

类型检查与转化

我们的语义分析需要支持四种基本类型:integer、real、boolean、char,以及这四种基本类型声明的数组。

类型转化

我们仅支持隐式类型转化,且其中也仅支持从integer类型到real类型的隐式类型转化。需要特别注意的是,传引用参数不支持隐式类型转化。

表达式类型检查

每个运算符对于操作数的类型都有不同的要求,需要具体分析不同运算符的具体要求,例如mod运算符要求两个操作数均为”integer”类型,relop运算符要求两个操作数类型一致,或者符合隐式类型转化的规定。

语句类型检查

每个运算符对于操作数的类型都有不同的要求,需要具体分析不同运算符的具体要求,例如mod运算符要求两个操作数均为”integer”类型,relop运算符要求两个操作数类型一致,或者符合隐式类型转化的规定。

作用域识别

由于PASCAL-S不支持函数/过程的嵌套定义,所以作用域规则是十分简单的。

定义在PASCAL程序一开始的常量、变量就可以是视作是全局作用域,不仅可以被主程序体引用,也可以被子程序引用(如果子程序没有定义同名标识符)。

另外,每一个子函数/过程中定义的常量、变量的作用域就局限在当前函数/过程,属于局部作用域。

检测标识符未定义错误时,除了局部作用域(如果当前在局部作用域中),还需退回到全局作用域中。

检测标识符重定义错误时,只需要在局部作用域(如果当前在局部作用域中)中检查。