语义分析

大观念是输入一个程序文本,我们把它分解为一个个小的词组(Token)

image-20200430170243609

Token

  • 名字 , \(xyz, pi\)
  • 常数 , \(43, 5.32\)
  • 操作符 \(+,=\)
  • 保留字 $if ,int $

Siever

在做分析之前,我们要进行预处理(pre-processing):

  • 扔掉多余的: 空格,注释
  • 收集Pragmas,
  • 用它们更具体的含义代替 Token,比如constants,names

正则表达式

\(\Sigma\) 时程序编写的有限字符集

定义:正则表达式的集合 \(E _{\Sigma}\) 时最小的集合 \(E\) ,满足:

  • \(\epsilon \in E\) ($$ 不是 \(\Sigma\) 中的新符号)
  • \(a \in E\) 对于所有 \(a \in \Sigma\)
  • \(\left(e_{1} | e_{2}\right),\left(e_{1} \cdot e_{2}\right) |, e_{1}^{*} \in E\), 若 \(e_{1}, e_{2} \in E\)

练习 1.1 根据要求写出正则表达式

我们定义了一个语法:

对于 \(e \in E _{\Sigma}\) ,我们递归地定义一个语言 \([e] \subseteq \Sigma^{*}\) 如下: \[ \begin{aligned} &[\epsilon]=\{\epsilon\}\\ &[a] \quad=\quad\{a\}\\ &\left[e^{*}\right] \quad=\quad([e])^{*}\\ &\left[e_{1} | e_{2}\right]=\left[e_{1}\right] \cup\left[e_{2}\right]\\ &\left[e_{1} \cdot e_{2}\right]=\left[e_{1}\right] \cdot\left[ e _{2}\right] \end{aligned} \] > 练习1.2 写出正则表达式的语言

对于一个正则表达式,我们也有一棵树与之对应:

image-20200430171931128

有穷自动机

一个不确定的有穷自动机( nicht-deterministischer endlicher Automat (NFA) ) 是一个元组 \(A=(Q, \Sigma, \delta, I, F)\) .

  • \(Q\) 是事件的有限集
  • \(\Sigma\) 是有限的字符集
  • \(I \subseteq Q\) 是初始状态集合
  • \(F \subseteq Q\) 是结束状态集合
  • \(\delta\) 是转移关系

