赞 | 2 |
VIP | 143 |
好人卡 | 1 |
积分 | 1 |
经验 | 216792 |
最后登录 | 2019-10-10 |
在线时间 | 24 小时 |
Lv1.梦旅人
- 梦石
- 0
- 星屑
- 61
- 在线时间
- 24 小时
- 注册时间
- 2008-8-5
- 帖子
- 1924
|
这个和解释器的实现有关,就快上完编译原理了,现炒现卖:
无论是解释器还是编译器,在解释、编译程序的时候都有两个相同的过程,第一个是词法分析,第二个是文法分析。两个过程都等同于把源文件遍历一次,前者是把源文件字元化,后者是解析字元,分析并检验语法,我们这个问题就和两者都有关。
字元化和解析都需要用到一个叫做确定性有限自动机的糟糕物(其英文缩写可能听过的人更多:DFA),和工程学里的有限状态自动机是一个概念,其作用有点像正则表达式,可以用有限的状态来组建一个字母串匹配模型。区别在于正则表达式仅仅是匹配字符串,其字母表是 ASCII 字符集,而确定性有限自动机可以表示任何字母,比如颜色的集合。自动机初始时是在一个“开始”状态,接着就开始逐字母读取,每获取一个字母,就根据“过渡”跳转到相应的状态。过渡是连接两个状态的桥梁,根据输入的字母的不同会有相应的过渡跳转到不同的状态。举个例子:可以设计这样一个自动机,开始状态为 S,接收到左圆括号 ( 字符则跳转到状态 A,在状态 A 时,输入任意字符都过渡这个状态本身,直到获取到一个右圆括号,才跳转到状态 B。如果我们把这个 “)” 所对应的状态设置为一个接受状态,那么这个简单的自动机实际上就是匹配了任意被圆括号包围的字符串,用相应的正则表达式表示就是 /\(.*\)/。
通过字元的有限状态自动机,我们可以用各种分隔符来表示一个字元的开始和结束,到最后,程序语言中的各种字元都会被按照在源件中出现的先后顺序串联起来。
这里就有一个问题:如何区分方法和局部变量。从正则语言的角度来讲,除了一些带特殊符号的方法名之外,方法名与局部变量名没有区别。显然字元的确定性有限自动机并不能有效地区分它们——在字元层上,这两者没有分别,所以需要把问题带到解析层上。
解析也有很多种方法,比较常见的是用 LR 解析器,进行从左到右的解析。在此之前,Ruby 必须要有一个严谨的语法定义:Part A:上下文无关文法;Part B:上下文有关文法。第一个部分是主要是一系列与上下文无关的生产规则,如 expr -> expr + expr 就是一个生产规则,意思是说一个表达式 A 的语法可以衍生为表达式 B + 表达式 C;第二个部分是与上下文有关语法,禁止使用未定义的变量、变量重声明都是上下文有关文法的一种。各种生产规则虽然变化多端,但却并非变化无穷,所以仍然可以被某种确定性有限自动机描述。
Ruby 首先遍历源文件,进行字元化,于是特殊符号和不带特殊符号的方法名或是变量名就分开了。我们姑且给除了关键字以外的所有以字母打头,以字母、数字或下划线结尾的字元取字元名: ID;称 “=” 这个符号为 BECOME;称 self 为 SELF,称小圆点 “.” 为 DOT。
在文法分析开始时,我们已经有了一个完整的字元列表。解释器随后会枚举列表中的字元,进行某种解析。
不难想到这里的存在的歧义:Ruby 方法名中可带 “=” 字元,是一个 ID 跟着一个 BECOME;而赋值语句,也是一个 ID 跟着一个 BECOME。
在这里 Ruby 必须只能做出一个抉择,那就是方法优先,还是变量名优先。再三考虑后,还是决定变量名优先,因为变量名可以统一使用 { ID } 跟着 { BECOME } 的形式来决定。具体的做法类似于:
如果前面没有 { SELF } 接着 { DOT } 的字元,那么当前如果输入了一个 ID 则处于状态 A,如果在状态 A,且下一个字元是 BECOME,那么 Ruby 会假设这是一个赋值语句,并跳转到相应的、独有的状态。所以 ID 表示的符号本身一定是一个局部变量,并把该符号列入符号表中,用某种方式提醒自己这个符号不是方法。
然而这里还有一另一个解析过程,就是 { SELF } 接着 { DOT }。如果出现这个组合,那么预先定义好的上下文无关语法的确定性有限自动机就会过渡到另一个状态 B。如果在状态 B,且下一个字元是 { ID },那么 Ruby 就会认为这是一个方法,并过渡到相应的、独有的状态。
一句话概括:self.XXXX= 和 XXXX= 是由在解释器定义的上下文无关文法所区分的,在其 DFA 中,它们分别处于两个不同的状态,所以相同的 XXXX 字元以及在这之后的字元也会有不同的意义。 |
|