2.词法分析
词法分析的目的是什么呢? 是把一长串的字符分解成一个个的词素,并且生成(id,2)这种作为语法分析的基础。
首先是词法单元,词素等的定义。
然后是如何输入。
三是输入后如何识别。
识别后如何组成中间式子。
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个数位组成的字母表。
另一种方法是将L和D看做语言,他们的所有串的长度都为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次连接就是只有一个符号的符号串,比如,a,b, c
2次连接是两个符号构成的符号串,比如,aa, ab, ac, ba, bb, bc,等等
3.2 闭包的解释
3.2.1 编译原理书籍闭包解释:闭包和正则
正则表达式的最基本概念来重新介绍一次,主要想让大家更深地理解它。首先我们要重新定义一下“语言”这个概念。“语言”就是指字符串的集合,其中的字符来自于一个有限的字符集合。也就是说,语言总要定义在一个有限的字符集上,但是语言本身可以既可以是有穷集合,也可以是无穷集合。比如“C#语言”就是指满足C#语法的全体字符串的集合,它显然是个无穷集合。当然也可以定义一些简单的语言,比如这个语言{ a }就只有一个成员,那就是一个字母a。后面我们都用大括号{}来表示字符串的集合。所谓正则表达式呢,就是描述一类语言的一种特殊表达式,正则表达式共有2种基本要素:
同时正则表达式定义了3种基本运算规则:
以上三种运算写在一起时克林闭包的优先级高于连接运算,而连接运算的优先级高于并运算。以上就是正则表达式的全部规则!并不是很难理解对吧?下面我们用正则表达式来描述一下刚才各个词素的规则。
首先是关键字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]*。其中的*运算也意味着“标识符”是一种无穷语言,有无数种可能的标识符。本来就是这样,很好理解对吧?
从上面例子可以看出,正则表达式都可以用两种要素和三种基本运算组合出来。但是如果我们要真的拿来描述词法单词的规则,需要一些便于使用的辅助语法,就像上边的方括号语法那样。我们定义一些正则表达式的扩展运算:
用过正则表达式的同学应该都熟悉以上运算了。其实.NET中的正则表达式还提供更多的扩展语法,但我们这次并不使用.NET的正则库,所以就不列出其余的语法了。
我们把所有能用正则表达式表示的语言称作正则语言。很遗憾,并非所有的语言都是正则语言。比如C#,或者所有编程语言、HTML、XML、JSON等等,都不是正则语言。所以不能用正则表达式定义上述语言的规则。但是,用正则表达式来定义词法分析的规则却是非常合适的。大部分编程语言的词素都可以用一个简单的正则表达式来表达。下面就是上述单词的正则表达式定义。
为什么需要闭包。因为编译原理是在一个字符长串或者集合中找到正确的词。
闭包之后是正则表达式
(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) ,则称x是R-关系于y,并记作xRy或R(x,y)。否则称x与y无关系R。但经常地我们把关系与其图等同起来,即:若R⊆X×Y,则R是一个关系。
例如:有四件物件 {球,糖,车,枪} 及四个人 {甲,乙,丙,丁}。 若甲拥有球,乙拥有糖,及丁拥有车,即无人有枪及丙一无所有— 则二元关系"为...拥有"便是R=({球,糖,车,枪}, {甲,乙,丙,丁}, {(球,甲), (糖,乙), (车,丁)})。
其中 R 的首项是物件的集合,次项是人的集合,而末项是由有序对(物件,主人)组成的集合。比如有序对(球,甲)∈G(R),所以我们可写作"球R甲",表示球为甲所拥有。
不同的关系可以有相同的图。以下的关系 ({球,糖,车,枪}, {甲,乙,丁}, {(球,甲), (糖,乙), (车,丁)} 中人人皆是物主,所以与R不同,但两者有相同的图。话虽如此,我们很多时候索性把R定义为G(R), 而 "有序对 (x,y) ∈G(R)" 亦即是 "(x,y) ∈R"。
二元关系可看作成二元函数,这种二元函数把输入元x∈X及y∈Y视为独立变量并求真伪值(即“有序对(x,y) 是或非二元关系中的一元”此一问题)。
若X=Y,则称R为X上的关系。
进行关系运算。
3.2.3数学中的闭包:
数学中是闭的集合,也就是集合和它的边界的并。集合e的全体聚点并上e称为e的闭包。关系的闭包运算时关系上的一元运算,它把给出的关系R扩充成一新关系R’,使R’具有一定的性质,且所进行的扩充又是最“节约”的。
比如自反闭包,相当于把关系R对角线上的元素全改成1,其他元素不变,这样得到的R’是自反的,且是改动次数最少的,即是最“节约”的。
什么是聚点?
在拓扑学、数学分析和复分析中都有聚点的概念。
在拓扑学中设拓扑空间(X,τ),A⊆X,x∈X。若x的每个邻域都含有A \ {x}中的点,则称x为A的聚 点。
在数学分析中坐标平面上具有某种性质的点的集合,称为平面点集。给定点集E ,对于任意给定的δ〉0 ,点P 的δ去心邻域内,总有E 中点,则称为P 是 E的聚点(或叫作极限点)。
聚点可以是E中的点,也可以不属于E。此聚点要么是内点,要么是边界点。内点是聚点,界点是聚点,孤立点不是聚点。对于有限点集是不存在聚点的。聚点必须相对给定的集合而言,离开了点集E,聚点就没有意义。
在复分析中点集E,若在复平面上的一点z的任意邻域都有E的无穷多个点,则称z为E的聚点。
以聚点为圆心,任意大的半径大ε>0画一圆,总有无穷多个点汇聚在该圆内。若聚点是唯一的,则聚点就是极限点。
3.2.4 Js中的闭包。更像java的内部类的概念。那么为什么要取名闭包??
闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
本质
集合 S 是闭集当且仅当 Cl(S)=S(这里的cl即closure,闭包)。特别的,空集的闭包是空集,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
见这个博客 数据库关系的解释。
为了描述程序设计语言所有合法的标识符,界符 ,运算符,关键字,常数。
Letter表示任一字母或者下划线
Digit表示数字
那么标识符可以用 letter_(letter_|digit)* 正则表达式表示。
正则表达式可以由较小的正则表达式按照如下规则递归地构建。
每个正则表达式r表示一个语言L(r),这个语言也是根据r的子表达式锁表示的语言递归地定义的。
在某个字母表E 上的正则表达式以及这些表达式所表示的语言。
归纳基础: 字母表用E表示,那个符号有点特殊,打不出,只好用E表示
下面我们举例说明。对于符号集合∑={a,b},有:
- 正则表达式a表示语言{a};
- 正则表达式a|b表示语言{a,b};
- 正则表达式(a|b)(a|b)表示语言{aa,ab,ba,bb};
- 正则表达式a*表示语言{ε,a,aa,aaa,…};
- 正则表达式(a|b)*表示语言{ε,a,b,aa,ab,ba,bb,aaa,…};
- 正则表达式a|a*b表示语言{a,b,ab,aab,aaab,…}。
正则定义: 为方便表示,我们可能希望给某些正则表达式命名,并在之后的正则表达式中像符号一样使用这些名字: d1->r1 比如 digit ->{0-9} digits->digit+
图3-11digit就是正则定义,右边是正则表达式。
我们可以将正则表达式转换成状态转换图
初始状态 接受状态,终结状态
Relop 关系运算符
Digit表达式对应的状态转换图:
自动机: 有穷自动机是识别器,他们只能对每个可能的输入串简单的回答 “是”或者“否”。
不确定的有穷自动机:
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,即转换函数的对应关系从一对多变为了一对一。