编译原理-第二篇

2.词法分析

词法分析的目的是什么呢? 是把一长串的字符分解成一个个的词素,并且生成(id,2)这种作为语法分析的基础。

首先是词法单元,词素等的定义。

然后是如何输入。

三是输入后如何识别。

识别后如何组成中间式子。

  1.三个关键词

词法单元,词素,模式

  1. 词法单元: 词法单元由一个词法单元名和一个可选的属性值组成。

词法单元名是一个表示某种词法单位的抽象符号。比如一个特定的关键字

或者代表一个标识符的输入字符序列。词法单元名字是语法分析器的输入符号。

2.模式 描述了一个词法单元的词素可能具有的形式。 比如if 关键字,模式是对

语法或者词法中你某些特定的字符的抽象表达。 比如  标识符,关键字,等

3.词素,源程序的一个字符序列。

 

2.输入缓冲 ---进行词法分析最首先的是要进行字符串的输入。

如何输入也是一个问题。

1. 如果一个字符一个字符的输入,则显得太快缓慢。而全部读入这太占内存。所以这里有了一个输入缓冲的概念。

2.就是进行词法分析的时候,只读一个字符是无法分析这个词素属于什么模式。是一个标识符还是关键字,还是界符之类的。所以就有了超前搜索这个概念。

至少必须向前多查找一个字符做比对才知道这个词素属于什么模式

3.为什么需要缓冲区对?

缓冲区是为了加快读入数据的效率,当输入字符超长的时候,一个缓冲区并不能完全识别串,所以需要缓冲区对。然后又加入了哨兵标记。

那么哨兵标记是干什么的呢?

 

 

3.词法规约:

       3.1.串和语言 这里的定义的串和语言比较泛化。不够深入和详尽。也可以表示为集合。

暂且理解为串是字母表符号的有限集合,而语言是串的集合

串: {a,b,v,d,s,s,}  

语言:{{a,v,c,,s},{dwmme,r,r,t,q,r}}

 

上图中确实很好的解释了串和语言了

L {A,B,…,a,b,…z}   

D {0,1,2,3,4,5,…,9} 

L看成是大小写字母组成的字母表,将D看成是10个数位组成的字母表。

另一种方法是将LD看做语言,他们的所有串的长度都为1.  A是长度为1的串 1,也是长度为1的串。

 

 

         1.串是一个字母表符号的有限集合。而字符表是一个有限的符号集合。符号包括了数字,字母和标点符号。{a,b,c,d,e}

         2.语言:语言是给定某个字母表上一个任意的可数的串的集合。

            {a,ab,abc,ed,treeeasdasa}

 3.闭包

编译原理的闭包:

 V是一个符号集合,假设V指的是三个符号a, b, c的集合,记为 V = {a, b, c }
V* 读作“V的闭包,它的数学定义是V自身的任意多次自身连接(乘法)运算的积,也是一个集合。
也就是说,用V中的任意符号进行任意多次(包括0次)连接,得到的符号串,都是V*这个集合中的元素。
0次连接的结果是不含任何符号的空串,记为 ε
1次连接就是只有一个符号的符号串,比如,ab c
2次连接是两个符号构成的符号串,比如,aa, ab, ac, ba, bb, bc,等等

3.2  闭包的解释

 

 

3.2.1 编译原理书籍闭包解释:闭包和正则

正则表达式的最基本概念来重新介绍一次,主要想让大家更深地理解它。首先我们要重新定义一下语言这个概念。语言就是指字符串的集合,其中的字符来自于一个有限的字符集合。也就是说,语言总要定义在一个有限的字符集上,但是语言本身可以既可以是有穷集合,也可以是无穷集合。比如“C#语言就是指满足C#语法的全体字符串的集合,它显然是个无穷集合。当然也可以定义一些简单的语言,比如这个语言{ a }就只有一个成员,那就是一个字母a。后面我们都用大括号{}来表示字符串的集合。所谓正则表达式呢,就是描述一类语言的一种特殊表达式,正则表达式共有2种基本要素:

  1. 表达式ε表示一个语言,仅包含一个长度为零的字符串,可以理解为{ String.Empty },我们通常把String.Empty记作ε,读作epsilon
  2. 对字符集中任意字符a,表达式a表示仅有一个字符a的语言,即{ a }

