变量声明,是强类型编程语言中的关键语句。
强类型语言之所以叫强类型,就是它对变量的类型检查比较严格。其中一点就是变量在使用前必须先声明(类类型也一样),另一点就是在自动类型升级不可用时,需要明确使用强制类型转换(type cast)。
这点上C++的语法检查很严格,会提示error信息,而C语言一般只提示warning信息。
变量声明语句以基本类型的关键字,或者类类型的标志符开始,之后跟变量的标志符。
变量的标志符之后可以跟中括号,声明数组。
变量的标志符之前,可以跟星号声明指针,多个星号表示多级指针。
然后可以跟赋值运算符=,后接初始化表达式。例如,int a = 1;
当然也可能这样,int a = 1, b = 2, *p;
变量声明的语法还是比较复杂的。
为了代码的清晰可读,最好还是一行只声明一个变量。但是,语法分析必须可以正常处理int a = 1, b = 2, *p;这样的语句。
下图是标志符模块的代码:
在之前(关于词法分析)的文章里说过,标志符在源代码中具有多义性,必须到语法分析时才可以确定语义。
用C语言实现一个真正的词法分析器
词法分析器的简单思路
在词法分析中,会把关键字之外的字符序列都叫做标志符,并且给它们编号。
编号从SCF_LEX_WORD_ID开始,所以type大于等于这个值的单词都是标志符。
单纯的一个标志符没法确定语义,先把它存在一个栈里,继续分析。它有可能是函数、类型、变量。
接下来看类型模块:
类型可以是基本类型,也可以是类类型(前面的关键字struct或class可以省略,也可以带着)。星号可以没有,也可以有多个。具体的语法编辑见上图。
基本类型base_type的action函数:
如果是基本类型,那么可以在ast语法树中找到它的数据结构。在语法分析开始之前,我们就把基本类型添加到了语法树的根节点(全局作用域)。
type模块的标志符的action函数:
在编辑type模块的语法时,我们把identity模块的identity作为类型名,把type模块的identity作为变量名。其他情况下,一般引用identity模块的identity。
标志符的语义很复杂,还有可能是变量和函数。
当连续两个标志符出现的时候,前面那个肯定是类型。
如果这时候栈不空(前一个标志符已存在),就可以使用_type_find_type()函数查找所需的类型(确定前一个标志符的语义)。
不管栈空不空,当前标志符的语义是没法确定的。因为可以是变量,或函数,例如:
class A {};
A* p;
A* p();
p到底是函数还是变量,没法确定,必须等待后续的分析。
_type_find_type()函数:
我们优先当作类型查找,然后当作函数查找,所以类型名和函数名不能一样。为了简单,先这么规定。
星号的action函数:
当出现星号时,它前面的标志符肯定是类型。要记录星号出现的次数,每一个表示一级指针。
乘号只能出现在表达式里,所以这里出现的肯定是星号,当作指针处理。
变量声明模块:
变量声明模块的dfa节点不多,主要使用其他模块的节点,例如identity模块和type模块。
但是它的语法很复杂,如下:
type模块的identity,实际上是变量的标志符。
360-366行,是数组的声明,变量之后跟左中括号,然后是表示数组大小的表达式(可以省略,根据初始化表达式自动计算)。
右中括号再接左中括号,就可以递归分析多维数组。例如int a[ 2] [ 2],黑体的两个字符就是。这个递归机制由dfa框架实现:说说字符串处理和有限自动机
之后可以是逗号,继续声明其他变量,也可以跟分号结束。
369-370,是变量或数组变量的初始化,跟赋值运算符=。
373-375,跟new关键字创建类的对象。
之后是普通变量的初始化,和结构体或数组的初始化。
左中括号的action函数:
当发现了左中括号之后,它前面的就是变量。这时运行函数_var_add_var()把它加入语法树。
在这之前,没法确定这个标志符是不是变量。
到了这里,可以确定它是数组变量,所以要准备记录数组的维数和每一维的大小,暂时设置为-1。
挂载hook,检查右中括号。
右中括号的action函数:
右中括号出现的时候,说明表示数组当前维的大小的表达式分析完了,使用scf_expr_calculate()函数把它算出来。
赋值符号的action函数:
出现赋值的时候,之后跟初始化表达式。
先申请一个表达式,把变量和等号先添加进去,然后分析初始化表达式。
逗号的action函数:
逗号出现,说明前面的分析完了,后续还有同一个类型的变量、指针、或数组。
把变量和初始化表达式加到语法树,如果有的话。
分号的action函数:
分号出现,说明真分析结束了。也把变量或初始化表达式加入语法树,然后返回OK。
最后,看看添加变量的函数_var_add_var():
变量不能在同一个作用域里重复声明,58行是检查这个的。
往上查找所属的函数或者类,69-74行。
在函数里声明的是局部变量,在类里声明的是成员变量,其他的是全局变量:加static为文件作用域,不加为全局作用域。
如果在类里加static,则是类的静态变量。
95-105行,如果是声明的类的成员变量,它的类型不能递归。例如:
struct S {
struct S s; //成员变量的类型递归了,没法计算结构体的大小,语法错误。
struct S* next; //可以定义同类型的成员指针,因为指针大小是固定的。
最后是为这个变量生成一个结构,scf_variable_t,添加到语法树。
判断类型递归的函数:
想了解更多精彩内容,快来关注闲聊代码