Skulpt-源码浅析-Parser

Skulpt中Parser是一个从cpython的lib2to3派生的解析器

该Parser采用了自底向上的语法分析,也就是从分析树的底部(叶节点)向顶部(根节点)方向构造分析树。

注: Skulpt 后来搞了一个 skulpt_parser 用于 python3.9 语法解析。python3.9提出了一个 新的 使用LL(1)的 Parser。

自底向上分析

自底向上的语法分析采用最左归约方式(反向构造最右推导),其通用的分析框架为 移入-归约分析(Shift-Reduce Parsing)。

移入-归约分析

移入-归约分析的工作过程

  1. 在对输入串的一次从左到右扫描过程中,语法分析器将零个或多个输入符号移入到栈的顶端,
    直到它可以对栈顶的一个文法符号串β进行归约为止
  2. 然后,它将β归约为某个产生式的左部
  3. 语法分析器不断地重复这个循环,直到它检测到一个语法错误,或者栈中包含了开始符号且输入缓冲区为空(当进入这样的格局时,语法分析器停止运行 并宣称成功完成了语法分析)为止

移入-归约分析器可采取的4种动作

  1. 移入:将下一个输入符号移到栈的顶端
  2. 归约:被归约的符号串的右端必然处于栈顶。语法分析器在栈中确定这个串的左端,并决定用哪个非终结符来替换这个串
  3. 接收:宣布语法分析过程成功完成
  4. 报错:发现一个语法错误,并调用错误恢复子例程

LR分析法

LR文法(Knuth, 1963) 是最大的、可以构造出相应移入-归约语法分析器的文法类

  1. L: 对输入进行从左到右的扫描
  2. R: 反向构造出一个最右推导序列
  3. K: 需要向前查看k个输入符号的LR分析。 k = 0 和 k = 1 这两种情况具有实践意义。当省略(k)时,表示k=1

LR分析器的总体结构:
LR分析器的总体结构

LR算法流程:
LR算法流程

Skulpt Parser

parse

parse 需要 filename、input 参数,其中:filename 被 makeParser 用于构造 Parser对象 , input 被 readline 用于构造 字符流。

Sk._tokenize 是一个词法分析器,解析 字符流 形成 符号流,编码为utf-8

对于字符流调用回调函数yield_,yield_需要TokenInfo类型的对象

回调函数中调用了 parser.addtoken 进行语法分析

至于分支 tokenInfo.type === T_COMMENT || tokenInfo.type === T_NL || tokenInfo.type === T_ENCODING 里的代码有什么用,我也不知道

Parser的构造

makeParser 用于构造一个Parser对象,需要传入filename和style,但实际上只要传入filename就好。当style为undefined时会被置为"file_input",而Parser也仅支持"file_input"。

然后 makeParser 创建了一个 Parser对象,并且调用了 setup 方法,最后将 Parser对象 返回。

Parser对象 的 构造函数 需要 filename 和 grammar 两个参数。grammar就是LR分析器所需要的分析表,makeParser传入的实参是 Sk.ParseTables。Parser构造函数中还将 p_flags 赋值为 0,p_flags与输出相关。

Sk.ParseTables 源码位于 parse_tables.js,该文件的生成脚本源码位于regenparser.js, 本质上使用了pgen中的代码生成 parse_tables.js

setup 构造了 LR分析器所需要的状态符号栈。栈中元素包含属性 dfa、state 和 node,node 包含属性 type、value、context 和 children

addtoken

编译原理教学

addtoken 需要 type, value, context 三个参数

  • 例如 第2行第1列有个abc
    1. type 为 Sk.token.tokens.T_NAME
    2. value 为 abc
    3. context 包含了 具体信息, 例如context[0][0]=2, context[0][1]=1

实际上就是 查表 然后进行移入归约操作

classify

classify 将 终结符类型 转成 对应数字

push

Parser 的 push 方法 用于添加 非终结符

push方法 将 栈顶元素 的 state 改为 newstate

该方法用 type、context 构造了 newnode,进而用newdfa、newnode构造了新的对象入栈

shift

pop

弹出状态符号栈的栈顶元素。

如果栈内不止一个元素,自底向上构造语法树; 否则rootnode指向语法树根节点.

参考资料