同时正则表达式定义了3种基本运算规则:

  1. 两个正则表达式的,记作X|Y,表示的语言是正则表达式X所表示的语言与正则表达式Y所表示语言的并集。比如a|b所得的语言就是{a, b}。类似于加法
  2. 两个正则表达式的连接,记作XY,表示的语言是将X的语言中每个字符串后面连接上Y语言中的每一种字符串,再把所有这种连接的结果组成一种新的语言。比如令X = a|bY = c|d,那么XY所表示的语言就是{ac, bc, ad, bd}。因为X表示是{a, b},而Y表示的是{ c, d},连接运算取X语言的每一个字符串接上Y语言的每一个字符串,最后得到了4种连接结果。这类似于乘法
  3. 一个正则表达式的克林闭包,记作X*,表示分别将零个,一个,两个……无穷个X与自己连接,然后再把所有这些求并。也就是说X* = ε | X | XX | XXX | XXX | ……比如a*这个正则表达式,就表示的是个无穷语言{ ε, a, aa, aaa, aaaa, …. }。这相当于任意次重复一个语言。

以上三种运算写在一起时克林闭包的优先级高于连接运算,而连接运算的优先级高于并运算。以上就是正则表达式的全部规则!并不是很难理解对吧?下面我们用正则表达式来描述一下刚才各个词素的规则。

 

首先是关键字string,刚才我们描述说它是正好是s-t-r-i-n-g这几个字母按顺序组成,用正则表达式来表示,那就是s-t-r-i-n-g这几个字母的连接运算,所以写成正则表达是就是string。大家一定会觉得这个例子很无聊。。那么我们来看下一个例子:标识符。用白话来描述是由字母开头,后面可以跟零个或多个字母或数字。先用正则表达式描述由字母开头,那就是指,可以是a-z中任意一个字母来开头。这是正则表达式中的运算:a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z。如果每个正则表达式都这么写,那真是要疯掉了,所以我们引入方括号语法,写在方括号里就表示这些字符的并运算。比如[abc]就表示a|b|c。而a-z一共26个字母我们也简写成a-z,这样,由字母开头就可以翻译成正则表达式[a-z]了。接下来我们翻译第二句后面可以跟零个或多个字母或数字这句话中的零个或多个可以翻译成克林闭包运算,最后相信大家都可以写出来,就是[a-z0-9]*。最后,前后两句之间是一个连接运算,因此最后描述标识符语言的正则表达式就是[a-z][a-z0-9]*。其中的*运算也意味着标识符是一种无穷语言,有无数种可能的标识符。本来就是这样,很好理解对吧?

 

从上面例子可以看出,正则表达式都可以用两种要素和三种基本运算组合出来。但是如果我们要真的拿来描述词法单词的规则,需要一些便于使用的辅助语法,就像上边的方括号语法那样。我们定义一些正则表达式的扩展运算:

  1. 方括号表示括号内的字符并运算。[abc]就等于a|b|c
  2. 方括号中以^字符开头,表示字符集中,排除方括号中的所有字符之后,所剩字符的并运算。[^ab]就表示除了ab以外所有字符求并。
  3. .点表示字符集内所有字符的并。因此 .* 这个表达式就能表示这种字符集所能组成的一切字符串。
  4. X?表示 X|ε 。表示X与空字符串之间可选。
  5. X+表示XX*。这等于限制了X至少要重复1次。

用过正则表达式的同学应该都熟悉以上运算了。其实.NET中的正则表达式还提供更多的扩展语法,但我们这次并不使用.NET的正则库,所以就不列出其余的语法了。

 

我们把所有能用正则表达式表示的语言称作正则语言。很遗憾,并非所有的语言都是正则语言。比如C#,或者所有编程语言、HTMLXMLJSON等等,都不是正则语言。所以不能用正则表达式定义上述语言的规则。但是,用正则表达式来定义词法分析的规则却是非常合适的。大部分编程语言的词素都可以用一个简单的正则表达式来表达。下面就是上述单词的正则表达式定义。

 

为什么需要闭包。因为编译原理是在一个字符长串或者集合中找到正确的词。

 

闭包之后是正则表达式

letter_|digit*就是闭包运算。通过闭包运算来形成表达式。

我们数学中也有运算,比如 a+b  1+2 然后  1+2=3 这就形成了表达式 1+2是一个加法表达式。  + 表示加法运算。  闭包表示闭包运算。通过运算形成表达式。通过表达式形成自动机,其实自动机可以算成是等式。

