stream就是个Iterable<Unit>,最基础的unit是bit,对应二进制流,
从二进制流到character流Iteralbe<Character>是encoding解决的问题,
再往上一个层次是token流,或者word流
1)基础的tokenize就是自然token,即按分隔符切割的,alphabet字符(a-z,1-9等)和非alphabet字符(空格,标点,括号等。
2)自然token不一定是和合法的token,token还必须是语言接受的(预定义的),英文字典里没有的单词就是非法英文单词,再者,像中文这种没有空格分隔的语言,是不存在自然token的,必须用预定义的某种规则识别。
预定义的方式可以是像英文词典那样的有限的,罗列出所有实例,还有一个例子是程序语言的关键字集合;更多是用规则定义,即所谓formal language的概念,就是可以用有限的规则定义出无限的集合。比如正则表达式。规则往往不只一种,一种规则对应一种类型的token。
比如程序语言里,当前字符流是以int开始的,可以解读为identifier,也可以解读为keyword,这里就有一个问题,用哪个规则去匹配呢,有两个原则
1)规则是有优先级的,或者说有顺序的,按顺序一个一个试,前面的匹配了,就不用考虑后面的规则了
2)匹配多长问题,优先匹配长的,比如 “==”,单等号可以赋值匹配运算符,双等号可以匹配比较运算符,匹配长的,即双等号。
这一部分叫词法分析,lexical analysis、tokenize,输出是一个合法的token stream:Iterable<Token>。注意token即包含了分出的词的文本,也包含了类别信息,这一点和01流到Character流不同,Character是没有类别的,token除了文本还有类别,(动词?名词?冠词?)
之前都是从一种流到另一种流
01流 ---encoding--> Character流 --tokenize---> token流
之后的parse部分,不再是输出另一种流,不再是一种线性结构,而是树结构,语法树。这里用到的文法一般是上下文无关文法,对应上下文无关语言。上下文无关意思是非终结字符的展开不依赖于两边的上下文。
正则文法 < 上下文无关文法 < formal grammar
正则文法就是用字符和三种规则(union, concatenation, iteration) 表达的语言。因为正则是CFG的子集,可以用CFG描述正则文法:
R -> alphabet character
R-> RT
T->R| e
R-> R|R
为什么要把左递归该为右递归?
因为右递归是先parse了一部分才递归的,也就是输入在收敛的递归,这种递归是真正的递归。左递归,输入没有任何变化。
为什么要Left Factor?
一个产生式的多个alternative有相同的开始token,改写一下,提取公共factor,最终使得每个alternative的开始token都不同,left factor 保证只有一个production是可行的,因为起始token不同。
自顶向下的parse基本是个路径搜索问题,从start非终结字符(相当于root)开始,分别尝试不同的alternative的产生式。要消除左递归,并且进行left factoring(剪枝作用,根据当前token有预见性的选择alternative)。一个输入token流,如果可以对应多棵parse tree,则这个CFG文法是具有二义性的。无二义性的的CFG从路径搜索的角度就是有且只有一条路径,一个可行解。
leetcode wordbreak ||是分词问题还是parse问题?答案是parse问题,parse问题的特点是整体性,要使得整体匹配。分词只需要考虑自己,不需要考虑对后续其他词的影响。word break以现在的知识点看应该是一种二义性(有多个可行解)、上下文相关的文法。