字母表 Σ \Sigma Σ,如{a,b,c,d}
假设A,B为两个字母表
加法: A + B = { ω ∣ ω ∈ A o r ω ∈ B } A+B=\{ \omega|\omega\in A \ or \ \omega \in B\} A+B={ω∣ω∈A or ω∈B}
乘积: A ∗ B = { a b ∣ a ∈ A , b ∈ B } A*B = \{ab|a \in A,b \in B \} A∗B={ab∣a∈A,b∈B}
n次幂: A 0 = { ε } A^0=\{\varepsilon \} A0={ε}
A n = A n − 1 A A^n=A^{n-1}A An=An−1A
正闭包: A + = A ∪ A 2 ∪ A 3 . . . . . . A^+=A \cup A^2 \cup A^3...... A+=A∪A2∪A3......,例如: { 0 , 1 } + = { 0 , 1 , 00 , 01 , 10 , 11 , 000 , 001 , 010 , 011 , 100 , . . . . . . } \{0,1\}^+=\{0,1,00,01,10,11,000,001,010,011,100,......\} {0,1}+={0,1,00,01,10,11,000,001,010,011,100,......}
克林闭包: A ∗ = A 0 ∪ A + A^*=A^0 \cup A^+ A∗=A0∪A+, 例如: { 0 , 1 } ∗ = { ε , 0 , 1 , 00 , 01 , 10 , 11 , 000 , 001 , 010 , 011 , 100 , . . . . . . } \{0,1\}^*=\{\varepsilon,0,1,00,01,10,11,000,001,010,011,100,......\} {0,1}∗={ε,0,1,00,01,10,11,000,001,010,011,100,......}
句子: ∀ x ∈ A ∗ , x 称 为 一 个 句 子 , ε 叫 做 空 句 子 \forall x \in A^*,x称为一个句子,\varepsilon 叫做空句子 ∀x∈A∗,x称为一个句子,ε叫做空句子
前缀: 对 ∀ x , y , z ∈ A ∗ , 如 果 x = y z , 则 y 是 x 的 前 缀 , z 不 为 ε 时 称 作 真 前 缀 对\forall x,y,z \in A^*,如果x=yz,则y是x的前缀,z不为\varepsilon 时称作真前缀 对∀x,y,z∈A∗,如果x=yz,则y是x的前缀,z不为ε时称作真前缀
后缀: 对 ∀ x , y , z ∈ A ∗ , 如 果 x = y z , 则 z 是 x 的 后 缀 , y 不 为 ε 时 称 作 真 后 缀 对\forall x,y,z \in A^*,如果x=yz,则z是x的后缀,y不为\varepsilon 时称作真后缀 对∀x,y,z∈A∗,如果x=yz,则z是x的后缀,y不为ε时称作真后缀
语言: ∀ L ⊆ Σ ∗ \forall L \subseteq\Sigma ^* ∀L⊆Σ∗,L称为字母表 Σ \Sigma Σ上的语言
上下文有关文法CSG:
上下文无关文法CFG:
正规文法RG:
如果为下述样式则称为3型文法:
A → a B 或 A → a A \rightarrow aB或A \rightarrow a A→aB或A→a(左线性)
A → B a 或 A → a A \rightarrow Ba或A \rightarrow a A→Ba或A→a(右线性)
其识别系统是有穷自动机
又称为分析树、推导树、派生树
短语:一颗子树的所有叶子自左向右排列起来,形成一个相对于子树根的短语。
直接短语:仅仅有父子两代的一颗子树的所有叶子
句柄:一个句型的分析树中的最左那颗只有父子两代的子树的所有叶子的自左至右排列。
功能:输入源程序,输出单词符号(token)
单词的种类:
关键字:begin、end…
标识符:用户定义的各种名字
常数
运算符:+,-
分界符:, . ;
一般对种类编码时,保留字和分界符最好每一个都单独编码。
正则表达式和正则文法等价:教材p77,可以看一下
如图为正则表达式转换为正则文法的例题:
确定的有穷自动机(DFA):
五元组: M = ( Q , Σ , δ , q 0 , F ) M=(Q,\Sigma,\delta,q_0,F) M=(Q,Σ,δ,q0,F),依次为:有穷状态集合、字母表、状态转移映射、初始状态、终止状态
DFA所接受的语言:
L ( M ) = { ω ∣ ω ∈ Σ ∗ , 且 δ ( q 0 , ω ) ∈ F } L(M)=\{\omega| \omega \in \Sigma^*,且\delta(q_0,\omega) \in F\} L(M)={ω∣ω∈Σ∗,且δ(q0,ω)∈F}.
状态转换图
有穷状态自动机和正则文法等价。
单词识别:
自 顶 向 下 : { 递 归 子 程 序 法 预 测 分 析 法 ( L L ( 1 ) ) 自顶向下: \begin {cases} 递归子程序法 \\ 预测分析法(LL(1)) \end {cases} 自顶向下:{递归子程序法预测分析法(LL(1))
自 底 向 上 { 算 符 优 先 分 析 法 L R ( 0 ) , S L R ( 1 ) , L R ( 1 ) , L A L R ( 1 ) 自底向上 \begin {cases} 算符优先分析法 \\ LR(0),SLR(1),LR(1),LALR(1) \end {cases} 自底向上{算符优先分析法LR(0),SLR(1),LR(1),LALR(1)
二义性问题
回溯问题:对于产生式A,如果A有多个候选式存在公共前缀,则自顶向下的语法分析程序将无法根据当前输入符号准确选择用于推导的产生式。此时需要向前试探,如果不正确则需要回溯。
左递归引起的无穷推导:
对上下文无关文法的改造
消除二义性
消除左递归:
提取左因子:
如 果 有 产 生 式 : A → a b 1 ∣ a b 2 . . . 则 可 以 用 下 面 的 产 生 式 替 换 { A → a A ′ A ′ → b 1 ∣ b 2 . . . 如果有产生式:A \rightarrow ab_1|ab_2... \\ 则可以用下面的产生式替换\begin {cases} A \rightarrow aA' \\ A' \rightarrow b_1|b_2... \end {cases} 如果有产生式:A→ab1∣ab2...则可以用下面的产生式替换{A→aA′A′→b1∣b2...
LL(1)文法
FIRST(a):从a推导出的串的首符号
FOLLOW(A): F O L L O W ( A ) = { b ∣ S ⇒ ∗ a A b β , b ∈ T , a , b ∈ ( V ∪ T ) ∗ } FOLLOW(A)=\{ b|S \overset{*} {\Rightarrow } aAb\beta ,b \in T,a,b \in (V \cup T)^*\} FOLLOW(A)={b∣S⇒∗aAbβ,b∈T,a,b∈(V∪T)∗}.
对于一个文法G,如果G的==任意两个具有相同左部的产生式 A → a ∣ b A \rightarrow a|b A→a∣b==满足以下条件:
则称G为LL(1)文法
步骤:
关键:构造预测分析表
在启动时,输入指针指向输入串的第一个字符,分析栈中存放栈底符号#和文法开始符号S
根据栈顶符号A和读入的符号a,查看分析表,以进行相应的动作。
如 E → T E ′ E\rightarrow TE' E→TE′,执行动作时,将E弹出栈,然后先将E’入栈,再将T入栈。
预测分析表
思想:从输入串出发,反复利用产生式进行规约,如果最后能得到文法的开始符号,则输入串是句子。一旦句柄在栈顶形成,就将其弹出归约。
移进-归约分析:分析栈、输入缓冲区
四种动作:移进,归约,接受,出错
有什么问题?
如何确定句柄的开始处与结束处?
算符文法:如果文法中不存在: A → a B C β A \rightarrow aBC\beta A→aBCβ的产生式,即不具有相邻非终结符的产生式,则称为算符文法。
算符优先文法:假设G是一个不含$\varepsilon $-产生式的文法:
如果对于不含$\varepsilon $-产生式的G的任意a,b,a和b至多有一种关系成立,则称G为算符优先文法。
FIRSTOP(B):以B为左部的产生式的最开头的终结符
LASTOP(B):以B为左部的产生式的最末尾的终结符
原理:如果栈中的符号<输入符号,则移进,如果> ,则归约
问题:有时未归约真正的句柄(S);不是严格的最左归约
素短语:至少含有一个终结符,且不含更小的含终结符的短语。
最左素短语:算符优先分析过程中识别出来的“句柄”(不是真正意义上的句柄),即在树状图中的素短语最左的那个
优先函数:用两个优先选择函数f和g,做一个从终结符到整数的映射。
优先函数的损失:id>id不存在,但是f(id)和g(id)可以比较
优先函数的构造:对于终结符a或者#,建立 f a , g a f_a,g_a fa,ga,并构造有向图。
算符优先文法简单小结:
L:从左到右扫描输入符号;R:最右推导对应的最左归约;k:超前读入k个符号
总体结构:
规范句型:分析栈中内容+剩余输入符号,即最左归约形成的句型。
活前缀:不含句柄右侧任意符号的规范句型的前缀(活前缀在分析栈中,可以说活前缀就是句柄的子集)
LR(0)分析表的构造
上下文无关文法不总是LR(0)文法
项目集 I I I的相容:
SLR(1)分析表的构造
对于项目集 I I I:
如果I中存在归约项目如: B → a b c . B \rightarrow abc. B→abc. ,那么在下一个输入符号d来临时,假如d在B的FOLLOW集中,则进行归约操作,反之进行移入操作。
局限性:仅使用FOLLOW集依然有可能产生冲突
构造分析表:
LR(1)分析表的构造
LALR(1)分析表的构造
LR分析的基本步骤:
S-属性定义:只含有综合属性的语法制导定义称为S属性定义。
通常使用自底向上的分析方法。即在用哪个产生式进行归约后,就执行那个产生式的S-属性定义计算属性的值。
L-属性定义:当且仅当它的每个属性是综合属性,或者是满足如下条件的继承属性:设有产生式 A → X 1 X 2 . . . X n A\rightarrow X_1X_2...X_n A→X1X2...Xn,其右部符号 X i X_i Xi的继承属性只依赖于下列属性:
语法树
翻译模式:是语法制导定义的一种便于实现的书写形式。语义规则或语义动作用{}括起来,并可以被插入到产生式右部的任何合适的位置上。
翻译模式的设计:根据语法制导定义:
语法制导定义是L-属性定义:
语法制导定义是S-属性定义
S-属性定义的自底向上计算:
实现:只要将每个语义规则放到相应产生式的末尾即可得到一个和翻译模式,在对产生式进行归约时执行相应的语义动作。
在分析栈中使用一个附加的域来存放综合属性值。
采用自底向上分析。
L-属性定义的自顶向下翻译:
用翻译模式构造自顶向下的翻译
从翻译模式中消除左递归
对于一个翻译模式,若采用自顶向下分析,必须消除左递归和提取左公因子。
将语义动作看作终结符号,当其处在栈顶时被弹出执行
为了便于讨论,只看S-属性定义的左递归消除:
设 有 如 下 左 递 归 翻 译 模 式 : { A → A 1 Y { A . a = g ( A 1 . a , Y . y ) } A → X { A . a = f ( X . x ) } 则 消 除 左 递 归 后 : { A → X R R → Y R ∣ ε 翻 译 模 式 变 为 : { A → X { R . i = f ( X . x ) } R { A . a = R . s } R → Y { R 1 . i = g ( R . i , Y . y ) } R 1 { R . s = R 1 . s } R → ε { R . s = R . i } 设有如下左递归翻译模式: \begin {cases} A \rightarrow A_1Y \ \ \ \ \{ A.a = g(A_1.a,Y.y)\} \\ A \rightarrow X \ \ \ \ \ \ \ \ \{A.a = f(X.x) \} \end {cases} \\ 则消除左递归后: \begin {cases} A \rightarrow XR \\ R \rightarrow YR|\varepsilon \end {cases} \\ 翻译模式变为: \begin {cases} A \rightarrow X\{R.i = f(X.x) \}R \{A.a = R.s \} \\ R \rightarrow Y\{R_1.i = g(R.i,Y.y) \}R_1 \{R.s = R_1.s \} \\ R \rightarrow \varepsilon \{ R.s = R.i\} \end {cases} \\ 设有如下左递归翻译模式:{A→A1Y {A.a=g(A1.a,Y.y)}A→X {A.a=f(X.x)}则消除左递归后:{A→XRR→YR∣ε翻译模式变为:⎩⎪⎨⎪⎧A→X{R.i=f(X.x)}R{A.a=R.s}R→Y{R1.i=g(R.i,Y.y)}R1{R.s=R1.s}R→ε{R.s=R.i}
L-属性定义的递归下降翻译法
L-属性定义的LL(1)翻译法
与递归子程序法的区别与联系:
语法栈按照LL(1)分析法,将语义子程序视作终结符,当在栈顶时,执行相应的动作对语义栈进行修改。
当最终语义栈中只有T.node时,代表语法树的根节点
L-属性定义的自底向上翻译
+ 下面讨论如何在自底向上的分析中实现L-属性定义
+ 给定一个L-属性定义,假设它的每个非终结符A都有一个继承属性A.inh,而且每个文法符号X都有一个综合属性X.syn
+ 如果X是终结符,则其综合属性值就是词法分析器所返回的词法值
+ 假设分析栈仍是stack,语义栈仍为val,如果stack[i]代表文法符号X,那么stack[i].val将保存X.syn
+ 为了在自底向上的分析过程中计算继承属性,需要对每一个产生式引入标记性非终结符M
+ 归约M1时,它出现在产生式 A → M 1 X 1 M 2 X 2 . . . A \rightarrow M_1X_1M_2X_2... A→M1X1M2X2...中,从而可以确定为计算继承属性X1.inh所需要的属性的位置。
+ 归约非标记符号时,如 A → M 1 X 1 M 2 X 2 . . . A \rightarrow M_1X_1M_2X_2... A→M1X1M2X2...,只需要计算A.syn,而需要的其他属性已经在栈中了。
作用:为变量或常量名指定类型
类型表达式:
类型等价;
声明语句的文法
临时变量:大量临时变量的使用对优化有利;但是会增加符号表管理的负担,也会增加运行时临时数据占的空间。
数组元素的寻址:bash+偏移地址
x = a [ i 1 , i 2 , i 3 ] o f f s e t = w i d t h ∗ [ [ i 1 ∗ n 2 + i 2 ] ∗ n 3 + i 3 ] x = a[i_1,i_2,i_3] \ \ \ offset=width*[ \ [i_1*n_2+i_2]*n_3+i_3 ] x=a[i1,i2,i3] offset=width∗[ [i1∗n2+i2]∗n3+i3]
带有数组引用的赋值语句的翻译
布尔表达式的翻译:
常见控制结构的翻译
文法
S → i f B t h e n S 1 S → i f B t h e n S 1 e l s e S 2 S → w h i l e B d o S 1 S → b e g i n S l i s t e n d S l i s t → S l i s t ; S ∣ S 其 中 B 是 布 尔 表 达 式 S \rightarrow if \ B \ then \ S_1 \\ S \rightarrow if \ B \ then \ S_1 \ else \ S_2 \\ S \rightarrow while \ B \ do \ S_1 \\ S \rightarrow begin \ Slist \ end \\ Slist \rightarrow Slist;S|S \\ 其中B是布尔表达式 S→if B then S1S→if B then S1 else S2S→while B do S1S→begin Slist endSlist→Slist;S∣S其中B是布尔表达式
翻译:(例子)
KaTeX parse error: Undefined control sequence: \ at position 152: …':')||S_1.code \̲ ̲\}
布尔表达式的控制流翻译
混合模式的布尔表达式翻译
决策:
名字及其绑定
声明的作用域
静态作用域:如果只通过考察其程序就可以确定某个声明的作用域,则称该语言使用静态作用域;否则是动态作用域
显式访问控制:public、private、protected
动态作用域:像java中的例子: x . m ( ) 且 : 1. 有 一 个 类 C 带 有 方 法 m ( ) ; 2. D 是 C 的 子 类 , D 也 有 一 个 方 法 m ( ) x.m() \ 且:1.有一个类C带有方法m();2.D是C的子类,D也有一个方法m() x.m() 且:1.有一个类C带有方法m();2.D是C的子类,D也有一个方法m().
一般来说,编译时无法确定x是类C的对象还是子类D的对象,直到运行时才能确定
过程及其活动
参数传递方式
运行时内存的划分
− − − − − − 目 标 代 码 − − − − − − 静 态 数 据 − − − − − − 栈 − − − − − − 空 闲 空 间 − − − − − − 堆 − − − − − − ------\\ 目标代码\\ ------\\ 静态数据\\ ------\\ 栈\\ ------\\ 空闲空间\\ ------\\ 堆\\ ------\\ −−−−−−目标代码−−−−−−静态数据−−−−−−栈−−−−−−空闲空间−−−−−−堆−−−−−−
目标代码:在编译时即可确认
静态数据:部分数据大小在编译时也可确认
栈:可动态增长
堆:可动态增长
活动记录
活动记录:过程的每次活动(函数的每次调用)所需要的信息用一块连续的存储区来管理。
采用栈式存储分配机制
运行时,每当将进入一个过程就有一个相应的活动记录压入栈顶。
临时变量:存放临时数据对象,如计算表达式时的临时变量。
局部数据区:局部变量、内情变量、临时工作单元(如存放对表达式求值的结果)
静态链:用它来访问存放在其他活动记录中的非局部数据。
动态链:用来指向调用者的活动记录。
形式单元:存放相应的实参的地址或值。
旧SP:前一活动记录的地址
局部数据的组织
全局存储分配策略
如果过程允许递归:必须保存每次调用相应的数据区内容(活动记录)
引入一个运行栈,让过程的每次执行和过程的活动记录相对应。
调用序列和返回序列
C语言的过程调用和返回
栈中的可变长数据
无嵌套过程的静态作用域的实现(C语言)
包含嵌套过程的静态作用域的实现(Pascal语言)
嵌套深度:主程序名的嵌套深度为1,每当进入一个额内层过程时深度加一。
访问链:如果将过程p直接嵌套在过程q中,则p的活动记录的访问链指向q的最近一次活动的活动记录的访问链。这样,沿着访问链找到的活动记录,就是当前正在运行的过程可以访问数据和对应过程的活动记录。
a 深 度 为 : n a ∣ ( 相 差 n p − n a 个 访 问 链 ) ∣ p 深 度 为 : n p 这 样 可 以 快 速 定 位 到 a 所 在 的 活 动 记 录 , 由 于 a 所 在 位 置 是 : 活 动 记 录 中 相 对 于 某 个 位 置 的 固 定 偏 移 。 故 a 可 以 访 问 到 。 a \ \ 深度为:n_a \\ | \\ (相差 n_p-n_a个访问链) \\ | \\ p \ \ 深度为:n_p \\ 这样可以快速定位到a所在的活动记录,由于a所在位置是:活动记录中相对于某个位置的固定偏移。故a可以访问到。 a 深度为:na∣(相差np−na个访问链)∣p 深度为:np这样可以快速定位到a所在的活动记录,由于a所在位置是:活动记录中相对于某个位置的固定偏移。故a可以访问到。
如何建立访问链:
过程型参数的访问链:
动态作用域的实现(Lisp语言)
一个新的活动记录将继承已经存在的非局部名字与存储位置的绑定。
实现动态作用域的方法:
深访问:用控制链搜索运行栈,寻找包含该非局部名字的第一个活动记录。
浅访问:将每个名字的当前值保存在静态分配的内存空间中。当过程p开始一个新的活动时,p的局部名字n使用在静态数据区分配给n的内存单元。n的先前值必须保存在p的活动记录中,当p的活动结束时再恢复。
即:直接到内存找,看有没有同名的,如果有直接拿来用,但是这个n的原来的值需要保存在活动记录中。
如果需要将函数作为参数传递或者作为结果返回,则使用深访问方法更方便一些。