1 弁言
文法用来形貌言语的语法划定规矩,所以不仅能够用在编程言语上,也可用在汉语、英语上。
2 精读
我们将一块语法划定规矩称为 发生式,运用 “Left → Right” 示意恣意发生式,用 “Left => Right” 示意发生式的推导历程,比方关于发生式:
E → i
E → E + E
我们举行推导时,能够如许示意:E => E + E => i + E => i + i + E => i + i + i
也有运用 Left : Right 示意发生式的例子,比方 ANTLR。
BNF 范式经由过程 Left ::= Right 示意发生式。
举个例子,比方 SELECT * FROM table
能够被表达为:
S → SELECT * FROM table
固然这是最牢固的语法,实在场景中,*
能够被替换为其他单词,而 table
不只能够有其他名字,还多是个子表达式。
平经常使用大写的 S 示意文法的开首,称为最先标记。
闭幕符与非闭幕符
下面为了轻易誊写,运用 BNF 范式示意文法。
闭幕符就是语句的闭幕,读到它示意发生式剖析完毕,相反,非闭幕符就是一个新发生式的最先,比方:
<selectStatement> ::= SELECT <selectList> FROM <tableName>
<selectList> ::= <selectField> [ , <selectList> ]
<tableName> ::= <tableName> [ , <tableList> ]
一切 ::=
号左边的都黑白闭幕符,所以 selectList
黑白闭幕符,剖析 selectStatement
时碰到了 selectList
将会进入 selectList
发生式,而剖析到一般 SELECT
单词就不会继承剖析。
关于有二义性的文法,能够经由过程 上下文相干文法 体式格局形貌,也就是在发生式左边补全前提,处置惩罚二义性:
aBc -> a1c | a2c
dBe -> d3e
平常发生式左边都黑白闭幕符,大写字母黑白闭幕符,小写字母是闭幕符。
上面示意,非闭幕符 B
在 ac
之间时,能够剖析为 1
或 2
,而在 de
之间时,剖析为 3
。但我们能够增添一个非闭幕符让发生式可读性更好:
B -> 1 | 2
C -> 3
如许就将上下文相干文法转换为了上下文无关文法。
上下文无关文法
依据是不是依靠上下文,文法分为 上下文相干文法 与 上下文无关文法,平常来讲 上下文相干文法 都能够转换为一堆 上下文无关文法 来处置惩罚,而用顺序处置惩罚 上下文无关文法 相对轻松。
SQL 的文法就是上下文相干文法,在正式引见 SQL 文法之前,举一个简朴的例子,比方我们形貌等号(=)的文法:
SELECT
CASE
WHEN bee = 'red' THEN 'ANGRY'
ELSE 'NEUTRAL'
END AS BeeState
FROM bees;
SELECT * from bees WHERE bee = 'red';
上面两个 SQL 中,等号前后的关键字取决于当前是在 CASE WHEN
语句里,照样在 WHERE
语句里,所以我们以为等号所在位置的文法是上下文相干的。
然则当我们将文法粒度变细,将 CASE WHEN
与 WHERE
区块离别交由两块文法处置惩罚,将等号这个通用的表达式抽离出来,就能够不关心上下文了,这类体式格局称为 上下文无关文法。
附上一个 mysql 上下文无关文法鸠合。
左推导与右推导
上面提到的推导标记 =>
在现实运转历程当中,显然有两种方向左和右:
E + E => ?
从最左边的 E 最先剖析,称为左推导,对语法剖析来讲是自顶向下的体式格局,经常使用要领是递归下落。
从最右侧的 E 最先剖析,称为右推导,对语法剖析来讲是自底向上的体式格局,经常使用要领是移进、规约。
右推导历程比左推导历程庞杂,所以假如斟酌手写,最好运用左推导的体式格局。
左推导的分支展望
比方 select <selectList>
的 selectList
发生式,它能够示意为:
<SelectList> ::= <SelectList> , <SelectField>
| <SelectField>
由于它能够睁开:SelectList => SelectList , a => SelectList , b, a => c, b, a。
但顺序执行时,读到这里会进入死循环,由于 SelectList 能够被无穷睁开,这就是左递归题目。
消弭左递归
消弭左递归平常经由过程转化为右递归的体式格局,由于左递归完整不斲丧 Token,而右递归能够经由过程斲丧 Token 的体式格局跳出死循环。
Token 见上一期精读
精读《手写 SQL 编译器 – 词法剖析》
<SelectList> ::= <SelectField> <G>
<G> ::= , <SelectList>
| null
这现实上是一个通用处置惩罚,能够笼统出来:
E → E + F
E → F
E → FG
G → + FG
G → null
不过我们也不难发明,经由过程通用体式格局消弭左递归后的文法更难以浏览,这是由于用死循环的体式格局诠释题目更轻易让人明白,但会致使机械崩溃。
笔者发起此处不要僵硬的套公式,在套了公式后,再对发生式做一些润饰,让其更具有语义:
<SelectList> ::= <SelectField>
| , <SelectList>
提取左公因式
即便是上下文无关的文法,经由过程递归下落体式格局,很多时刻也必需从左向右超前检察 K 个字符才肯定运用哪一个发生式,这类文法称为 LL(k)。
但假如每次超前检察的内容都有很多字符雷同,会致使第二次最先的超前检察反复剖析字符串,影响机能。最理想的状况是,每次超前检察都不会对已肯定的字符反复检察,处置惩罚要领是提取左公因式。
想象以下的 sql 文法:
<Field> ::= <Text> as <Text>
| <Text> as<String>
| <Text> <Text>
| <Text>
实在 Text 自身也是比较庞杂的发生式,最坏的状况须要对 Text 一连婚配六遍。我们将 Text 公因式提取出来就能够仅婚配一遍,由于无论是何种 Field 发生式,都一定先碰到 Text:
<Field> ::= <Text> <F>
<F> ::= <G>
| <Text>
<G> ::= as <H>
<H> ::= <space> <Text>
| <String>
和消弭左递归一样,提取左公因式也会下降文法的可读性,须要举行工资修复。不过提取左公因式的修复没方法在文法中处置惩罚,在后面的 “函数式” 处置惩罚环节是有方法处置惩罚的,敬请期待。
连系优先级
对 SQL 的文法来讲不存在优先级的观点,所以从某种程度来讲,SQL 的语法庞杂度还不如基础的加减乘除。
3 总结
在完成语法剖析前,须要运用文法形貌 SQL 的语法,文法形貌就是语法剖析的骨干营业代码。
下一篇将引见语法剖析相干学问,协助你一步步打造本身的 SQL 编译器。
4 更多议论
假如你想介入议论,请点击这里,每周都有新的主题,周末或周一宣布。