前言:
现时同学们对“词法分析器作用是什么”大致比较关心,姐妹们都想要了解一些“词法分析器作用是什么”的相关内容。那么小编同时在网摘上收集了一些有关“词法分析器作用是什么””的相关知识,希望小伙伴们能喜欢,兄弟们快快来学习一下吧!任何一个框架都是由两部分组成的:一是框架代码,二是模块上下文。
框架代码,是把各个模块组合起来的胶水。
模块上下文,是各个模块的私有数据。
这两者之间通过一些函数指针作为接口进行联系,C语言是这么设计的,C++的话多使用虚函数。
这些函数指针接口,一般包括初始化、结束、其他这3类,分别用于框架的初始化、析构、运行。
大多数情况下,用于初始化的函数指针有多个,分多步运行,以解决多个模块之间的耦合问题。
各种各样的框架基本都是这么设计的,scf编译器的语法分析框架也不例外。
上图的scf_parse_s是scf的总结构体,整个编译器框架的代码都是围绕它进行的。
其中的2个成员变量dfa和dfa_data,分别是语法分析框架和它的总上下文。
dfa_parse_data_t的数据结构如下,内容的注释见下图的绿字:
其中第1项void** module_datas是各个语法模块的上下文指针数组,是最重要的一项。具体的(各模块)上下文类型是由各个模块自己定义的。
module_datas指针数组的大小与模块个数是一致的。
其他的都是当前正在分析的语法内容:有些语法元素需要在多个模块之间处理,就放在这个总上下文里。
例如:int a[2] = {24 * 3600, 1};
数组变量a在分析初始化表达式的时候也要用到,但变量的声明和初始化表达式的分析是2个模块,所以总上下文里记录了一个current_var,表明当前正在分析的初始化表达式是数组a的。
scf_stack_t* current_identities是当前的标志符组成的栈,语法分析的场景里每个标志符的语义是不一样的(一般分为函数、变量、类型这3种),有时候没法一下子确定,所以把暂时确定不了的标志符放在这个栈里。
例如:type t;
显然第一个标志符type是类型,第二个标志符t是变量,但在刚分析到第一个标志符的时候是没法确定type到底是变量还是类型的,所以就暂时把它放在这个栈里(等可以确定时再一起出栈)。
语法分析的各个模块,是以上图的结构体表示的。它有2个初始化函数,和1个析构函数。
初始化分2步,模块初始化init_module()和语法初始化init_syntax(),在多个模块之间在初始化时有耦合的情况下,可以通过这2步来解耦。
语法分析框架是先运行所有模块的init_module()函数,然后再运行所有模块的init_syntax()函数。
例如:while (i < n) i++;
它的语法是while (条件表达式) 主体顺序块,所以while模块初始化的时候表达式模块必须已经初始化完成了。
只要把表达式的初始化放在init_module()里,而把while的初始化放在init_syntax()里,就可以保证它们的初始化顺序。
dfa节点的名字是以“模块名_节点名”命名的,例如表达式模块的左小括号节点名字是expr_lp,while模块的左小括号节点是while_lp。
所有的语法节点都放在scf_dfa_s结构体的nodes动态数组里。
所有语句类型的语法规则都放在scf_dfa_s结构体的syntaxes动态数组里。
如果scf_parse_t* parse是整个编译器框架的总上下文:
parse->dfa->nodes就是语法节点的数组,
parse->dfa->syntaxes就是语法规则的数组。
语法节点的添加和查找,我给它做了2个宏。
对源代码文件的语法分析入口,是下图的scf_parse_file()函数,已添加注释:
在我的scf框架里,用scf_block_t表示一个作用域和顺序块,可以是全局域、文件域、函数域、类型域,等等。
结构体类型或类类型的成员变量,就是属于类型域里的变量。
这个函数最后的while循环,从词法分析器里读取一个个的词,进行具体的语法分析。
它的代码如下图:
scf_dfa_parse_word()函数,是语法分析框架的入口函数。
从词法分析器读取一个单词,然后调用scf_dfa_parse_word()就可以进行具体的语法分析,它会一直分析完一个完整的语法块。
例如:while (*p) p++;
读取的第一个词是while,这时候调用scf_dfa_parse_word()会一直分析完整个while循环。
scf_dfa_parse_word()函数,内容如上,它会调用_scf_dfa_childs_parse_word()函数。
_scf_dfa_childs_parse_word()函数:它首先查看有没有接收这个词的前置hook,然后根据前置hook进行语法分析,并且清除这个hook;
如果没有前置hook,就判断要不要接收这个词,如果接收就调用_scf_dfa_node_parse_word()函数进行语法分析。
这个函数的主要功能,实际就是查看使用哪个子节点去进行下一步的语法分析。
具体的语法分析,在_scf_dfa_node_parse_word()里实现,见下面的几个图:
后置hook的使用场景是这样的:
while (*p) p++;
这里的右小括号,既是while的条件表达式结束的小括号,又是普通表达式(*p)结束的小括号。
普通表达式模块先运行,while模块后运行,但实际只有一个右小括号。
也就是说这1个小括号要触发两个模块的action()函数,所以后运行的那个模块就添加一个后置hook给dfa框架,让它自己可以在前一个模块运行完之后被正确触发。
end hook,结束hook,它在一个代码块的语法分析完成之后触发。
还是以上面的while循环为例,当循环体代码p++;分析完之后,整个while循环也就分析完了。
所以,最后的分号不但表示表达式p++的结束,也表示while循环的结束。
如果是while (*p) { p++; },则右大括号不但表示循环体的结束,也表示整个while循环的结束。
这种情况下触发多层的end hook。
例如:
while (i < 10)
while (j < i) i--, j++;
最后的分号会触发循环体和两层while循环的返回,也是使用end hook。
如果之前节点的action()函数返回NEXT_WORD,就读取下一个单词,继续进行语法分析。
如果是其他情况就返回对应的值,包括出现语法错误,或者一个代码块分析完了。
在需要继续分析的情况下,如果当前节点没有子节点了,就使用下一条语法规则。
如果当前节点有子节点,就使用它的子节点继续分析:
这里会调用_scf_dfa_childs_parse_word()函数,形成与_scf_dfa_node_parse_word()函数的递归调用。
这个递归调用的存在,让编辑语法规则的时候只要把某个节点添加到它自己的子节点,就可以自动实现递归。
只要把星号节点添加到它自己的子节点,就可以分析void**** ctx这种4级指针[呲牙]
最后给个scf框架的类型模块的语法规则,它是这么编辑的:
语法编辑在_dfa_init_syntax_type()函数被框架调用时进行,
编辑的方式,就是代码怎么写的就把对应的语法节点连接起来就行。
例如,static const int** p = NULL; 那么:
const是static的子节点,int是const的子节点,*是int的子节点,
同时*也是它自己的子节点,这样就可以通过框架的_scf_dfa_childs_parse_word()和_scf_dfa_node_parse_word()这两个函数的互相递归调用,实现对源代码的递归分析。
多级指针,是这种情况的典型场景。
具体的语法编辑,代码如下,2张图,也可以看我在gitee上的代码。
标签: #词法分析器作用是什么