什么是式,是一种格式,规范等。 表达式,算式,等式。

什么是正则表达式。 表示正则关系的规范。

 

3.2.2二元关系理解闭包:

 

集合X与集合Y上的二元关系是R=(X,Y,G(R)),其中G(R),称为R,是笛卡儿积X×Y的子集。若 (x,y) G(R) ,则称xR-关系于y,并记作xRyR(x,y)。否则称xy无关系R。但经常地我们把关系与其图等同起来,即:若RX×Y,则R是一个关系。

例如:有四件物件 {球,糖,车,枪} 及四个人 {甲,乙,丙,丁} 若甲拥有球,乙拥有糖,及丁拥有车,即无人有枪及丙一无所有则二元关系"...拥有"便是R=({球,糖,车,枪}, {甲,乙,丙,丁}, {(,), (,), (,)})

其中 R 的首项是物件的集合,次项是人的集合,而末项是由有序对(物件,主人)组成的集合。比如有序对(球,甲)G(R),所以我们可写作"R",表示球为甲所拥有。

不同的关系可以有相同的图。以下的关系 ({球,糖,车,枪}, {甲,乙,丁}, {(,), (,), (,)} 中人人皆是物主,所以与R不同,但两者有相同的图。话虽如此,我们很多时候索性把R定义为G(R) "有序对 (x,y) G(R)" 亦即是 "(x,y) R"

二元关系可看作成二元函数,这种二元函数把输入元xXyY视为独立变量并求真伪值(即有序对(x,y) 是或非二元关系中的一元此一问题)。

X=Y,则称RX上的关系。

进行关系运算。

 

3.2.3数学中的闭包:

数学中是闭的集合,也就是集合和它的边界的并。集合e的全体聚点并上e称为e的闭包。关系的闭包运算时关系上的一元运算,它把给出的关系R扩充成一新关系R’,使R’具有一定的性质,且所进行的扩充又是最节约的。
比如自反闭包,相当于把关系R对角线上的元素全改成1,其他元素不变,这样得到的R’是自反的,且是改动次数最少的,即是最节约的。

什么是聚点?

在拓扑学、数学分析和复分析中都有聚点的概念。
在拓扑学中设拓扑空间(Xτ)A⊆XxX。若x的每个邻域都含有A \ {x}中的点,则称xA的聚 点。
在数学分析中坐标平面上具有某种性质的点的集合,称为平面点集。给定点集E ,对于任意给定的δ0 ,点P δ去心邻域内,总有E 中点,则称为P E的聚点(或叫作极限点)。
聚点可以是E中的点,也可以不属于E。此聚点要么是内点,要么是边界点。内点是聚点,界点是聚点,孤立点不是聚点。对于有限点集是不存在聚点的。聚点必须相对给定的集合而言,离开了点集E,聚点就没有意义。
在复分析中点集E,若在复平面上的一点z的任意邻域都有E的无穷多个点,则称zE的聚点。
以聚点为圆心,任意大的半径大ε>0画一圆,总有无穷多个点汇聚在该圆内。若聚点是唯一的,则聚点就是极限点。

 

3.2.4 Js中的闭包。更像java的内部类的概念。那么为什么要取名闭包??

 

闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成定义在一个函数内部的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

 

本质

集合 S 闭集当且仅当 Cl(S)=S(这里的clclosure,闭包)。特别的,空集的闭包是空集,X 的闭包是 X。集合的交集的闭包总是集合的闭包的交集的子集(不一定是真子集)。有限多个集合的并集的闭包和这些集合的闭包的并集相等;零个集合的并集为空集,所以这个命题包含了前面的空集的闭包的特殊情况。无限多个集合的并集的闭包不一定等于这些集合的闭包的并集,但前者一定是后者的父集。

A 为包含 S X 子空间,则 S A 中计算得到的闭包等于 A S X 中计算得到的闭包(Cl_A(S) = A ∩ Cl_X(S))的交集。特别的,S A 中是稠密的,当且仅当 A Cl_X(S) 的子集。

 

如果一个程式语言容许函数递回另一个函数的话 (像 Perl 就是),闭包便具有意义。要注意的是,有些语言虽提供匿名函数的功能,但却无法正确处理闭包; Python 这个语言便是一例。如果要想多了解闭包的话,建议你去找本功能性程式 设计的教科书来看。Scheme这个语言不仅支持闭包,更鼓励多加使用。

 

闭包是函数和声明该函数的词法环境的组合。

 

词法作用域

考虑如下情况:

 

function init() {

var name = "Mozilla"; // name 是一个被 init 创建的局部变量

n displayName() { // displayName() 是内部函数,一个闭包

alert(name); // 使用了父函数中 声明的变量

}

displayName();

}

init();

init() 创建了一个局部变量 name 和一个名为 displayName() 的函数。displayName() 是定义在 init() 里的内部函数,仅在该函数体内可用。displayName() 内没有自己的局部变量,然而它可以访问到外部函数的变量,所以 displayName() 可以使用父函数 init() 中声明的变量 name 。但是,如果有同名变量 name 在 displayName() 中被定义,则会使用的 displayName() 中定义的 name 。

 

运行代码可以发现 displayName() 内的 alert() 语句成功的显示了在其父函数中声明的 name 变量的值。这个词法作用域的例子介绍了引擎是如何解析函数嵌套中的变量的。词法作用域中使用的域,是变量在代码中声明的位置所决定的。嵌套的函数可以访问在其外部声明的变量。

 

闭包

现在来考虑如下例子 :

 

function makeFunc() {

var name = "Mozilla";

function displayName() {

alert(name);

}

return displayName;

}

 

var myFunc = makeFunc();

myFunc();

运行这段代码和之前的 init() 示例的效果完全一样。其中的不同 — 也是有意思的地方 — 在于内部函数 displayName() 在执行前,被外部函数返回。

 

第一眼看上去,也许不能直观的看出这段代码能够正常运行。在一些编程语言中,函数中的局部变量仅在函数的执行期间可用。一旦 makeFunc() 执行完毕,我们会认为 name 变量将不能被访问。然而,因为代码运行的没问题,所以很显然在 JavaScript 中并不是这样的。

 

这个谜题的答案是,JavaScript中的函数会形成闭包。 闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。在我们的例子中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用,而 displayName 实例仍可访问其词法作用域中的变量,即可以访问到 name 。由此,当 myFunc 被调用时,name 仍可被访问,其值 Mozilla 就被传递到alert中。

 

下面是一个更有意思的示例 — makeAdder 函数:

 

function makeAdder(x) {

return function(y) {

return x + y;

};

}

 

var add5 = makeAdder(5);

var add10 = makeAdder(10);

 

console.log(add5(2));  // 7

console.log(add10(2)); // 12

在这个示例中,我们定义了 makeAdder(x) 函数,他接受一个参数 x ,并返回一个新的函数。返回的函数接受一个参数 y,并返回x+y的值。

 

从本质上讲,makeAdder 是一个函数工厂 — 他创建了将指定的值和它的参数相加求和的函数。在上面的示例中,我们使用函数工厂创建了两个新函数 — 一个将其参数和 5 求和,另一个和 10 求和。

 

add5 和 add10 都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在 add5的环境中,x 为 5。而在 add10 中,x 则为 10。

 

实用的闭包

闭包很有用,因为他允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

 

 

 

 

 

因此,通常你使用只有一个方法的对象的地方,都可以使用闭包。

 

在 Web 中,你想要这样做的情况特别常见。大部分我们所写的 JavaScript 代码都是基于事件的 — 定义某种行为,然后将其添加到用户触发的事件之上(比如点击或者按键)。我们的代码通常作为回调:为响应事件而执行的函数。

 

假如,我们想在页面上添加一些可以调整字号的按钮。一种方法是以像素为单位指定 body 元素的 font-size,然后通过相对的 em 单位设置页面中其它元素(例如header)的字号:

 

body {

font-family: Helvetica, Arial, sans-serif;

font-size: 12px;

}

 

h1 {

font-size: 1.5em;

}

 

h2 {

font-size: 1.2em;

}

我们的文本尺寸调整按钮可以修改 body 元素的 font-size 属性,由于我们使用相对单位,页面中的其它元素也会相应地调整。

 

闭包,正则- 自动机-词法分析器:

 

 

 

 

数据库的关系和编译原理的关系:

D1 D2 D3 笛卡尔积D1 x D2 x … Dn的子集合,记作R(D1, D2, … , Dn)

R称为关系名,n为关系的目或度

编译原理的关系:

其实应该差不多,D1 D2 D3代表的是语言或者串

D1 D2 D3 笛卡尔积D1 x D2 x … Dn的子集合,记作R(D1, D2, … , Dn)

R称为关系名,n为关系的目或度

那么编译原理的闭包和js或者说java内部类的闭包有什么一样的呢

闭包在

数学中是闭的集合,也就是集合和它的边界的并。

其实看起来不管是js编译原理,java里面的闭包都源自数学里面这个闭包的概念。

编译原理的闭包的概念其实是一个集合里面连接N次,但是数学里面是集合和边界的并,其实要是画一个图理解起来是一样的。 Js的闭包呢?

 

还有关系这个概念数据库里的关系,编译原理里面的关系应该都是脱胎于数学的关系或者二元关系https://baike.baidu.com/item/%E4%BA%8C%E5%85%83%E5%85%B3%E7%B3%BB/2587180?fr=aladdin

见百度百科二元关系的解释。

https://blog.csdn.net/Jbinbin/article/details/84250852

见这个博客 数据库关系的解释。

 

 

  1. 正则表达式

为了描述程序设计语言所有合法的标识符,界符 ,运算符,关键字,常数。

Letter表示任一字母或者下划线

Digit表示数字

那么标识符可以用 letter_(letter_|digit)*  正则表达式表示。

正则表达式可以由较小的正则表达式按照如下规则递归地构建。

每个正则表达式r表示一个语言Lr),这个语言也是根据r的子表达式锁表示的语言递归地定义的。