\(\delta: Q \times \Sigma \rightarrow Q\) 是一个函数,且\(\# I=1\)(只有一个初始状态),那么它是确定的有穷自动机

transitiven Abschluss(传递闭包)

传递闭包 \(\delta ^{*}\) 是最小的集合 \(\delta '\) 满足:

  • \((p, \epsilon, p) \in \delta^{\prime}\)
  • \((p, xw, p) \in \delta^{\prime}\)\(\left(p, x, p_{1}\right) \in \delta \quad\)\(\quad\left(p_{1}, w, q\right) \in \delta^{\prime}\)

传递闭包描述了对于任意两个状态,它们怎么从一个到另一个。

可接受语言

一个自动机的可接受语言是: \[ L (A)=\left\{w \in \Sigma^{*} | \exists i \in I, f \in F: \quad(i, w, f) \in \delta^{*}\right\} \]

从表达式转换到NFA

Thompson's 算法

下面的式子可以这么转换,处理 \(n\) 长度的正则表达式需要 \(\mathcal{O} (n)\) 事件

image-20200508144440243

练习1.4:把表达式转换成自动机

Berry-Sethi算法

这个算法可以把一个正则表达式的树,转换成自动机。我们在每个节点的左右都画一个点。左边是入口,右边是出口。

我们使用数字来代替节点

image-20200508150747229
Glushkov Automaton

是使用 Berry-Sethi算法转换完后的自动机

结构如下:

状态:每个节点有左右两个点

image-20200508150459040

开始状态是左边的点,结束状态是右边的点。

转换关系:对于叶子就是左节点到右节点

image-20200508150545577

对于其他的可以用下面的表来表示

image-20200508150606620

练习 1.5 写出某个正则操作符的转换关系

如果我们直接把这些关系放到语法树上,那么浪费的节点有很多,我们因此要压缩信息

DFS处理标签

我们用DFS处理出下面的标签

  • first 状态点的集合,前面都是 \(\epsilon\)
  • next 这个点的下一个可能的字符
  • last 这个点最后的字符(到终点是 \(\epsilon\) )
  • empty 语言中包含 \(\epsilon\) 的节点

对于empty我们有下面的关系

image-20200508160838043

比如对于下面的树就是

image-20200508160906307

对于first有下面的关系

image-20200508161416199

对应图上就是:

image-20200508161354274

然后我们求next标签,它的关系如下:

image-20200508162255299

我们一般先考虑根节点的出口状态的next是空集,然后一步步倒推来求

image-20200508162341116

最后我们求last,和求first有点类似

image-20200508162543914

例子上是

image-20200508162558185

最后我们就有了高级版的Berry-Sethi算法

image-20200508162843886

我们把上述自动机称为 \(A_{e}\) . 比如之前的自动机就长这样:

image-20200508162956114

转换NFA为确定的自动机

比如上面的NFA可以使用Powerset来合并一些节点,由此转换

image-20200508221801202

定理

对于每个 NFA: \(A=(Q, \Sigma, \delta, I, F)\),我们都可以求一个确定的自动机 \(\mathcal{P} (A)\) \[ \mathcal{L} (A)= \mathcal{L} ( \mathcal{P} (A)) \] 构造:

状态: \(Q\) 的Powersets

开始状态:\(I\)

结束状态: \(\left\{Q^{\prime} \subseteq Q | Q^{\prime} \cap F \neq \emptyset\right\}\)

转换关系:\(\delta_{ \mathcal{P} }\left(Q^{\prime}, a\right)=\left\{q \in Q | \exists p \in Q^{\prime}:(p, a, q) \in \delta\right\}\)

但是\(Q\) 的Powerset可能有很多很多个,所以我们要根据实际情况来构造Powerset。比如下面这个例子

image-20200508221943386

我们只用到了之前的2个Powerset,所以我们就构造2个就可以了。

定理改进

对每个正则表达式 \(e\) 我们可以计算出一个确定自动机 \(A = \mathcal{P}(A_{e})\) \[ \mathcal{L} (A)=[ e] \]

设计扫描器Scanner

输入是一组规则,每一个正则表达式对应一个具体动作

image-20200508224419476

输出是一个程序。

它读取最多可以匹配到\(\left(e_{1}|\ldots| e_{k}\right)\) 的字符 \(w\)

然后确定其中一个最小的 \(i\) ,使得 \(w \in [e_{i}]\)

然后执行对应的动作 \(action_{i}\)

Idea

我们对 \(e=\left(e_{1}|\ldots| e_{k}\right)\) 建立一个NFA \(P \left(A_{e}\right)=\left(Q, \Sigma, \delta, q_{0}, F\right)\) ,我们定义下面的集合 \[ \begin{aligned} F_{1} &=\left\{q \in F | q \cap \operatorname{last}\left[e_{1}\right] \neq \emptyset\right\} \\ F_{2} &=\left\{q \in\left(F \backslash F_{1}\right) | q \cap \operatorname{last}\left[e_{2}\right] \neq \emptyset\right\} \\ & ... \\ F_{k} &=\left\{q \in\left(F \backslash\left(F_{1} \cup \ldots \cup F_{k-1}\right)\right) | q \cap \operatorname{last}\left[e_{k}\right] \neq \emptyset\right\} \end{aligned} \] 它们对应了每一个\(e_{i}\) 的状态。

对于输入 \(w\) ,它匹配到某一个动作,当且仅当 \(\delta^{*}\left(q_{0}, w\right) \in F_{i}\)

Scanner状态

Scanner可以有不同的状态,我们根据不同的状态来应对不同类型的文本,比如在处理注释的时候。

image-20200508230400445

左边<state> 是Scanner状态,后面是该状态对应的正则表达式和操作\(yybegin(state_{i})\) 是转换到第i个状态。

image-20200508230546473

比如在YYINITIAL中,看到了/* 就要转换到COMMENT状态.在 COMMENT状态下,看到了*/就要转换回YYINITIAL。

语法分析

image-20200528131127262

在语法分析 Syntatic analysis的任务是把Tokens变成更大的程序单元,如表达式,条件分支,循环。

Parser并不是用手写,而是生成出来的

image-20200528131328723

上下文无关文法

一个程序可能有任意多个token,但是只有固定的类别 Token-classes.

定义

一个上下文无关文法(context-free gramar) (CFG)是一个4元组 \(G = (N,T,P,S)\)

  • \(N\) : nonterminals 的集合就是可以拓展的表达式
  • \(T\): terminals 的集合,无法拓展的表达式
  • \(P\) : 规则的集合
  • \(S \in N\) : 开始符号

约定 Conventions

Context-free Grammar的格式是这样的: \[ A \rightarrow \alpha \quad \text { with } \quad A \in N, \quad \alpha \in(N \cup T)^{*} \] 比如

image-20200528132826341
序号

对于每个nonterminal,我们从左到右来记录扩展规则

image-20200528133440011

以上这两者描述的是相同的语言

派生 Derivation

一个序列 \(\alpha_{0} \rightarrow \ldots \rightarrow \alpha_{m}\) 是派生 derivation, 比如

image-20200528133218326

派生树

比如上面的可以这样画

image-20200528135157775

语法的唯一性

一个语法 $ G$ 是唯一的,若对于每个 \(w \in T^{*}\) 只有一个派生树

下推自动机

一个上下文无关文法推出的语言会被下推自动机(Pushdown Automata)所接受

定义

一个下推自动机(PDA) 是一个元组\(M=(Q,T,\delta,q_{0},F)\)

  • \(Q\) 是状态的有限集
  • \(T\) 是输入字符集
  • \(q_{0} \in Q\) 是开始状态
  • \(F \subseteq Q\) 是中止状态的集合
  • \(\delta \subseteq Q^{+} \times(T \cup\{\epsilon\}) \times Q^{*}\) 是传递关系的有限集

比如

image-20200528141041219

从开始状态开始,左边是0,右边第一个字符是a,于是把0替换成右边的11;然后左边第一个字符是1,右边第一个字符是a,所用第二条关系。以此类推...

所以计算关系是 \((\alpha \gamma, x w) \vdash\left(\alpha \gamma^{\prime}, w\right) \quad\) for \(\quad\left(\gamma, x, \gamma^{\prime}\right) \in \delta\)

确定的下推自动机

一个下推自动机 \(M\) 是确定的,若每个设置里有最多一个后继

定理

对于每个上下文无关文法 \(G = (N,T,P,S)\) ,我们可以造一个下推自动机 \(M\) , \(\mathcal{L}(G) = \mathcal{L}(M)\)

自顶向下Parsing

Item 下推自动机

它是一个前序遍历构造派生树的下推自动机,比如

image-20200528144923208

它的转换关系是:

image-20200528144945968

根据上面下推自动机的推导步骤可以推出来这个例子。

我们可以发现他有三种转换关系

  • Expansions(扩大): \(([A \rightarrow \alpha \bullet B \beta], \epsilon,[A \rightarrow \alpha \bullet B \beta][B \rightarrow \bullet \gamma])\) ,对于\(A \rightarrow \alpha B \beta, B \rightarrow \gamma \in P\)
  • Shifts(移动点): \(([A \rightarrow \alpha \bullet a \beta], a,[A \rightarrow \alpha a \bullet \beta])\) 对于\(A \rightarrow \alpha a \beta \in P\)
  • Reduces(减少): \(([A \rightarrow \alpha \bullet B \beta][B \rightarrow \gamma \bullet], \epsilon,[A \rightarrow \alpha B \bullet \beta])\) 对于 \(A \rightarrow \alpha B \beta, B \rightarrow \gamma \in P\)

问题

有可能会发生歧义,解决办法有2个想法

GLL Parsing

对每个冲突,制造一个虚拟拷贝然后平行计算

回溯

用DFS来回溯

看前面

可以看下一步输入来判断如何解决

LL(1)-Parser 的结构

image-20200701111256648

这个Parser访问长度为1的输入。

他对应一个下推自动机

表格 \(M[q, w]\) 包含选择的规则

定义 \(LL(1)\)

一个reduced grammer 是 \(LL(1)\) 若每2个冲突的规则 \(A \rightarrow \alpha, A \rightarrow \alpha^{\prime} \in P\) 且每次推导 \(S \rightarrow_{L}^{*} u A \beta\) , \(u \in T^{*}\) 下面的是合法的: \[ \text { First }_{1}(\alpha \beta) \cap \text { First }_{1}\left(\alpha^{\prime} \beta\right)=\emptyset \]

向前看集合 Lookahead Sets

定义 \(First_{1}\)- Sets

对于一个集合 \(L \subseteq T^{*}\) 定义: \[ \text { First }_{1}(L)=\{\epsilon \mid \epsilon \in L\} \cup\left\{u \in T \mid \exists v \in T^{*}: \quad u v \in L\right\} \] 就是 \(L\) 后面可以生成的集合,比如

image-20200701155649780

算数

\[ \begin{aligned} \text { First }_{1}(\emptyset) &=\emptyset \\ \text { First }_{1}\left(L_{1} \cup L_{2}\right) &=\text { First }_{1}\left(L_{1}\right) \cup \text { First }_{1}\left(L_{2}\right) \\ \text { First }_{1}\left(L_{1} \cdot L_{2}\right) &=\text { First }_{1}\left(\text { First }_{1}\left(L_{1}\right) \cdot \text { First }_{1}\left(L_{2}\right)\right) \\ &:=\text { First }_{1}\left(L_{1}\right) \odot_{1} \text { First }_{1}\left(L_{2}\right) \end{aligned} \]

定义 1-连接

\(L_{1}, L_{2} \subseteq T \cup\{\epsilon\}\)\(L_{1} \neq \emptyset \neq L_{2}\) , 那么 \[ L_{1} \odot_{1} L_{2}=\left\{\begin{aligned} L_{1} & \text { if } \epsilon \notin L_{1} \\ \left(L_{1} \backslash\{\epsilon\}\right) \cup L_{2} & \text { otherwise } \end{aligned}\right. \]

快速计算Lookahead Set

  • Let empty \((X)=\) true iff \(X \rightarrow^{*} \epsilon\)

我们对没有 \(\epsilon\)\(First_{1}\) 集合写成一个系统 \[ \begin{array}{l} F_{\epsilon}(a)=\{a\} \quad \text { if } \quad a \in T \\ F_{\epsilon}(A) \supseteq F_{\epsilon}\left(X_{j}\right) \quad \text { if } \quad A \rightarrow X_{1} \ldots X_{m} \in P, \quad \text { empty }\left(X_{1}\right) \wedge \ldots \wedge \text { empty }\left(X_{j-1}\right) \end{array} \] 比如

image-20200701160257111

然后我们可以把这些关系写成自动机的形式

image-20200701160452040

这个是变量依赖图(Variable Dependency Graph) 。然后我们把强连通分量合并。

image-20200701162216596

然后我们从没有入度的边开始,把它们的元素加入里面

image-20200701162230642

然后就可以把这个点“删掉”.

比如

image-20200701162329037

定义 \(Follow_{1}\)- Sets

\[ \begin{array}{lllll} \text { Follow }_{1}(S) & \supseteq & \{\$\} & & \\ \text { Follow }_{1}(B) & \supseteq & F_{\epsilon}\left(X_{j}\right) & \text { if } & A \rightarrow \alpha B X_{1} \ldots X_{m} \in P, \operatorname{empty}\left(X_{1}\right) \wedge \ldots \wedge \text { empty }\left(X_{j-1}\right) \\ \text { Follow }_{1}(B) & \supseteq & \text { Follow }_{1}(A) & \text { if } & A \rightarrow \alpha B X_{1} \ldots X_{m} \in P, \text { empty }\left(X_{1}\right) \wedge \ldots \wedge \text { empty }\left(X_{m}\right) \end{array} \]

也就是 \(S\) 的后面是什么

LL(1)-Lookahead Table

我们设表格 \(M[B, w]=i\)\(B \rightarrow \gamma^{i}\) if \(w \in\) First \(_{1}(\gamma) \odot_{1}\) Follow \(_{1}(B)\)

比如

image-20200701170141197

我们就可以根据这个表格来解决冲突

RR-CFG

定义 RR-CFG

一个 right-regular context-free grammar (RR-CFG) 是一个4元组 \(G=(N, T, P, S)\) ,其中

  • \(N\) : nonterminals 的集合就是可以拓展的表达式
  • \(T\): terminals 的集合,无法拓展的表达式
  • \(P\) : 正则表达式规则的集合
  • \(S \in N\) : 开始符号

例如数学表达式:

image-20200703154949710

我们可以把这个规则通过添加标签转化一下,拆解开来

转化的规则是

image-20200703155042774

所以上面的可以写成

image-20200703155103089

我们可以得到一个\(LL(1)\) 的语法

定义 RLL(1)

一个 RR-CFG \(G\) 是 RLL(1), 若它的 CFG \(\langle G\rangle\) 是一个 \(LL(1)\) 语法

Recursive Descent RLL Parsers

Recursive desent RLL(1)-parser 是一个表格驱动的parser

image-20200703155457726

对于 S() 函数是一个生产器

image-20200703155528306

它具体是

image-20200703155549617

对于正则表达式的逻辑可以这样实现

image-20200703155612955