在某个字母表E 上的正则表达式以及这些表达式所表示的语言。

归纳基础:  字母表用E表示,那个符号有点特殊,打不出,只好用E表示

  1. E是一个正则表达式LE={E},即该语言只包含空串。
  2. 如果aE上的一个符号,那么a是一个正则表达式,并且La={a}。也就是说,这个语言仅包含一个长度为1的符号串a

下面我们举例说明。对于符号集合∑={ab},有:

- 正则表达式a表示语言{a}

- 正则表达式a|b表示语言{ab}

- 正则表达式(a|b)(a|b)表示语言{aaabbabb}

- 正则表达式a*表示语言{ε,aaaaaa,…}

- 正则表达式(a|b)*表示语言{ε,abaaabbabbaaa,…}

- 正则表达式a|a*b表示语言{ababaabaaab,…}

正则定义: 为方便表示,我们可能希望给某些正则表达式命名,并在之后的正则表达式中像符号一样使用这些名字:  d1->r1  比如 digit ->{0-9} digits->digit+

3-11digit就是正则定义,右边是正则表达式。

  1. 每个d1都是一个新符号,他们都不在E中,并且各不相同。
  2. 每个r都是字母表E  U {d1,d2,d3,d4..dn-1}的正则表达式。

  1. 状态转换图-确定的有穷自动机和不确定的又穷自动机

我们可以将正则表达式转换成状态转换图

初始状态  接受状态,终结状态

Relop  关系运算符

Digit表达式对应的状态转换图:

 

自动机:  有穷自动机是识别器,他们只能对每个可能的输入串简单的回答 “是”或者“否”。

    1. 不确定的有穷自动机,对齐边上的标号没有任何限制,一个符号标记离开同一状态的多边条。并且空串E也可以作为标号。
    2. 对于每个状态及自动输入字母表的每个符号,确定的有穷自动有且只有一条离开该状态,以该符号为标号的边。

 

 

 

 

不确定的有穷自动机:

   1一个有穷的状态集合S

   2 一个输入符号集合E 即输入字母表,我们假设代表空串的E不是E中的元素

   3  一个转换函数,他为每个状态和E U {E}中的每个符号都给出了相应的后继状态的集合

   4 S中的一个状态被S0被指定为开始状态

   5  S的一个字节F被指定为接受状态集合

确定的有穷自动机

确定的有穷自动机(Deterministic Finite Automate,下文简称DFA)是NFA的一个特例,其中:

 

标记集合:一个输入符号集合∑,但不包含空串ε;

转换函数:对每个状态s和每个输入符号a,有且仅有一条标号为a的边离开s,即转换函数的对应关系从一对多变为了一对一。

猜你喜欢

转载自blog.csdn.net/Jbinbin/article/details/84344903